做嵌入式開發過程中常常提到交叉編譯,這到底是怎麼回事?本文從先介紹一些作者自己對計算的一些思考。
1. 從計算說起
多讀幾下計算機這個名字,我們大概會去想這應該是一個機器、用來做計算的,它應該和我們理解的加減乘除四則運算有關係。應該是一個機器可以幫我們做一些運算。運算是啥意思呢?就是從一些資訊的到另外一些資訊的過程。
- 把遇到的問題抽象出一個模型。
- 把這個抽象的模型對映到另一個實際的事物上。
- 用這個實際事物一些特性“自然地”的到一些東西。
- 把的到的東西轉換為遇到的問題的結果。
舉個例子:
現在要請人吃飯,需要準備筷子,已經知道一人需要一雙筷子(就是兩支),現在來了A個人,一共需要多少支筷子。(這是具體問題)
當然,聰明如你,肯定知道需要2A支筷子。為了增加一下難度,現在來了33223493個人,你能一下算出結果嗎?大概不太能很快的到結果。此時需要藉助一些工具,如草稿紙,如計算器。在草稿紙或者計算器上進行一個運算。(這裡就把要準備多少支筷子這個事情轉換為草稿紙計算或者計算器計算這個過程了,實現了轉換到另一個實際事物上了。)
你學過四則運算,你肯定會用豎式計算,得到一個結果。如果你決定充分利用現代科技的技術,你會自然在計算器上按下對應的按鍵,的到結果。 總之,這兩種方式都讓你自然地的到了一個結果。
最後你根據草稿紙或者計算器上顯示的數字,得到了要準備的筷子數目。
這裡我們從總人數,得到了要準備的筷子數目:這就是計算過程。
當然,上面是一個簡單的例子,簡單到讓我們會以為我們的現代科技也不過如此。但是實際上計算機面對的問題可比這個複雜。
- 我們稍微增加一下問題,就是說一支筷子0.5元,一共要多少錢?
- 考慮到薄利多銷,如果數目超過1000支不超過2000支的打9折,2000~3000打8折,請問一共要多少錢?
- 出現了一個競爭賣家,優惠規則是一律88折,請問應該選哪一家?
- …
諸如此類問題可以一直問下去。相信你已經通過這些問題,學會了計算這個過程了,請幫我計算一下明天是否會下雨這個問題。
畢竟小學二年級就已經開始算術入門了。
這裡說到幾個關鍵的步驟,建立模型,這就是一堆演算法研究員乾的工作。 自然得到另外一個結果,這就是計算機和電子工程在研究的事情。
這兩個過程其實是強耦合的。
這裡再來解釋一下Hello World的事情,它是大多人接觸程式設計的初體驗:
print("Hello World")
按下回車,計算機就乖乖在螢幕上輸出 Hello World。其實我們有如下看似非常自然的共識:
- 在螢幕上顯示 Hello World這樣的圖案,我們就認為執行成功了。
但是實際上,我們並沒有說到底是白底黑字還是黑底白字。
但計算機怎麼就讀懂了我們的指令?
計算機並不能看懂我們寫的abcd,它只能看懂0101序列。

0101序列和abcd的區別是啥,為啥能看懂數字而不能看懂字母?
本質上來說,計算機也看不懂0101序列,它只能理解電子元器件的物理狀態。0101序列是人們對電子元器件物理狀態的抽象。
電子計算機由電子元器件實現,最核心部件是電晶體。電晶體作為一種電子器件,最重要的參數是電壓電流。電子元器件通電,即可在電子元器件不同的地方測量電壓和電流,這是電子元器件最基本的特性。而這些電壓電流就可以用來對世界進行抽象。
C 語言的教材告訴我們,我們寫的程式碼會被編譯為計算機能夠識別的二進位制語言,然後才能在計算機上執行。也許你聽說過,chrome 從原始碼編譯,需要很長時間。那麼編譯是什麼呢?
計算機程式碼編譯,把人類語言翻譯為計算機能看懂的語言

mosfet,在不同埠施加電壓,可以在另外埠測得電壓,另外埠的電壓和在當前埠施加電壓大小有關係 製造 cpu 時,會用一種非常重要的器件 mosfet。其特點是:
- 在一個埠施加電壓(記為輸入電壓),另外埠也可以測量到電壓(記為輸出電壓)
- 輸入電壓變化,則輸出電壓會(自動)變化
電壓是連續變化的物理量,在計算機內部處理並不方便。此外精確的電壓也不好控制。雖然到控制電壓到某個精準值很困難,但是讓電壓低於一個值(不管低多少,只要低就行)或者高於一個值(不管高多少,只要高就行),這個在物理上實現起來就比較容易。比如大學考試,不掛科(不小於 60)可比恰好及格容易。
通常把電壓高於一個值的狀態叫做高電平,低於一個值的狀態做低電平。其中高電平和低電平的狀態是可以改變的。其中高電平和低電平之間是涇渭分明的,高電平的最小值一定要比低電平的最高值要大。充分考慮元器件的電氣特性,並且精心設計,這一切都是可以實現的。
對於這些元器件來說,它們在高低電平狀態之間的跳轉,是物理規律。只要它輸入電平在某個狀態,就一定讓輸出狀態是一個確定的狀態,用電子元器件的物理行為來模擬計算過程。這就是計算機計算的本質。
所謂0101序列,描述的就是計算機中複雜的元器件的電平高低狀態。01和高低狀態之間沒有絕對關係,可以用1表示高電平0表示低電平,也可以用1表示低電平0表示高電平。不過一旦確定,就要按照確定的規則來編寫程式,否則就會出錯。
不同的 cpu 架構就存在這諸如高低電平表示方式不一樣的問題。當然,高低電平只是一種差異,還有比如指令集不一樣的差異等。這都導致,不同 cpu,在輸入同樣電平序列時候,輸出可能不一樣。最後展示在輸出平臺,如螢幕上,得到的結果不一樣。
其實在計算機內部都是一系列狀態的集合,不管是原始碼還是 01 序列,計算機其實是分不清楚的。我們只是進行了抽象,就解決了這些問題。為了實現人類可以看懂,序列的排布可能並不適合 cpu 運算出結構。所以需要編譯,把這些命令轉換為適合計算機運算時候的電平狀態,這就是編譯乾的事情。
當然,這個轉換過程十分複雜,上面只是簡單地介紹。這裡想說明的問題是,人眼看起來相同的原始碼,在不同平臺上,實際上是不同的。翻譯為最終機器可以執行的電平狀態,也是不同的。這也是不同平臺執行檔不一定能夠通用的原因。
舉一個不是那麼恰當的例子,如果你肚子餓了,你想吃東西,你在中國就說:我餓了,我要吃東;但如果你到了美國,你可能要講 I am hungry, and I want some food. 如果在中國講美國話,或者在美國講中國話,大機率會進行得不那麼順利,甚至要一直捱餓。應為人家聽不懂你在說啥呀。
1.2 不同架構,執行檔不一樣
架構的意思就是 cpu 的結構和規則,比如說 cpu 的指令是是 32 位還是 64 位,cpu 指令集是 cisc 還是 risc。如果要簡單類比,就是有中國打工人,美國打工人。計算機有不同的架構(確切說是 CPU):
- x86,這是大多數 pc 的平臺
- arm 平臺,多用在嵌入式裝置,以及大部分智慧手機
- IBM 的 powerPC 架構
- mips,國產龍芯用的這個
- RISCV 最近非常火
不同架構之間的執行檔通常是不通用的,最常見的是 windows 上的應用不能直接拿到 linux 上來用,ios 上的應用不能拿到安卓上的來用。
不同架構有效能功耗的差異,針對不同應用場景。總之,就出現了有不同架構這麼一個事實。一句話總結,計算機架構不同是有歷史原因的。
交叉編譯,重點就在這個交叉上。就是在一種規則下,通曉了另一種規則。就是要理解,不同計算機的架構是不一樣的。
2. 編譯也是個體力活,交叉編譯解決大問題
不同 cpu 架構不一樣,編譯出來的執行檔不通用不僅不奇怪,而且理所當然。不同平臺有不同優勢。有的架構計算能力強,可以用來編譯;有的架構功耗低,用來執行簡單程式,畢竟可以讓你多用一會兒。
當然我們又想馬兒跑,又想馬兒不吃草。其實也是可以的,就是有點麻煩,這就是交叉編譯,在一個平臺編譯可以在另外一個平臺執行的執行檔。
比如說可以在 x86 平臺上編譯能夠在 arm 平臺上執行的執行檔。本質上,就是在 x86 平臺上,按照 arm 平臺執行的規則翻譯了執行檔。這時候編譯出的結果,反而不能在 x86 平臺上來跑。
舉個例子,交叉編譯就是一個美國人到中國來用中文考試,考試結果其它美國人看不懂,他自己雖然能看懂但也沒有什麼用,但是中國人可以看懂的。不過他考試的方式是先用英文作答,然後用翻譯軟體翻譯為中文。
3. x86 編譯 arm 編譯器介紹
這部分來自這裡,但似乎連結掛了,這裡進行一下摘抄吧。
交叉編譯工具鏈的命名規則為:arch [-vendor] [-os] [-(gnu)eabi]
- arch - 體系架構,如 ARM,MIPS
- verdor - 工具鏈提供商
- os - 目標作業系統
- eabi - 嵌入式應用二進位制介面
根據對作業系統的支援與否,ARM GCC 可分為支援和不支援作業系統,如
- arm-none-eabi:這個是沒有作業系統的,自然不可能支援那些跟作業系統關係密切的函式,比如 fork(2),它使用的是 newlib 這個專用於嵌入式系統的 C 庫。
- arm-none-linux-eabi:用於 Linux 的,使用 Glibc
3.1 交叉編譯例子
3.1.1 arm-none-eabi-gcc
(ARM architecture,no vendor,not target an operating system,complies with the ARM EABI)用於編譯 ARM 架構的裸機系統(包括 ARM Linux 的 boot、kernel,不適用編譯 Linux 應用 Application),一般適合 ARM7、Cortex-M 和 Cortex-R 核心的晶片使用,所以不支援那些跟作業系統關係密切的函式,比如 fork(2),他使用的是 newlib 這個專用於嵌入式系統的 C 庫。
3.1.2 arm-none-linux-gnueabi-gcc
(ARM architecture, no vendor, creates binaries that run on the Linux operating system, and uses the GNU EABI)
主要用於基於 ARM 架構的 Linux 系統,可用於編譯 ARM 架構的 u-boot、Linux 核心、linux 應用等。 arm-none-linux-gnueabi 基於 GCC,使用 Glibc 庫,經過 Codesourcery 公司最佳化過推出的編譯器。 arm-none-linux-gnueabi-xxx 交叉編譯工具的浮點運算非常優秀。一般 ARM9、ARM11、Cortex-A 核心,帶有 Linux 作業系統的會用到。
3.1.3 arm-eabi-gcc
Android ARM 編譯器。
3.1.4 armcc
ARM 公司推出的編譯工具,功能和 arm-none-eabi 類似,可以編譯裸機程式(u-boot、kernel),但是不能編譯 Linux 應用程式。armcc 一般和 ARM 開發工具一起,Keil MDK、ADS、RVDS 和 DS-5 中的編譯器都是 armcc,所以 armcc 編譯器都是收費的(愛國版除外,呵呵~~)。
3.1.5 arm-none-uclinuxeabi-gcc 和 arm-none-symbianelf-gcc
arm-none-uclinuxeabi 用於 uCLinux,使用 Glibc。 arm-none-symbianelf 用於 symbian,沒用過,不知道 C 庫是什麼 。
3.2 Codesourcery 產品
Codesourcery 推出的產品叫 Sourcery G++ Lite Edition,其中基於 command-line 的編譯器是免費的,在官網上可以下載,而其中包含的 IDE 和 debug 工具是收費的,當然也有 30 天試用版本的。
目前 CodeSourcery 已經由明導國際(Mentor Graphics)收購,所以原本的網站風格已經全部變為 Mentor 樣式,但是 Sourcery G++ Lite Edition 同樣可以註冊後免費下載。
Codesourcery 一直是在做 ARM 目標 GCC 的開發和最佳化,它的 ARM GCC 在目前在市場上非常優秀,很多 patch 可能還沒被 gcc 接受,所以還是應該直接用它的(而且他提供 Windows 下 mingw 交叉編譯的和 Linux 下的二進位制版本,比較方便;如果不是很有時間和興趣,不建議下載 src 原始碼包自己編譯,很麻煩,Codesourcery 給的 shell 指令碼很多時候根本沒辦法直接用,得自行提取關鍵的部分手工執行,又費精力又費時間,如果想知道細節,其實不用自己編譯一遍,看看他是用什麼步驟構建的即可,如果你對交叉編譯器感興趣的話。
3.3 ABI 和 EABI
ABI:二進位制應用程式介面(Application Binary Interface (ABI) for the ARM Architecture)。在計算機中,應用二進位制介面描述了應用程式(或者其他型別)和作業系統之間或其他應用程式的低階介面。
EABI:嵌入式 ABI。嵌入式應用二進位制介面指定了檔案格式、資料型別、暫存器使用、堆積組織最佳化和在一個嵌入式軟體中的參數的標準約定。開發者使用自己的組合語言也可以使用 EABI 作為與相容的編譯器生成的組合語言的介面。
兩者主要區別是,ABI 是計算機上的,EABI 是嵌入式平臺上(如 ARM,MIPS 等)。
3.2.1 arm-linux-gnueabi-gcc 和 arm-linux-gnueabihf-gcc:
兩個交叉編譯器分別適用於 armel 和 armhf 兩個不同的架構,armel 和 armhf 這兩種架構在對待浮點運算採取了不同的策略(有 fpu 的 arm 才能支援這兩種浮點運算策略)。
其實這兩個交叉編譯器只不過是 gcc 的選項 -mfloat-abi 的預設值不同。gcc 的選項 -mfloat-abi 有三種值 soft、softfp、hard(其中後兩者都要求 arm 裡有 fpu 浮點運算單元,soft 與後兩者是相容的,但 softfp 和 hard 兩種模式互不相容):
- soft: 不用 fpu 進行浮點計算,即使有 fpu 浮點運算單元也不用,而是使用軟體模式。
- softfp: armel 架構(對應的編譯器為 arm-linux-gnueabi-gcc )採用的預設值,用 fpu 計算,但是傳參數用普通暫存器傳,這樣中斷的時候,只需要儲存普通暫存器,中斷負荷小,但是參數需要轉換成浮點的再計算。
- hard: armhf 架構(對應的編譯器 arm-linux-gnueabihf-gcc )採用的預設值,用 fpu 計算,傳參數也用 fpu 中的浮點暫存器傳,省去了轉換,效能最好,但是中斷負荷高。
總結
本文從概念上簡單說明了計算機編譯個過程,詳細解釋了交叉編譯要做的事情,最後給出了交叉編譯說明的相關連結。講述過程可能並不嚴謹,主要目的是說明清楚這些問題。