0%

交叉编译就是跨平台编译,例如在 window 下编译程序的linux版本,或者在 x86_64 平台下编译 aarch64 版本。跨平台编译在Go语言中非常方便,得益于Go语言汇编器的设计。

MacOS 交叉编译 Linux 程序

本文展示如何在 Apple M1 的平台下编译 Linux aarch64 的应用程序。

  • Apple M1

    1
    2
    3
    4
    5
    6
    7
    ~/WORKDIR/gamelife1314.github.io on  source! ⌚ 13:15:25
    $ uname -pvm
    Darwin Kernel Version 21.4.0: Fri Mar 18 00:46:32 PDT 2022; root:xnu-8020.101.4~15/RELEASE_ARM64_T6000 arm64 arm

    ~/WORKDIR/gamelife1314.github.io on  source! ⌚ 13:15:38
    $ gcc -dumpmachine
    arm64-apple-darwin21.4.0
  • linux aarch64

    1
    2
    3
    4
    5
    ubuntu@vm-docker:~$ gcc -dumpmachine
    aarch64-linux-gnu
    ubuntu@vm-docker:~$ uname -pvm
    #40-Ubuntu SMP Mon Mar 7 08:06:10 UTC 2022 aarch64 aarch64
    ubuntu@vm-docker:~$
阅读全文 »

学习 rust 的第一步当然是安装,在 rust 中,工具链的安装,升级版本切换都是由 rustup 来完成的。rust 的工具链分布在三个不同的 channelstablebetanightly

可以将 rustup 看做 rust 的版本管理器,方便我们在不同的 channel 之间进行切换。 在国内 rust 的相关网站是没有被 GFW 屏蔽的,但是访问速度还是很慢。好在国内有很多镜像源,例如,我这里使用的是中国科学技术大学的镜像,配置的话只需要添加两个环境变量:

  • export RUSTUP_DIST_SERVER=https://mirrors.ustc.edu.cn/rust-static
  • export RUSTUP_UPDATE_ROOT=https://mirrors.ustc.edu.cn/rust-static/rustup

rustup 的安装我们依然使用官方的方式:

curl --proto ‘=https’ --tlsv1.2 -sSf https://sh.rustup.rs | sh

执行结束之后,应该能看到下面这样的信息,而且会默认安装 nightly(每日构建)版本:

我们可以顺手配置以下 cargo 的镜像地址,参考自 中科大 Rust Crates 镜像使用帮助

~/.cargo/config
1
2
3
4
5
6
[source.crates-io]
registry = "https://github.com/rust-lang/crates.io-index"
replace-with = 'ustc'

[source.ustc]
registry = "git://mirrors.ustc.edu.cn/crates.io-index"
阅读全文 »

在部署k8s的时候,因为某些众所周知的原因,k8s.gcr.io 的镜像会拉取失败,本文示例一种或方式能正常拉取镜像,前提是你能科学上网,示例环境:

ubuntu@vm-docker:~/workdir$ lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 21.10
Release:	21.10
Codename:	impish

docker 服务创建一个内嵌的 systemd 目录:

mkdir -p /etc/systemd/system/docker.service.d

创建配置文件 /etc/systemd/system/docker.service.d/http-proxy.conf,并且写入以下内容;配置规则请看 https://docs.docker.com/network/proxy/#use-environment-variables

ubuntu@vm-docker:~/workdir$ cat /etc/systemd/system/docker.service.d/http-proxy.conf
[Service]
Environment="HTTP_PROXY=http://192.168.3.100:1087"
Environment="HTTPS_PROXY=http://192.168.3.100:1087"
Environment="NO_PROXY=localhost,127.0.0.1,https://******.mirror.aliyuncs.com"

更新配置并且重启docker:

systemctl daemon-reload && systemctl restart docker

验证配置加载成功:

ubuntu@vm-docker:~/workdir$ sudo systemctl show --property=Environment docker
Environment=HTTP_PROXY=http://192.168.3.100:1087 HTTPS_PROXY=http://192.168.3.100:1087 NO_PROXY=localhost,127.0.0.1,https://******.mirror.aliyuncs.com
ubuntu@vm-docker:~/workdir$

测试镜像拉取:

参考文章

  1. 下载k8s.gcr.io仓库的镜像的两个方式

依赖注入是一种通用技术,通过显式地为组件提供它们工作所需的所有依赖关系,生成灵活且松耦合的代码。在Go语言中,我们经常采用下面这样的方式为构造器传递依赖:

1
2
// NewUserStore returns a UserStore that uses cfg and db as dependencies.
func NewUserStore(cfg *Config, db *mysql.DB) (*UserStore, error) {...}

这种技术在小规模上效果很好,但较大的应用程序可能有一个复杂的依赖关系图,导致一大块初始化代码依赖于顺序。通常很难干净地分解这段代码,尤其是某些依赖项被多次使用。如果涉及到服务替换可能会更痛苦,因为它涉及通过添加一组全新的依赖项,我们需要修改依赖关系图。如果大家干过这种事情,发现这种代码的修改很繁琐。

依赖注入工具旨在简化初始化代码的管理,我们只需要将自己的服务及其依赖关系描述为代码或配置,然后依赖注入工具会处理生成的依赖关系图,确定排序并且为每个服务自动传递所需要的依赖。通过更改函数签名,添加或删除初始化程序就可以更改应用程序的依赖项,然后依赖注入完成为整个依赖关系图生成初始化代码的繁琐工作。

在Go语言中,这样依赖工具有不少,例如:diginject 以及 wire。这次我们着重介绍 wire,相对其他两个有如下优势:

  1. wire 使用代码生成而不是运行时反射。因为当依赖图变得复杂时,运行时依赖注入可能很难跟踪和调试。使用代码生成意味着在运行时执行的初始化代码是常规的、惯用的 Go 代码,易于理解和调试;

  2. wire 使用Go类型名称识别依赖项,不用像其他的服务定位器,需要为每个依赖项定义一个名称;

  3. wire 更容易避免依赖膨胀。 Wire 生成的代码只会导入需要的依赖项,因此二进制文件不会有未使用的导入。然而运行时依赖注入器直到运行时才能识别未使用的依赖项;

  4. Wire 的依赖图是静态可知的,便于工具可视化;

阅读全文 »

ABI(Application Binary Interface),即应用程序二进制接口,定义了函数调用时参数和返回值如何传递。就像C语言 x86-64 系统中,返回值保存在寄存器 %rax 中,前6个参数分别通过寄存器 %rdi%rsi%rdx%rcx%r8 以及 %r9 传递。

但是 Go 语言使用了一套跨架构通用 ABI 设计,它定义了数据在内存上的布局和函数之间的调用规约,这个调用规约是不稳定的,是会随着 Go 的版本进行变换的,称之为 ABIInternal。如果我们想开发汇编代码,应该使用稳定的 ABI0。所有原代码中定义的 Go 函数都遵循 ABIInternal,两种调用规约下的函数可以通过透明的 wrapper 相互调用。

之所以有两套调用规约,并且一个是稳定的(ABI0,承诺向后兼容),一个是不稳定的(ABIInternal,不承诺向后兼容)是因为一开始Go的调用规约约定所有的参数和返回值都通过栈传递,并且很多Go内部的包中有很多基于这个机制编写的汇编代码,例如 math/big,如果现在想升级调用规约,那么这么多汇编代码都得重写,显然不是很现实。所以,比较好的办法是引入一种新的私有约定,不承诺向后兼容,但可以在多个调用规约之间透明互调。私有的调用规约用于Go代码最终汇编的生成,稳定的调用规约用于汇编代码开发,由编译器完成两者之间的自动互调用。更多的内容可以查看 Proposal: Create an undefined internal calling convention

Go1.17 Release Notes Compiler 就对原有的调用规约做了更新,从基于栈的参数传递更新成基于寄存器,基准测试发现,性能有 5% 的提升,二进制大小减少 2%,但是 Go1.17 只在 Amd64 平台上实现了。

Go1.18 Release Notes Compiler 开始支持 GOARCH=arm64OARCH=ppc64, ppc64le。在 64ARM64PowerPC 系统上,基准测试显示性能提升 10% 或更多。

也就是说,在Go的调用规约中,我们需要遵循以下这些点:

  • 如果想写汇编代码,那么可以基于 ABI0,通过栈传递参数,汇编中使用 FP 等伪寄存器传递和访问参数以及返回值;
  • ABI0 是当前的调用约定,它在堆栈上传递参数和结果,在调用时破坏所有寄存器,并且有一些平台相关的固定寄存器;
  • ABIInternal 不稳定,可能会随版本变化。最初的时候它是与 ABI0 相同的,但 ABIInternal 为扩展提供了更多的可能性;
阅读全文 »

请看正文一张图。

阅读全文 »

汇编语言是最接近机器代码的人类可读语言,通过阅读汇编代码,我们可以了解到自己所编写的高级语言代码最终生成的指令都是什么,以便更好的掌握高级语言和了解计算机系统。Go 语言的汇编器基于 Plan9 汇编器,并且在此基础之上定义了一些创新。

阅读全文 »

在Go语言中,为了控制低级别的 runtime 行为,官方提供了一些环境变量,主要有:

  • GOGC
  • GODEBUG
  • GOMAXPROCS
  • GORACE
  • GOTRACEBACK

初次之外,还有用于编译期的 GOROOTGOPATHGOOSGOARCH,以及用于玩转SSA的 GOSSAFUNC

阅读全文 »

ELF(Executable and Linking Format) 是linux系统下可执行文件,目标文件,共享链接库和内核转储文件的格式。维基百科中是这样描述的:

在计算机科学中,ELF文件是一种用于可执行文件、目标文件、共享库和核心转储(core dump)的标准文件格式。其中核心转储是指: 操作系统在进程收到某些信号而终止时,将此时进程地址空间的内容以及有关进程状态的其他信息写出的一个磁盘文件。这种信息往往用于调试。

  • 可重定位文件(relocatable file) 它保存了一些可以和其他目标文件链接并生成可执行文件或者共享库的二进制代码和数据;
  • 可执行文件(excutable file) 它保存了适合直接加载到内存中执行的二进制程序;
  • 共享库文件(shared object file 一种特殊的可重定位目标文件,可以在加载或者运行时被动态的加载进内存并链接。
  • 核心转储文件(core dump) 是操作系统在进程收到某些信号而终止运行时,将此时进程地址空间的内容以及有关进程状态的其他信息写入一个磁盘文件。这种信息往往用于调试。

ELF文件主要由四部分组成:

  • ELF Header:主要包括文件的类型,架构,程序入口地址,Program HeaderSection Header 的大小,数量,偏移量等;

  • Programe Header:列举所有有效的 segments 的属性,描述如何创建进程运行时内存镜像,当内核看到这些 segments 时,使用 mmap 将他们映射到虚拟地址空间,为程序的运行准备;

  • Section:在ELF文件中,数据和代码分开存放的,这样可以按照其功能属性分成一些区域,比如程序、数据、符号表等。这些分离存放的区域在ELF文件中反映成section

  • Section Header:定义ELF文件中所有的 section,用于链接和重定位。对于可执行文件,有四个主要部分:.text.data.rodata.bss

ELF 文件各个部分的布局如下:

阅读全文 »

UML 是一种开放的方法,用于说明、可视化、构建和编写一个正在开发的、面向对象的、软件密集系统的制品的开放方法。UML展现了一系列最佳工程实践,这些最佳实践在对大规模,复杂系统进行建模方面,特别是在软件架构层次已经被验证有效。

阅读全文 »