簡述交叉編譯常用的方法及在構建Docker映象中的應用

fliaping發表於2018-10-15

原文連結:blog.fliaping.com/introduce-t…

CrossCompile

軟體編譯

眾所周知,伺服器大部分都是複雜指令集的x86平臺,移動裝置是精簡指令集的ARM平臺,還有IMB的PowerPC平臺,之前家用路由器和一些嵌入式裝置常用的MIPS平臺。 不同平臺的CPU的指令集(ISA,Instruction Set Architecture)是不同的,對於在其上執行的軟體都要編譯成對應的平臺可識別的執行之後才可以執行。

一個可執行檔案的產生需要經過的步驟不盡相同,但都是要將程式語言翻譯成CPU可識別的二進位制指令。而程式語言主要有兩種:編譯型和解釋型,其中編譯型像C/C++,Golang等,都是在執行前編譯,直接生成可執行檔案。另外一種解釋型語言如JavaPythonPHP, 是在執行時進行編譯(執行前也可能編譯,不過是中間碼,例如Java),將程式語言或者中間碼交給預先安裝的直譯器,由直譯器來識別並轉換成相應的機器指令並執行。其實不管是哪種型別,都是需要有可執行的(即CPU可識別的)二進位制檔案來執行於CPU之上。

對於使用解釋型語言的開發者來說,基本上不談編譯,只有開發這個語言直譯器(執行時)的人才會涉及到這個問題。但這個世界不可能只使用解釋型語言,開發者一定會接觸到一些編譯型語言,尤其是在關注到效能,或者是資源受限的情況下。當然那些有極客精神,喜歡搗鼓的人來講更是不可避免。(多說兩句,雖說我稱不上極客,但是還是有些搗鼓的精神的,在技術上從來不會覺得哪些事情做不到,僅僅是代價問題,解決方案不會侷限於熟悉的領域,喜歡嘗試其它可能的方向)

編譯型語言生成可執行檔案最重要的兩步是編譯和連結。

  • 編譯是將程式語言翻譯為機器指令,當然這個過程有很多步驟,通常是先翻譯成組合語言,再由彙編轉換成機器碼。而彙編就是和CPU指令緊密相關的。
  • 連結是分為靜態連結和動態連結,靜態連結就是要把程式依賴的外部庫的二進位制程式碼複製進可執行檔案,而動態連結是指定依賴庫的路徑即可

上面兩步中都是和CPU指令相關的,編譯時要生成目標平臺對應的二進位制程式碼,連結要連結的是目標平臺對應的庫。那麼我們需要在什麼平臺上執行,直接去這個平臺上編譯不就好了麼?當然這樣是可以的。

交叉編譯

交叉編譯: 簡單地說,就是在一個平臺上生成另一個平臺上的可執行程式碼

為什麼要這麼做?
答:有時是因為目的平臺上不允許或不能夠安裝我們所需要的編譯器,而我們又需要這個編譯器的某些特徵;有時是因為目的平臺上的資源貧乏,無法執行我們所需要編譯器;有時又是因為目的平臺還沒有建立,連作業系統都沒有,根本談不上執行什麼編譯器。

接著上面的問題,受限於目標平臺的環境和效能,就產生了交叉編譯。目前主要方式兩種:通過虛擬機器或者對編譯器做文章

虛擬機器實現

虛擬機器是個好東西,能用軟體模擬出不同平臺的硬體環境,做到資源隔離和充分利用,缺點大家都知道,效能損耗。原因也是很簡單的,虛擬機器本質和解釋型語言的直譯器類似,做的都是即時翻譯的工作,翻譯當然要耗費效能,翻譯的級別越低,效能耗費就更嚴重。當然有時候這個額外的消耗卻很值。

優點

  • 對於ARM和其它的嵌入式平臺,效能往往都不如x86平臺,我們通過虛擬機器的方式在x86平臺上進行編譯就可以獲得很高的編譯速度。
  • 最接近目標平臺的環境,使得編譯更容易通過,減少出錯的可能

編譯器實現

通過文章第一段的介紹,編譯器的工作是將程式語言翻譯為另外一種CPU能識別的語言,那麼不同的CPU指令相當於是不同的方言,讓編譯器適配一下不同的方言不就好了麼。例如將英語翻譯為普通話,河南話,四川話。這就是用編譯器實現交叉編譯的方法。

但是要實現交叉編譯需要一系列工具,包括C函式庫,核心檔案,編譯器,連結器,偵錯程式,二進位制工具……, 這些稱為交叉編譯工具鏈。需要這麼多東西的原因在於程式不僅僅是編譯這麼簡單,還要連結依賴的其它的庫檔案,都是需要是針對特定平臺的。由於目前並不在做相關領域的工作,交叉編譯的環境也比較複雜,在此不再詳述。另外有些別人做好的docker映象,可以直接拉下來使用。

常見應用

QEMU

qemu

QEMU (short for Quick Emulator) is a free and open-source hosted hypervisor that performs hardware virtualization.

QEMU is a hosted virtual machine monitor: it emulates the machine's processor through dynamic binary translation and provides a set of different hardware and device models for the machine, enabling it to run a variety of guest operating systems. It also can be used with KVM to run virtual machines at near-native speed (by taking advantage of hardware extensions such as IntelVT). QEMU can also do emulation for user-level processes, allowing applications compiled for one architecture to run on another.

QEMU是一個主機上的VMM(virtual machine monitor),通過動態二進位制轉換來模擬CPU,並提供一系列的硬體模型,使guest os認為自己和硬體直接打交道,其實是同QEMU模擬出來的硬體打交道,QEMU再將這些指令翻譯給真正硬體進行操作。

執行模式

QEMU提供多種執行模式:

  1. User-mode emulation: 這種模式下QEMU上僅進執行一個linux或其他系統程式,由和主機不同的指令集來編譯執行。這種模式一般用於交叉編譯及交叉除錯使用。

  2. System emulation: 這種模式QEMU模擬一個完整的作業系統,包括外設。可用來實現一臺物理主機模擬提供多個虛擬主機。QEMU也支援多種guest OS:Linux,windows,BSD等。支援多種指令集:x86,MIPS,ARMv8,PowerCP,SPARC,MicroBlaze等等。

  3. KVM Hosting: 這種模式下QEMU處理包括KVM映象的啟停和移植,也涉及到硬體的模擬,guest的程式執行由KVM請求呼叫QEMU來實現。

  4. Xen Hosting:這種模式下QEMU僅參與硬體模擬,guest的執行完全對QEMU不可見。

其中User-mode emulation就是用來做交叉編譯用的。

實驗及相關概念

用Go語言來做個實驗,因為它原生支援不同平臺可執行檔案的編譯,通過下面的程式碼片段可以看到,用go編譯了linux-arm64的可執行檔案,但是在x86_64的的機器上並不能執行,因為格式錯誤。

fliaping@June:~/temp$ GOOS=linux GOARCH=arm64 go build hello.go
fliaping@June:~/temp$ ls
hello  hello.go
fliaping@June:~/temp$ file hello
hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, not stripped
fliaping@June:~/temp$ ./hello
-bash: ./hello: cannot execute binary file: Exec format error
複製程式碼

上面的ELF即Executable and Linkable Format,簡單說就是可執行檔案、庫檔案,具體解釋如下:

In computing, the Executable and Linkable Format (ELF, formerly named Extensible Linking Format), is a common standard file format for executable files, object code, shared libraries, and core dumps.

下面的程式碼塊中展示,通過qemu-aarch64-static來執行剛剛構建的arm64的可執行檔案hello,居然就成功了。其實功勞在於qemu把檔案中的指令進行了翻譯,轉換為x86認識的指令。

fliaping@June:~/temp$ ls
hello  hello.go  qemu-aarch64-static
fliaping@June:~/temp$ ./qemu-aarch64-static hello
Hello, 世界
複製程式碼

每次執行前都要加一個命令挺煩的,那麼有沒有辦法讓linux直接執行其它架構可執行檔案?當然有,那就是 binfmt_misc

binfmt_misc is a capability of the Linux kernel which allows arbitrary executable file formats to be recognized and passed to certain user space applications, such as emulators and virtual machines. It is one of a number of binary format handlers in the kernel that are involved in preparing a user-space program to run. The executable formats are registered through the special purpose file system binfmt_misc file-system interface (usually mounted under part of /proc). This is either done directly by sending special sequences to the register procfs file or using a wrapper like Debian-based distributions binfmt-support package or systemd's systemd-binfmt.service.

上面那段話的大體意思就是說linux核心有個功能叫binfmt_misc,能夠識別可執行檔案格式,並傳遞給使用者空間的應用,例如模擬器或虛擬機器。它是核心中二進位制檔案處理程式之一,用於準備程式執行的使用者空間。不同格式的可執行檔案的處理程式通過專用檔案系統binfmt_misc檔案系統介面(通常安裝在/proc目錄下)註冊。註冊方式有:通過將特殊序列傳送到暫存器 procfs檔案、使用基於Debian的binfmt-support包、systemd的systemd-binfmt.service之類的服務或類庫來完成。

註冊的不同格式的處理器都安裝在這個目錄下 /proc/sys/fs/binfmt_misc,我們進去可以看到register和status檔案,接著安裝qemu-user-staticbinfmt-support,並執行前面的hello程式。

# 安裝
sudo apt update
sudo apt install -y qemu-user-static binfmt-support

# /proc/sys/fs/binfmt_misc目錄
fliaping@June:/proc/sys/fs/binfmt_misc$ ls
python2.7  qemu-aarch64  qemu-arm    qemu-cris  qemu-microblaze  qemu-mips64    qemu-mipsel  qemu-ppc64       qemu-ppc64le  qemu-sh4    qemu-sparc        qemu-sparc64  status
python3.6  qemu-alpha    qemu-armeb  qemu-m68k  qemu-mips        qemu-mips64el  qemu-ppc     qemu-ppc64abi32  qemu-s390x    qemu-sh4eb  qemu-sparc32plus  register

# 再次執行上文的arm64可執行檔案,成功執行
fliaping@June:~/temp$ ./hello
Hello, 世界
複製程式碼

這時原理應該清楚了,kernel在處理可執行檔案時通過binfmt_misc機制,找到了qemu-aarch64並連同/usr/bin/qemu-aarch64-static來執行arm64構架的可執行檔案,進而翻譯為x86的指令,於是程式可以跨平臺執行咯。

構建不同平臺的Docker映象

因為docker的興起,一些物聯網平臺也開始廣泛應用,並從中獲得隔離和系統無關的益處。例如resin.io,home assistant。而物聯網裝置最常的就是ARM架構的CPU,ARM架構又分為兩種互不相容的指令集,32位的arm(ARMv3 to ARMv7)和64位的aarch64(ARMv8)。當然也有其它架構的物聯網裝置,所以在製作docker映象的時候需要相容不同的CPU架構。

ARM平臺為例

知道上面的原理之後,docker映象的構建就很容易理解了,因為docker本身隔離的就是一些檔案,裝置之類的東西,實質還是用的宿主機核心,在執行容器時binfmt_misc機制依然是起作用的,只要把qemu相關聯的包放到容器中相應的位置就好了。例如下面的示例:

FROM aarch64/debian:stretch

COPY ./qemu-aarch64-static /usr/bin

RUN apt-get update && apt-get install nginx

EXPOSE 80
複製程式碼

參考內容

  1. 交叉編譯 - 百科
  2. 微處理器 - Wiki
  3. 編譯器的工作過程
  4. QEMU,KVM及QEMU-KVM介紹
  5. binfmt_misc
  6. Executable and Linkable Format
  7. How to Build ARM Docker Images on Intel host

相關文章