title: 【转载】Golang 编译优化之静态链接
date: 2022-05-26 14:34:00
toc: false
index_img: https://puep.qpic.cn/coral/Q3auHgzwzM4fgQ41VTF2rAOrGvBASaprDnwukcq5GutBULkN9WJT9A/0
category:
- Go
tags: - 资料
- Java
- 项目
- 文件
- Golang
- 解决
- 方案
- 格式
- 不同
- 出现
原文:Golang 编译优化之静态链接 - FranzKafka Blog,本站仅作保存记录及调整排版,不做盈利性质的商业行为。
最近开始学习 golang,对于 go 语言的一些特性相当满意。想起来与 Python 兴起时类似的那句话 —— 人生苦短,我用 Python。但 Python 终究只是解释型语言,而作为一个真正的程序员,不写编译型语言是不太合理的。在此我单方面宣布(开个玩笑啦~),不写编译型语言的程序员不是一个合格的程序员。那么,回到 golang 本身,也可以得出类似的一句话:后端苦短,我用 golang ~
大概半个月前,我简单学习了一下 golang 的基础知识,就开始写了自己的第一个 golang 程序,无论是语言特性,公有、私有的界定,与 Java 类似的 interface,自带的格式化工具,超丰富的标准库与大量第三方库,还是高效的编译、测试与运行,都让我对自己主力 C++ 的选择产生了怀疑。这玩意儿这么好,写什么 C++ 呀!
后来开始参与一个小的开源项目,自己添加了一些功能。编译之后跑了跑,没什么问题,于是就放心大胆的发布了 release,结果很多童鞋反馈在一些老版本的系统上报错,编译报错的内容都是 GLIBC_2.28 not found。Google 了半天,发现类似的问题还真不少,于是就想着解决该问题,Google 出来很多的答案无非就两条:要么升级系统,要么升级 glibc 依赖。那这两者我都不想选呢(因为很麻烦),有没有其他的办法可以解决整个问题呢。那这篇文章就是对这个问题的一个记录啦~
Background:为什么会报错#
在直奔主题找解决方案之前,首先要了解一下为什么会报这个错误。那么就从报错本身开始,Glibc 是个什么东东呢,在维基百科上可以看到,其是我们 Linux 内核运行所依赖的一个基础组件库,提供很多标准的 API 接口。这些标准的接口如 open、read、write、malloc、printf、dlopen 等等,可想而知其有多重要了。
在不同的 Linux 发行版本中,Glibc 的版本是不同的。这些不同的版本之间所提供的 API 功能自然也是有差异的。可是这跟 go 程序报错有什么关系呢。这里我们就不得不引出另一个内容了,那就是 ——CGO
CGO 其实是为了在 Go 语言中调用 C 函数而设计的,我们都知道 Linux 或者说 Unix 内核都是由 C 语言实现的。本身 C/C++ 经过多年的发展已经具备了很完备的生态,拥有大量的标准 API。那么 Go 语言在设计时就想到了直接利用这些 API 或者说经过包装之后的 C 程序,要实现这样的机制,就必须通过 CGO。以下为 CGO 使用的一个示例:
package cgoexample
/*
#include <stdio.h>
#include <stdlib.h>
void myprint(char* s) {
printf("%s\n", s);
}
*/
import "C"
import "unsafe"
func Example() {
cs := C.CString("Hello from stdio\n")
C.myprint(cs)
C.free(unsafe.Pointer(cs))
}
既然要通过 CGO 调用 C 提供的标准 API 接口,那么就可能会遇到由于 Glibc 库版本不同导致标准的 API 接口实现上的差异。对于 Go 程序而言这是不希望看到的。所以在很多用到了 CGO 的标准库中会对 Glibc 的版本存在要求。如 net 标准库,os/user 标准库等等。
在我们的报错信息中,我们可以看到该程序应该依赖于 Glibc 2.28 版本。那么我们可以通过 ldd –version 命令确认一下本机的 Glibc 版本。
在出现问题的系统中,我们的 Glibc 版本为 2.23。但是我们编译出来的 go 程序所依赖的 Glibc 版本要求为 2.28 及以上。由于 Glibc 版本过低,导致我们的程序无法正常运行并报错提醒。
Solution: 如何解决报错#
在这里有几个可以选择的解决方案。
方案一 :升级最新的系统,一般而言各大 Linux 发行版本都会在发布最新的版本时集成最新的 Glibc 版本,所以升级系统可以获得最新的 Glibc 版本。下表是一些流行的 Linux 发行版本内集成的 Glibc 版本:
glibc | rhel | centos | sle | debian | ubuntu | kylin | manylinux |
---|---|---|---|---|---|---|---|
2021/2/1 | 2.33 | 21.04 hirsute | |||||
2.32 | 20.10 groovy | ||||||
2020/2/1 | 2.31 | 11 bullseye | 20.04 LTS focal | ||||
2.3 | 19.10 eoan | ||||||
2.29 | 19.04 disco | ||||||
2018/8/1 | 2.28 | 8.3 Ootpa | 8.2.2004 | 10 buster | 18.10 cosmic | V10 Tercel | |
2018/2/1 | 2.27 | 18.04 LTS bionic | |||||
2.26 | 17.10 artful | ||||||
2.26 | 15-SP2 | ||||||
2016/8/4 | 2.24 | 9 stretch | 17.04 zesty | ||||
16.10 yakkety |
从表里我们可以看到,如果要使用 Glibc2.28 及以上的版本,你的系统应该为这几类:CentOS 8.1,Fedora 30,Gentoo – 2020-01-22、Arch Linux 2020.01.22、ubuntu 18.10、Debian 8
方案二 :升级 Gilbc 版本,实际上这是我不推荐的方式。因为 Glibc 是 Linux 系统内很重要的一个组件,无论是升级或者降级都有可能导致系统无法正常工作。当然,在某些情况下你可以通过单独升级 Glibc 的方式进行尝试。
方案三 :将 go 程序以静态链接的形式进行编译。在非交叉编译时,默认 CGO 是开启的,此时编译出来的程序将会以动态链接的形式生成可执行文件。我们可以通过 file 命令或者 ldd 命令检测确认该信息。如下所示:
动态链接本是为了将共享库以动态加载的形式进行链接,从而减少软件自身的体积并实现最大程度上的软件服用,但是一般会对宿主系统动态链接的共享库版本有要求。而静态链接则是在编译时以编译系统内包含的共享库作为资源进行链接,在运行时不再依赖系统的共享库。
那么,针对这个问题,我们可以采用全静态链接的方式进行编译。
使用如下命令开启 CGO 同时静态链接编译:
CGO_ENABLED=1 go build -trimpath -ldflags='-extldflags=-static' -tags musl,osusergo,netgo,sqlite_omit_load_extension -o output -v main.go
编译成功后我们使用命令进行查看,发现已经是静态链接的了。
之后我们将该执行文件放到老系统中进行运行,也能正常运行啦~
参考资料: