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
編譯成功後我們使用命令進行查看,發現已經是靜態鏈接的了。
之後我們將該執行檔放到舊系統中進行運行,也能正常運行啦~
參考資料: