V1.0 2024年6月17日 釋出於部落格園
目錄
- 目錄
- 基本概念
- 本地編譯
- 交叉編譯
- 如何選擇編譯器
- 目標晶片架構
- 大小端
- 目標作業系統
- C標準庫型別
- 應用二進位制介面
- 浮點模式
- 編譯器版本號
- 編譯器型別對程式的影響
- 安裝軟浮點編譯器
- 執行軟浮點動態編譯的程式
- 開發板的glibc庫型別
- 執行軟浮點靜態編譯的程式
- 如何安裝ARM-GCC
- 使用APT安裝ARM-GCC
- 交叉編譯Hello World程式
- 下載第三方製作好的工具鏈
- 下載
- 安裝
- 使用
- 使用APT安裝ARM-GCC
- 參考資料
基本概念
本地編譯
- 編譯器執行在X86_64架構平臺上,編譯生成X86_64架構的可執行程式
這種編譯器和目標程式都是相同架構的編譯過程,被稱為 本地編譯 。
交叉編譯
而當前我們希望的是編譯器執行在x86架構平臺上,編譯生成ARM架構的可執行程式,這種編譯器和目標程式執行在不同架構的編譯過程,被稱為 交叉編譯。
既然已經有本地編譯,為什麼需要交叉編譯?這是因為通常編譯工具鏈對編譯環境有較高的要求,編譯複雜的程式時,可能需要巨大的儲存空間以及強大的CPU運算能力加快編譯速度。常見的ARM 架構平臺資源有限,無論是儲存空間還是CPU運算能力,都與X86平臺相去甚遠,特別是對於MCU平臺,安裝編譯器根本無從談起。有了交叉編譯,我們就可以在PC上快速編譯出針對其他架構的可執行程式。
相對的,能進行架構“交叉”編譯過程的編譯器,就被稱為 交叉編譯器(Cross compiler)。 交叉編譯器聽起來是個新概念,但在MCU開發中一直使用的就是交叉編譯器, 例如開發STM32、RT1052所使用的IDE軟體Keil(MDK)或IAR,就是在Windows x86架構編譯,生成MCU平臺的應用程式,最後下載到板子執行。
如何選擇編譯器
除了我們安裝的arm-linux-gnueabihf-gcc外,編譯器還有很多版本,如arm-linux-gnueabi-gcc,《GCC和Hello World》章節中使用的本地編譯器gcc全名為x86_64-linux-gnu-gcc。
目前大部分ARM開發者使用的都是由Linaro組織提供的交叉編譯器,包括前面使用APT安裝的ARM-GCC工具鏈,它的來源也是Linaro。Linaro是由ARM發起,與其它ARM SOC公司共同投資的非盈利組織。我們可以在它官網的如下地址找到它提供的ARM交叉編譯器:Linaro Releases ,如下圖所示,在它提供的編譯器列表中首先選擇版本號,然後可選擇ARM架構型別,最後是具體的編譯器平臺。
編譯器的命名沒有嚴格的規則,但它們的名字中一般包含我們最關心的內容,可根據它們的名字選擇要使用的編譯器:
arch [-os] [-(gnu)eabi(hf)] -gcc
其中的各欄位如下表所示。
GCC編譯器命名格式
欄位 | 含義 |
---|---|
arch | 目標晶片架構 |
os | 作業系統 |
gnu | C標準庫型別 |
eabi | 應用二進位制介面 |
hf | 浮點模式 |
以我們安裝的arm-linux-gnueabihf-gcc編譯器為例,表示它的目標晶片架構為ARM,目標作業系統為Linux,使用GNU的C標準庫即glibc,使用嵌入式應用二進位制介面(eabi),編譯器的浮點模式為硬浮點hard-float。而另一種名為arm-linux-gnueabi- gcc的編譯器與它的差別就在於是否帶“hf”,不帶“hf”表示它使用soft-float模式。
關於編譯器的各個欄位詳細說明如下:
目標晶片架構
目標晶片架構就是指交叉編譯器生成的程式執行的平臺,如ARM、MIPS,其中ARM交叉編譯器又分為ARMv7、ARMv8及aarch64架構。本書使用的i.MX 6ULL的核心為Cortex-A7,它使用的是ARMv7架構。 arm-linux-gnueabihf-gcc直接以arm表示ARMv7架構。
大小端
指目標晶片的大小端模式,i.MX 6ULL使用的是小端模式。若是大端模式(big edian),編譯器名字中會帶“be”或“eb”欄位進行標標註。
目標作業系統
目標作業系統表示編譯後的程式執行的系統,主要有Linux或bare-metal(無作業系統)兩種,arm-linux-gnueabi-gcc 表示它目標程式的執行環境為Linux系統,程式可以使用Linux下的C標準庫或Linux核心提供的API,如fork等程序函式。而arm- eabi-gcc或arm-none-eabi-gcc表示它們的目標程式執行在無作業系統的環境中。
所以嚴格來說,我們編譯Linux應用程式時應該使用帶“linux”的編譯器,而編譯uboot、裸機程式時,應該使用“bare-metal”型別的裸機編譯器,但很多開發者常常把它們混用也沒有出現問題,這一般是因為開發者編寫的裸機程式本身就沒有使用到Linux系統提供的API,所以才不會出錯。
C標準庫型別
C標準庫型別通常有gnu、uclibc等,分別表示GNU的glibc庫和uclibc庫,這取決於目標作業系統提供的C庫型別,不過由於glibc和uclibc庫是相容的,所以開發者在編通常直接使用GNU型別的編譯器而不管目標系統中的C庫型別。 除了裸機編譯器不帶C庫之外,其它編譯器的C庫型別都是glibc庫的,如arm-linux-gnueabihf-gcc。
應用二進位制介面
應用二進位制介面(Application Binary Interface),描述了應用程式和作業系統之間或其他應用程式的低階介面。在編譯器選項中主要有“abi”和“eabi”兩種型別,abi通常用在x86架構上,而eabi表示embed abi,即嵌入式架構,如ARM、MIPS等。
浮點模式
部分ARM處理器帶浮點運算單元,程式碼需要進行浮點運算時若交給fpu處理,可以加快運算速度。編譯器針對浮點運算的不同處理情況提供了以下幾種模式:
- hard: 硬浮點型別(hard-float),採用fpu參與浮點運算。 arm-linux-gnueabihf-gcc、armeb-linux-gnueabihf-gcc都是硬浮點型別,即名字中帶“hf”。
- soft:軟浮點型別(soft-float),即使有fpu浮點運算單元也不用,而是使用軟體模式,arm-linux-gnueabi-gcc、armeb-linux-gnueabi-gcc都是軟浮點型別,即名字中不帶“hf”。
- softfp:允許使用浮點指令,但保持與軟浮點ABI的相容性。
i.MX6ULL帶有fpu,對於soft-float和hard-float模式都支援,不過本開發板中提供Linux檔案系統中的庫都是使用hard模式編譯的,所以編寫應用程式時也需要使用相同型別的編譯器,否則會應用程式執行時會提示找不到庫檔案。( 注意!不同板子的軟硬不相容!! )
編譯器版本號
通常來說高版本的編譯器是向後相容的,但開發特定程式時會使用固定的某個版本編譯器,所以程式可能會依賴該版本的編譯器,根據自己要編譯的程式的要求選擇即可。
編譯器型別對程式的影響
講解編譯器型別時提到,編譯器名字中帶hf和不帶hf的差異為硬浮點和軟浮點模式,此處透過小實驗來進行講解,對比兩種編譯器對同樣程式的影響。
安裝軟浮點編譯器
首先安裝浮點模式為soft-float型別的編譯器,即arm-linux-gnueabi-gcc,它與前面使用的arm-linux-gnueabihf-gcc差異為編譯器名字不帶“hf”:
#在主機上執行如下命令
sudo apt install gcc-arm-linux-gnueabi
#安裝完成後使用如下命令檢視版本
arm-linux-gnueabi-gcc -v
執行軟浮點動態編譯的程式
安裝好arm-linux-gnueabi-gcc軟浮點編譯器後,繼續使用hello.c程式進行實驗。
切換至前面hello.c的目錄,使用不帶“hf”的軟浮點編譯器重新編譯:
#以下命令在主機上執行
#在hello.c程式所在的目錄執行如下命令,注意編譯器名字不帶hf
sudo arm-linux-gnueabi-gcc hello.c –o hello
此處我們使用的是同樣的hello.c程式碼檔案,只是編譯器的型別不同,再次透過readelf工具來檢視具體的程式頭資訊,在主機上執行以下命令:
#以下命令在主機上執行
readelf -h hello
可以看到結果與前面的差異在於此處的是soft-float型別,而前面的是hard-float型別,這正是編譯器型別不同導致的。
編譯好後,嘗試在開發板上執行該程式,在開發板的終端執行以下命令。
#以下命令在開發板上的終端上執行
#切換至共享的NFS目錄,下面的目錄根據自己的配置修改
cd /mnt/example/hello_c
#檢視是否有存在前面編譯生成的檔案
ls
#執行主機編譯的ARM平臺程式,soft-float型別
./hello
很遺憾,使用arm-linux-gnueabi-gcc軟浮點編譯的程式無法正常執行,它提示找不到檔案或目錄,這是因為程式內部呼叫了軟浮點的類庫(如glibc庫檔案libc.so.6),而開發板配套的庫檔案是硬浮點型別的。
開發板的glibc庫型別
關於庫檔案的型別,同樣可以使用readelf工具檢視,在開發板中執行以下命令
#使用readelf檢視開發板的libc.so.6型別
readelf -h libc.so.6
表示開發板的glibc庫檔案libc.so.6為ARM架構的hard-float型別庫,所以不帶hf編譯器生成的hello程式與它不相容,無法正常執行。
執行軟浮點靜態編譯的程式
既然hello程式是因為庫不相容,那如果程式使用靜態編譯,即程式自帶相關庫的內容,是不是就可以正常執行呢?答案是可以的。我們繼續進行如下測試:
在主機執行如下命令,對hello.c進行靜態編譯生成hello_static程式:
#以下命令在主機上執行
#使用不帶hf的編譯器對hello.c進行靜態編譯,生成的程式名為hello_static
sudo arm-linux-gnueabi-gcc hello.c -o hello_static --static
#檢視生成的程式大小
ls -lh
#檢視hello_static檔案頭
readelf -h hello_static
可看到使用靜態編譯得到的hello_static程式比動態編譯的hello大,這是因為它自身包含了庫檔案,使用readelf也可以看到hello_static程式依然是soft-float型別的。
接著嘗試在開發板中執行生成的hello_static靜態程式:
#以下命令在開發板上的終端上執行
#切換至共享的NFS目錄,下面的目錄根據自己的配置修改
cd /mnt/example/hello_c
#檢視是否有存在前面編譯生成的檔案
ls
#執行主機編譯的ARM平臺程式,soft-float型別,靜態可執行檔案
./hello_static
如下圖:
hello_static程式正常執行,這就是編譯器及系統庫檔案對程式執行的影響。
如何安裝ARM-GCC
安裝交叉編譯工具鏈有如下三種方式:
- 直接在Ubuntu下使用APT包管理工具下載安裝,操作簡單。
- 自行下載第三方製作好的工具鏈,如Linaro,好處是選擇豐富,能找到很多不同的版本。
- 使用crosstool-ng根據需要自己製作,過程複雜,不推薦。
使用APT安裝ARM-GCC
在Ubuntu系統下使用APT包管理工具安裝。
使用的編譯器主要有兩種型別:
- arm-linux-gnueabihf-gcc:名稱中的Linux表示目標應用程式是執行在Linux作業系統之上的,例如前面的hello.c程式。
- arm-none-eabi-gcc,名稱中的none表示無作業系統,目標應用程式的執行環境是不帶作業系統的,例如裸機程式碼、uboot、核心程式碼本身。
不過在開發中比較多的開發者對所有程式都直接用arm-linux-gnueabihf-gcc來編譯,包括裸機程式碼和uboot,雖然可能因為程式碼本身沒有呼叫到Linux相關的內容而不會出錯,但這樣做不夠嚴謹,條件允許的話,我們還是嚴格區分開來。
透過APT包管理工具可直接執行以下命令安裝:
#在主機上執行如下命令
sudo apt install gcc-arm-linux-gnueabihf
#安裝完成後使用如下命令檢視版本
arm-linux-gnueabihf-gcc –v
可以看到下圖的內容,它表明交叉編譯工具鏈安裝成功了,輸出資訊表明了它是7.4.0版本的編譯器,其中的 “Target:arm-linux-gnueabihf”也表明了它的目標架構。
安裝完成後輸入“arm-linux-gnueabihf-”,再按兩下TAB鍵,終端會提示可用的相關命令,如下圖包含了ARM-GCC工具鏈Binutils的各種工具。
安裝後包含的Binutils工具集
交叉編譯Hello World程式
安裝好交叉編譯器後,直接使用它對Hello World程式進行交叉編譯即可。
交叉編譯器與本地編譯器使用起來並沒有多大區別。對於原始檔的編譯過程,都是四個階段:預處理,編譯,彙編以及連結,區別只在於編譯工具。
在主機上執行如下命令對Hello World程式進行交叉編譯:
#以下命令在主機上執行
#在hello.c程式所在的目錄執行如下命令
arm-linux-gnueabihf-gcc hello.c –o hello
同樣的C程式碼檔案,使用交叉編譯器編譯後,生成的hello已經變成了ARM平臺的可執行檔案,可以透過readelf工具來檢視具體的程式資訊。
readelf工具在系統安裝GCC編譯工具鏈時一起被安裝了,我們可以直接使用。在主機上執行以下命令:
#以下命令在主機上執行
readelf -a hello
使有readelf檢視交叉編譯器生成的hello程式
可看到hello程式的系統架構為ARM平臺,標誌中表明瞭它是hard-float型別的EABI介面。
編譯好後,即可嘗試在開發板上執行,在開發板的終端執行以下命令,執行結果如下圖所示。
#以下命令在開發板上的終端上執行
#切換至共享的NFS目錄,下面的目錄根據自己的配置修改
cd /mnt/example/hello_c
#檢視是否有存在前面編譯生成的檔案
ls
#執行主機編譯的ARM平臺程式
./hello
下載第三方製作好的工具鏈
下載
Linaro Releases
正點原子的板子應該下載x86_64的版本, 因為開發平臺是64位的linux. 應該下載帶hf的, 表示硬浮點, 因為正點原子的板子的Linux檔案系統中的庫都是使用hard模式編譯的.
官方給的4.9版本太老, 使用8.x的lvgl庫時會報錯! 要修改的有很多. 故推薦5.4
安裝
sudo apt-get install lsb-core lib32stdc++6 #安裝其它的庫
在電腦的Linux系統中切換為root使用者
su - #切換root
cd /
mkdir /DevelopmentTool/zdyz/gcc/ #建立資料夾
# 然後將 下載好的 gcc-linaro-5.4.1-2017.01-x86_64_arm-linux-gnueabihf.tar.xz 上傳到其中
tar -vxf gcc-linaro-5.4.1-2017.01-x86_64_arm-linux-gnueabihf.tar.xz #解壓
cd /etc/profile.d #進入全域性指令碼資料夾
touch IoT_development.sh #建立自定義指令碼檔案
chmod 644 IoT_development.sh
vim IoT_development.sh
# 修改環境變數, 加入下面的內容即可
export PATH=/DevelopmentTool/zdyz/gcc/gcc-linaro-5.4.1-2017.01-x86_64_arm-linux-gnueabihf/bin:$PATH
# 重啟系統或
source ./IoT_development.sh
# 檢視版本號 如果交叉編譯器安裝正確的話就會顯示版本號,如圖
arm-linux-gnueabihf-gcc -v
使用
# 終端直接編譯
arm-linux-gnueabihf-gcc main.c -o demo.out
# Makefile編寫
CC = arm-linux-gnueabihf-gcc
可見編譯成功.
若有多個不同版本的交叉編譯鏈, 使用時使用完整的名稱即可.
參考資料
- 【正點原子】I.MX6U嵌入式Linux驅動開發指南V1.81.pdf---P164
- [4. ARM-GCC和開發板的HelloWorld — 野火]嵌入式Linux基礎與應用開發實戰指南——基於i.MX6ULL開發板 文件 (embedfire.com)
- Linaro Releases