簡述交叉編譯經常使用的方法及在構建Docker鏡像中的應用

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

CrossCompile

軟件編譯

衆所周知,服務器大部分都是複雜指令集的x86平臺,移動設備是精簡指令集的ARM平臺,還有IMB的PowerPC平臺,以前家用路由器和一些嵌入式設備經常使用的MIPS平臺。 不一樣平臺的CPU的指令集(ISA,Instruction Set Architecture)是不一樣的,對於在其上運行的軟件都要編譯成對應的平臺可識別的執行以後才能夠運行。python

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

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

編譯型語言生成可執行文件最重要的兩步是編譯和連接。docker

  • 編譯是將編程語言翻譯爲機器指令,固然這個過程有不少步驟,一般是先翻譯成彙編語言,再由彙編轉換成機器碼。而彙編就是和CPU指令緊密相關的。
  • 連接是分爲靜態連接和動態連接,靜態連接就是要把程序依賴的外部庫的二進制代碼複製進可執行文件,而動態連接是指定依賴庫的路徑便可

上面兩步中都是和CPU指令相關的,編譯時要生成目標平臺對應的二進制代碼,連接要連接的是目標平臺對應的庫。那麼咱們須要在什麼平臺上運行,直接去這個平臺上編譯不就行了麼?固然這樣是能夠的。編程

交叉編譯

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

爲何要這麼作?
答:有時是由於目的平臺上不容許或不可以安裝咱們所須要的編譯器,而咱們又須要這個編譯器的某些特徵;有時是由於目的平臺上的資源貧乏,沒法運行咱們所須要編譯器;有時又是由於目的平臺尚未創建,連操做系統都沒有,根本談不上運行什麼編譯器。api

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

虛擬機實現

虛擬機是個好東西,能用軟件模擬出不一樣平臺的硬件環境,作到資源隔離和充分利用,缺點你們都知道,性能損耗。緣由也是很簡單的,虛擬機本質和解釋型語言的解釋器相似,作的都是即時翻譯的工做,翻譯固然要耗費性能,翻譯的級別越低,性能耗費就更嚴重。固然有時候這個額外的消耗卻很值。服務器

優勢

  • 對於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
相關文章
相關標籤/搜索