2工具鏈
工具鏈是嵌入式Linux的第一個元素,也是你項目的起點。你將用它來編譯所有將在你的設(shè)備上運行的代碼。你在這個早期階段做出的選擇將對最終結(jié)果產(chǎn)生深遠的影響。你的工具鏈應(yīng)該能夠通過使用處理器的最佳指令集來有效地利用你的硬件。它應(yīng)該支持你所需要的語言,并對便攜式操作系統(tǒng)接口(POSIX)和其他系統(tǒng)接口有一個堅實的實現(xiàn)。
你的工具鏈應(yīng)該在整個項目中保持不變。換句話說,一旦你選擇了你的工具鏈,就一定要堅持下去。在一個項目中以不一致的方式改變編譯器和開發(fā)庫會導(dǎo)致微妙的錯誤。也就是說,當發(fā)現(xiàn)安全缺陷或錯誤時,最好還是要更新你的工具鏈。
獲得工具鏈可以像下載和安裝TAR文件一樣簡單,也可以像從源代碼構(gòu)建整個東西一樣復(fù)雜。在本章中,我采取了后一種方法,在crosstool-NG的工具的幫助下,我可以向你展示創(chuàng)建工具鏈的細節(jié)。稍后,在第6章 "選擇構(gòu)建系統(tǒng) "中,我將轉(zhuǎn)向使用構(gòu)建系統(tǒng)生成的工具鏈,這是更常用的獲得工具鏈的方法。當我們讀到第14章 "從BusyBox runit開始 "時,我們將下載一個預(yù)先構(gòu)建好的Linaro工具鏈來和Buildroot一起使用,這樣可以節(jié)省一些時間。
技術(shù)要求
基于Linux的主機系統(tǒng),安裝有autoconf, automake, bison, bzip2, cmake, flex, g++, gawk, gcc, gettext, git, gperf, help2man, libncurses5-dev, libstdc++6, libtool, libtool-bin, make, patch, python3-dev, rsync, texinfo, unzip, wget, and xz-utils或其對應(yīng)物。
我推薦使用Ubuntu 20.04 LTS或更高版本,因為本章的練習(xí)在寫作時都是在該Linux發(fā)行版上測試的。以下是在Ubuntu 20.04 LTS上安裝所有必要軟件包的命令:
$ sudo apt-get install autoconf automake bison bzip2 cmake \ flex g++ gawk gcc
gettext git gperf help2man libncurses5-dev libstdc++6 libtool \ libtool-bin make
patch python3-dev rsync texinfo unzip wget xz-utils
本章的所有代碼都可以在本書GitHub倉庫的Chapter02文件夾中找到:https://github.com/PacktPublishing/Mastering-Embedded-Linux-Programming-Third-Edition。
工具鏈的介紹
工具鏈是一套將源代碼編譯成可執(zhí)行文件的工具,可以在你的目標設(shè)備上運行,包括編譯器、鏈接器和運行時庫。最初,你需要工具鏈來構(gòu)建嵌入式Linux系統(tǒng)的其他三個要素:引導(dǎo)程序、內(nèi)核和根文件系統(tǒng)。它必須能夠編譯用匯編、C和C++編寫的代碼,因為這些都是基礎(chǔ)開源包中使用的語言。
通常情況下,Linux的工具鏈是基于GNU項目的組件
(http://www.gnu.org),在寫這篇文章時,大多數(shù)情況下仍是如此。然而,在過去的幾年中,Clang編譯器和相關(guān)的低級虛擬機(LLVM)項目(http://llvm.org)已經(jīng)發(fā)展到了現(xiàn)在可以替代GNU工具鏈的程度。LLVM和基于GNU的工具鏈之間的主要區(qū)別是許可;LLVM用BSD許可,而GNU用GPL。
Clang也有一些技術(shù)上的優(yōu)勢,比如更快的編譯和更好的診斷,但GNU GCC的優(yōu)勢在于與現(xiàn)有代碼庫的兼容性以及對各種架構(gòu)和操作系統(tǒng)的支持。雖然花了一些年的時間,Clang現(xiàn)在可以編譯嵌入式Linux所需的所有組件,是GNU的可行的替代方案。要了解更多的情況,請看https://www.kernel.org/doc/html/latest/kbuild/llvm.html。
關(guān)于如何使用Clang進行交叉編譯,在https://clang.llvm.org/docs/CrossCompilation.html,有很好的描述。如果你想把它作為嵌入式Linux構(gòu)建系統(tǒng)的一部分,EmbToolkit(https://embtoolkit.org)完全支持GNU和LLVM/Clang工具鏈,而且很多人正在努力將Clang用于Buildroot和Yocto項目。我將在第6章 "選擇構(gòu)建系統(tǒng) "中介紹嵌入式構(gòu)建系統(tǒng)。同時,本章主要討論GNU工具鏈,因為它仍然是Linux中最流行和最成熟的工具鏈。
標準的GNU工具鏈由三個主要部分組成:
-
Binutils: 一套二進制實用程序,包括匯編器和鏈接器。它可以在http://gnu.org/software/binutils。
-
GNU編譯器集合(GCC): 這些是C和其他語言的編譯器,根據(jù)GCC的版本,包括C++、Objective-C、Objective-C++、Java、Fortran、Ada和Go。它們都使用一個共同的后端,產(chǎn)生匯編代碼,并將其輸入到GNU匯編器中。它可以在http://gcc.gnu.org/。
-
C庫: 基于POSIX規(guī)范的標準化應(yīng)用程序接口(API),它是應(yīng)用程序進入操作系統(tǒng)內(nèi)核的主要接口。
應(yīng)用程序的主要接口。正如我們在本章后面將看到的,有幾個C語言庫需要考慮。
除了這些,你還需要一份Linux內(nèi)核頭文件的副本,其中包含直接訪問內(nèi)核時需要的定義和常量?,F(xiàn)在,你需要它們來編譯C語言庫,但是你以后在編寫程序或編譯與特定Linux設(shè)備交互的庫時也需要它們,例如,通過Linux幀緩沖器驅(qū)動來顯示圖形。這不是簡單的在你的內(nèi)核源代碼的include目錄下復(fù)制頭文件的問題。這些頭文件只是為了在內(nèi)核中使用,并且包含一些定義,如果在原始狀態(tài)下用來編譯普通的Linux應(yīng)用程序,會引起沖突。
相反,你需要生成一套經(jīng)過消毒的內(nèi)核頭文件,我在第5章 "構(gòu)建根文件系統(tǒng) "中已經(jīng)說明了這一點。
內(nèi)核頭文件是否由你將要使用的Linux的確切版本生成,通常并不重要。因為內(nèi)核接口總是向后兼容的,所以只需要頭文件來自與你在目標上使用的內(nèi)核相同或更早的內(nèi)核。
大多數(shù)人認為GNU調(diào)試器(GDB)也是工具鏈的一部分,而且通常在這時就已經(jīng)建立了。我將在第19章 "使用GDB進行調(diào)試 "中討論GDB。
工具鏈的類型
對于我們的目的,有兩種類型的工具鏈:
- 原生
這種工具鏈與它生成的程序在同一類型的系統(tǒng)(有時是同一實際系統(tǒng))上運行。這是臺式機和服務(wù)器的通常情況,它在某些類別的嵌入式設(shè)備上也開始流行。例如,運行Debian for ARM的Raspberry Pi就有自我托管的本地編譯器。
- 交叉
這種工具鏈在與目標系統(tǒng)不同的類型上運行,允許在快速的桌面PC上進行開發(fā),然后加載到嵌入式目標上進行測試。
幾乎所有的嵌入式Linux開發(fā)都是使用交叉開發(fā)工具鏈完成的,部分原因是大多數(shù)嵌入式設(shè)備由于缺乏計算能力、內(nèi)存和存儲空間而不太適合進行程序開發(fā),但也因為它使主機和目標環(huán)境分離。當主機和目標機使用相同的架構(gòu)時,例如x86_64,后一點尤其重要。在這種情況下,在主機上進行本地編譯并簡單地將二進制文件復(fù)制到目標機上是很誘人的。
這在一定程度上是可行的,但很可能主機發(fā)行版比目標版更經(jīng)常地收到更新,或者為目標版編寫代碼的不同工程師對主機開發(fā)庫的版本略有不同。隨著時間的推移,開發(fā)系統(tǒng)和目標系統(tǒng)將出現(xiàn)分歧,你將違反工具鏈應(yīng)該在項目的整個生命周期內(nèi)保持不變的原則。如果你能確保主機和目標構(gòu)建環(huán)境是同步的,你就能使這種方法發(fā)揮作用。然而,更好的方法是保持主機和目標的分離,而交叉工具鏈就是這樣做的方法。
然而,有一個支持本地開發(fā)的反駁理由。交叉開發(fā)會造成交叉編譯你的目標所需的所有庫和工具的負擔。我們將在后面的 "交叉編譯的藝術(shù) "一節(jié)中看到,交叉開發(fā)并不總是簡單的,因為許多開源包并不是被設(shè)計成以這種方式構(gòu)建的。集成構(gòu)建工具,包括Buildroot和Yocto項目,通過封裝規(guī)則來幫助你交叉編譯一系列你在典型的嵌入式系統(tǒng)中需要的軟件包,但是如果你想編譯大量的額外軟件包,那么最好是原生編譯它們。例如,使用交叉編譯器為Raspberry Pi或BeagleBone構(gòu)建一個Debian發(fā)行版將是非常困難的。相反,它們是被本地編譯的。
從頭開始創(chuàng)建一個本地構(gòu)建環(huán)境并不容易。首先,你仍然需要交叉編譯器來在目標上創(chuàng)建本地構(gòu)建環(huán)境,然后用它來構(gòu)建軟件包。然后,為了在合理的時間內(nèi)進行本地構(gòu)建,你將需要一個由配置良好的目標板組成的構(gòu)建農(nóng)場,或者你可以使用Quick EMUlator(QEMU)來模擬目標。
CPU架構(gòu)
工具鏈必須根據(jù)目標CPU的能力來構(gòu)建,這包括以下內(nèi)容:
- CPU架構(gòu): ARM、無互鎖流水線階段的微處理器(MIPS Microprocessor without Interlocked Pipelined Stages)、x86_64,等等。
- Big-或little-endian操作: 有些CPU可以在兩種模式下運行,但每種模式下的機器代碼是不同的。
- 浮點支持: 并非所有版本的嵌入式處理器都實現(xiàn)了硬件浮點單元,在這種情況下,工具鏈必須被配置為調(diào)用軟件浮點庫。
- 應(yīng)用二進制接口(Application Binary Interface): 用于在函數(shù)調(diào)用之間傳遞參數(shù)的調(diào)用慣例。
在許多體系結(jié)構(gòu)中,ABI在整個處理器系列中是不變的。值得注意的例外是ARM。ARM 架構(gòu)在 2000 年代末過渡到擴展應(yīng)用二進制接口 (EABI),導(dǎo)致以Extended Application Binary Interface前的 ABI 被命名為舊應(yīng)用二進制接口 (OABI Old Application Binary Interface )。雖然OABI現(xiàn)在已經(jīng)過時了,但你會繼續(xù)看到對EABI的引用。從那時起,根據(jù)浮點參數(shù)的傳遞方式,EABI已經(jīng)一分為二。
最初的EABI使用通用(整數(shù))寄存器,而較新的Extended Application Binary Interface Hard-Float(EABIHF)使用浮點寄存器。EABIHF的浮點運算速度明顯更快,因為它不需要在整數(shù)和浮點寄存器之間進行復(fù)制,但它與沒有浮點單元的CPU不兼容。因此,你必須在兩個不兼容的ABI之間做出選擇;你不能混合兩者,因此你必須在這個階段做出決定。
GNU在工具鏈中的每個工具的名稱上使用了一個前綴,它標識了可以生成的各種組合。它由一個用破折號隔開的三或四個組件組成,如下面所述:
- CPU: 這是CPU架構(gòu),如ARM、MIPS或x86_64。如果CPU有兩種endian模式,可以通過添加el來區(qū)分小端模式或eb來區(qū)分大端模式。很好的例子是小字節(jié)的MIPS,mipsel,和大字節(jié)的ARM,armeb。
- 供應(yīng)商: 這標識了工具鏈的提供者。例子包括buildroot、poky,或者只是未知。有時它被完全忽略了。
- 內(nèi)核: 對于我們的目的,它總是linux。
- 作業(yè)系統(tǒng): 用戶空間組件的名稱,可能是gnu或musl。ABI也可以附加在這里,所以對于ARM工具鏈,你可以看到gnueabi、gnueabihf、musleabi或musleabihf。
你可以通過使用gcc的-dumpmachine選項找到構(gòu)建工具鏈時使用的元組。例如,你可以在主機上看到以下內(nèi)容:
$ gcc -dumpmachine
x86_64-linux-gnu
這個元組表示CPU為x86_64,內(nèi)核為linux,用戶空間為gnu。
重要提示:
當一個本地編譯器安裝在機器上時,通常會在工具鏈中創(chuàng)建沒有前綴的工具鏈接,這樣你就可以用gcc命令調(diào)用C編譯器。
下面是一個使用交叉編譯器的例子:
$ mipsel-unknown-linux-gnu-gcc -dumpmachine
mipsel-unknown-linux-gnu-表示小-endian MIPS的CPU,未知的供應(yīng)商,linux的內(nèi)核,和gnu的用戶空間。
選擇C庫
Unix操作系統(tǒng)的編程接口是由C語言定義的,現(xiàn)在由POSIX標準定義。C庫是該接口的實現(xiàn);它是Linux程序進入內(nèi)核的門戶,如下圖所示。即使你用其他語言編寫程序,也許是Java或Python,各自的運行時支持庫最終都要調(diào)用C庫,如圖所示:
每當C庫需要內(nèi)核的服務(wù)時,它將使用內(nèi)核的系統(tǒng)調(diào)用接口在用戶空間和內(nèi)核空間之間轉(zhuǎn)換??梢岳@過C庫,直接進行內(nèi)核系統(tǒng)調(diào)用,但是這很麻煩,幾乎沒有必要。
有幾個C庫可以選擇。主要的選擇有以下幾種:
-
glibc: 這是標準的GNU C庫,可以在https://gnu.org/software/libc。它很大,而且直到最近還不是很好配置,但它是POSIX API的最完整的實現(xiàn)。它的許可證是LGPL 2.1。
-
musl libc: https://musl.libc.org。musl libc庫相對較新,但作為GNU libc的小型和符合標準的替代品,它已經(jīng)得到了很多關(guān)注。對于內(nèi)存和存儲空間有限的系統(tǒng)來說,它是一個不錯的選擇。它有一個MIT許可證。
-
uClibc-ng: https://uclibc-ng.org。u實際上是一個希臘的mu字符,表示這是微控制器的C庫。它最初是為了與uClinux(用于沒有內(nèi)存管理單元的CPU的Linux)一起使用而開發(fā)的,但后來被改編為可用于完整的Linux。uClibc-ng庫是原uClibc項目(https://uclibc.org)的,不幸的分支是該項目已經(jīng)年久失修了。兩者都是以LGPL 2.1授權(quán)的。
-
eglibc: http://www.eglibc.org/home?,F(xiàn)在已經(jīng)過時了,eglibc是glibc的分支,做了一些修改,使其更適合于嵌入式使用。在其他方面,eglibc增加了配置選項和對glibc沒有覆蓋的架構(gòu)的支持,特別是PowerPC e500 CPU內(nèi)核。在2.20版本中,eglibc的代碼庫被合并回glibc中。eglibc庫不再被維護。
那么,該選擇哪一個呢?我的建議是,只有當你使用uClinux時才使用uClibc-ng。如果你的存儲空間或內(nèi)存非常有限,那么musl libc是一個不錯的選擇,否則就使用glibc:
你對C庫的選擇可能會限制你對工具鏈的選擇,因為并不是所有預(yù)置的工具鏈都支持所有的C庫。
尋找工具鏈
對于你的交叉開發(fā)工具鏈,你有三個選擇:你可以找到符合你需求的現(xiàn)成的工具鏈;你可以使個由嵌入式構(gòu)建工具生成的工具鏈,這在第6章,選擇構(gòu)建系統(tǒng)中有所涉及;或者你可以按照本章后面的描述,自己創(chuàng)建。
預(yù)制的交叉工具鏈是一個有吸引力的選擇,因為你只需要下載和安裝它,但是你被限制在那個特定的工具鏈的配置上,而且你要依賴于你得到它的人或組織。
最有可能的是,這將是其中之一:
- SoC或電路板供應(yīng)商。大多數(shù)供應(yīng)商提供Linux工具鏈。
- 致力于為某個特定架構(gòu)提供系統(tǒng)級支持的聯(lián)盟。例如,Linaro, (https://www.linaro.org)有針對ARM架構(gòu)的預(yù)建工具鏈。
- 第三方Linux工具供應(yīng)商,如Mentor Graphics、TimeSys或MontaVista。
- 桌面Linux發(fā)行版的交叉工具包。例如,基于 Debian 的發(fā)行版有用于交叉編譯 ARM、MIPS 和 PowerPC 目標的軟件包。
- 集成的嵌入式構(gòu)建工具產(chǎn)生的二進制SDK。Yocto項目在http://downloads.yoctoproject.org/releases/yocto/yocto-[版本]/toolchain有一些例子。
- 已經(jīng)找不到的論壇的鏈接。
在所有這些情況下,你必須決定所提供的預(yù)建工具鏈是否符合你的要求。它是否使用你喜歡的C庫?供應(yīng)商是否會給你提供安全修復(fù)和錯誤的更新,請記住我在第一章 "起步 "中對支持和更新的評論。如果你的答案是否定的,那么你應(yīng)該考慮創(chuàng)建你自己的。
不幸的是,建立工具鏈不是一件容易的事。如果你真的想自己來做這件事,可以看看Cross Linux From Scratch(https://trac.clfs.org)。在那里,你可以找到關(guān)于如何創(chuàng)建每個組件的分步說明。
一個更簡單的選擇是使用crosstool-NG,它將這個過程封裝成一組腳本,并有菜單驅(qū)動的前端。不過,你仍然需要相當程度的知識,只是為了做出正確的選擇。
使用Buildroot或Yocto項目這樣的構(gòu)建系統(tǒng)更簡單,因為它們會在構(gòu)建過程中生成一個工具鏈。這是我的首選方案,正如我在第6章 "選擇構(gòu)建系統(tǒng) "中所示。
隨著crosstool-NG的興起,建立你自己的工具鏈當然是一個有效和可行的選擇。接下來讓我們看看如何做到這一點。
使用crosstool-NG構(gòu)建工具鏈
參見:https://crosstool-ng.github.io/docs/install/
幾年前,Dan Kegel寫了一套用于生成跨開發(fā)工具鏈的腳本和makefiles,并稱之為crosstool(http://kegel.com/crosstool/)。2007年,Yann E. Morin在此基礎(chǔ)上創(chuàng)建了下一代的crosstool,即crosstool-NG(https://crosstool-ng.github.io)。今天,它是迄今為止從源頭上創(chuàng)建獨立的交叉工具鏈的最方便的方法。
在本節(jié)中,我們將使用crosstool-NG來為BeagleBone Black和QEMU。
安裝crosstool-NG
在你從源代碼構(gòu)建crosstool-NG之前,你首先需要在你的主機上安裝本地工具鏈和一些構(gòu)建工具。關(guān)于crosstool-NG的完整構(gòu)建和運行時依賴項,請參見本章開頭的技術(shù)要求部分。
為BeagleBone Black構(gòu)建工具鏈
Crosstool-NG可以建立許多不同組合的工具鏈。為了使初始配置更容易,它附帶了一套涵蓋許多常見使用情況的樣本。使用bin/ct-ng list-samples來生成這個列表。
BeagleBone Black有一個TI AM335x SoC,它包含一個ARM Cortex A8內(nèi)核和VFPv3浮點單元。由于BeagleBone Black有大量的RAM和存儲空間,我們可以使用glibc作為C庫。最接近的樣本是arm-cortex_a8-linux-gnueabi。
你可以通過在名稱前加上show-來查看默認配置:
$ bin/ct-ng show-arm-cortex_a8-linux-gnueabi
[G...] arm-cortex_a8-linux-gnueabi
Languages : C,C++
OS : linux-5.16.9
Binutils : binutils-2.38
Compiler : gcc-11.2.0
C library : glibc-2.35
Debug tools : duma-2_5_15 gdb-11.2 ltrace-0.7.3 strace-5.16
Companion libs : expat-2.4.1 gettext-0.21 gmp-6.2.1 isl-0.24 libelf-0.8.13 libiconv-1.16 mpc-1.2.1 mpfr-4.1.0 ncurses-6.2 zlib-1.2.12
Companion tools :
這與我們的要求很接近,除了它使用eabi二進制接口,以整數(shù)寄存器傳遞浮點參數(shù)。我們更傾向于使用硬件浮點寄存器來實現(xiàn)這一目的,因為這樣可以加快有浮點和雙倍參數(shù)類型的函數(shù)調(diào)用速度。你以后可以改變配置,所以現(xiàn)在你應(yīng)該選擇這個目標配置:
$ bin/ct-ng arm-cortex_a8-linux-gnueabi
CONF arm-cortex_a8-linux-gnueabi
#
# configuration written to .config
#
***********************************************************
Initially reported by: Yann E. MORIN
URL: http://ymorin.is-a-geek.org/
***********************************************************
Now configured for "arm-cortex_a8-linux-gnueabi"
使用配置菜單命令menuconfig審查配置并進行修改:
$ bin/ct-ng menuconfig
該菜單系統(tǒng)是基于Linux內(nèi)核的menuconfig,因此,任何配置過內(nèi)核的人都會熟悉用戶界面的導(dǎo)航。如果沒有,請參閱第4章,配置和構(gòu)建內(nèi)核,了解menuconfig的描述。
建議你做三個配置上的改變:
- 在Paths and misc options中,禁用Render the toolchain read-only(CT_PREFIX_DIR_RO).
(ct_prefix_dir_ro)。 - Target options | Floating point,選擇硬hardware (FPU) (CT_ARCH_FLOAT_HW)。
- Target options | neon,選擇Use specific FPU。
如果你想在工具鏈安裝后將庫添加到工具鏈中,第一個選項是必要的,我將在后面的用庫鏈接部分介紹。第二個選擇eabihf二進制接口,原因在前面已經(jīng)討論過了。第三項是成功構(gòu)建Linux內(nèi)核所需要的。括號里的名字是存儲在配置文件中的配置標簽。當你做了修改后,退出menuconfig菜單,并像以前一樣保存配置。
現(xiàn)在你可以使用crosstool-NG來獲取、配置,并按照你的規(guī)范來構(gòu)建組件,輸入以下命令:
$ bin/ct-ng build
構(gòu)建將花費大約半小時,之后你會發(fā)現(xiàn)你的工具鏈出現(xiàn)在~/x-tools/arm-cortex_a8-linux-gnueabihf。 如果出現(xiàn)下載zlib失敗,參考https://github.com/crosstool-ng/crosstool-ng/issues/1337,手動下載拷貝即可。
為QEMU構(gòu)建工具鏈
在QEMU目標上,你將模擬具有ARM926EJ-S處理器內(nèi)核的ARM-versatile PB評估板,它實現(xiàn)了ARMv5TE指令集。你需要生成符合規(guī)范的crosstool-NG工具鏈。該程序與BeagleBone Black的程序非常相似。
你首先要運行bin/ct-ng list-samples來找到一個好的基礎(chǔ)配置來工作。沒有精確的配置,所以使用通用的目標,arm-unknown-linux-gnueabi。你選擇它,如圖所示,先運行distclean以確保沒有以前構(gòu)建時留下的工件:
$ bin/ct-ng distclean
$ bin/ct-ng arm-unknown-linux-gnueabi
與BeagleBone Black一樣,你可以查看配置并進行修改
使用配置菜單命令bin/ct-ng menuconfig。只有一個改動是必要的:
在Paths and misc options中,禁用Render the toolchain read-only
(ct_prefix_dir_ro)。
現(xiàn)在,用這里的命令構(gòu)建工具鏈:
$ bin/ct-ng build
和以前一樣,構(gòu)建將需要大約半小時。工具鏈將被安裝在~/x-tools/arm-unknown-linux-gnueabi。
工具鏈的剖析
ARM Cortex A8工具鏈在~/x-tools/arm-cortex_a8-linux-gnueabihf/bin目錄下。在那里,你會發(fā)現(xiàn)交叉編譯器,arm-cortex_a8-linux-gnueabihf-gcc。為了使用它,你需要用以下命令將該目錄添加到你的路徑中:
$ PATH=~/x-tools/arm-cortex_a8-linux-gnueabihf/bin:$PATH
簡單的helloworld程序:
#include <stdio.h>
#include <stdlib.h>
int main (int argc, char *argv[])
{
printf ("Hello, world!\n");
return 0;
}
你可以這樣編譯它:
$ arm-cortex_a8-linux-gnueabihf-gcc helloworld.c -o helloworld
你可以通過使用file命令打印文件的類型來確認它已經(jīng)被交叉編譯了:
$ file helloworld
helloworld: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 5.16.9, with debug_info, not stripped
了解你的交叉編譯器
可以通過查詢gcc發(fā)現(xiàn)很多東西。例如,要找到版本,你可以使用--版本:
$ arm-cortex_a8-linux-gnueabihf-gcc --version
arm-cortex_a8-linux-gnueabihf-gcc (crosstool-NG 1.25.0) 11.2.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
要知道它是如何配置的,請使用-v:
$ arm-cortex_a8-linux-gnueabihf-gcc -v
Using built-in specs.
COLLECT_GCC=arm-cortex_a8-linux-gnueabihf-gcc
COLLECT_LTO_WRAPPER=/home/andrew/x-tools/arm-cortex_a8-linux-gnueabihf/libexec/gcc/arm-cortex_a8-linux-gnueabihf/11.2.0/lto-wrapper
Target: arm-cortex_a8-linux-gnueabihf
Configured with: /opt/crosstool/.build/arm-cortex_a8-linux-gnueabihf/src/gcc/configure --build=x86_64-build_pc-linux-gnu --host=x86_64-build_pc-linux-gnu --target=arm-cortex_a8-linux-gnueabihf --prefix=/home/andrew/x-tools/arm-cortex_a8-linux-gnueabihf --exec_prefix=/home/andrew/x-tools/arm-cortex_a8-linux-gnueabihf --with-sysroot=/home/andrew/x-tools/arm-cortex_a8-linux-gnueabihf/arm-cortex_a8-linux-gnueabihf/sysroot --enable-languages=c,c++ --with-cpu=cortex-a8 --with-fpu=neon --with-float=hard --with-pkgversion='crosstool-NG 1.25.0' --enable-__cxa_atexit --disable-libmudflap --disable-libgomp --disable-libssp --disable-libquadmath --disable-libquadmath-support --disable-libsanitizer --disable-libmpx --disable-libstdcxx-verbose --with-gmp=/opt/crosstool/.build/arm-cortex_a8-linux-gnueabihf/buildtools --with-mpfr=/opt/crosstool/.build/arm-cortex_a8-linux-gnueabihf/buildtools --with-mpc=/opt/crosstool/.build/arm-cortex_a8-linux-gnueabihf/buildtools --with-isl=/opt/crosstool/.build/arm-cortex_a8-linux-gnueabihf/buildtools --enable-lto --enable-threads=posix --enable-target-optspace --enable-plugin --enable-gold --disable-nls --disable-multilib --with-local-prefix=/home/andrew/x-tools/arm-cortex_a8-linux-gnueabihf/arm-cortex_a8-linux-gnueabihf/sysroot --enable-long-long
Thread model: posix
Supported LTO compression algorithms: zlib
gcc version 11.2.0 (crosstool-NG 1.25.0)
值得注意的是以下內(nèi)容:
- --with-sysroot=/home/frank/x-tools/arm-cortex_a8-linux-gnueabihf/arm-cortex_a8-linux-gnueabihf/sysroot: 這是默認的sysroot目錄;請看下面的解釋。
- --enable-languages=c,c++: 使用這個,我們同時啟用了C和C++ 。
- --with-cpu=cortex-a8: 代碼是為ARM Cortex A8內(nèi)核生成的。
- --with-float=hard:為浮點單元生成操作代碼,并使用VFP寄存器作為參數(shù)。
- --enable-threads=posix:這將啟用POSIX線程。
這些是編譯器的默認設(shè)置。你可以在gcc命令行中覆蓋其中的大部分。例如,如果你想為不同的CPU編譯,你可以通過在命令行中加入-mcpu來覆蓋配置的設(shè)置--with-cpu,如下所示:
$ arm-cortex_a8-linux-gnueabihf-gcc -mcpu=cortex-a5 helloworld.c -o helloworld
你可以使用 --target-help 打印出可用的特定架構(gòu)選項的范圍,如下所示:
$ arm-cortex_a8-linux-gnueabihf-gcc --target-help
果你計劃為每個目標創(chuàng)建工具鏈,那么在一開始就設(shè)置好是有意義的,因為這將減少以后出錯的風險。另一方面,如果你想構(gòu)建通用的工具鏈,并且準備在為特定目標構(gòu)建時提供正確的設(shè)置,那么你應(yīng)該使基礎(chǔ)工具鏈通用化,這就是Yocto項目處理事情的方式。前面的例子都是遵循Buildroot的理念。
sysroot、庫和頭文件
工具鏈的系sysroot是包含庫、頭文件和其他配置文件的子目錄的目錄。它可以在配置工具鏈時通過 --with-sysroot= 設(shè)置,也可以在命令行中通過 --sysroot= 設(shè)置。 你可以通過使用 -print-sysroot 看到默認的系統(tǒng)根目錄的位置:
$ arm-cortex_a8-linux-gnueabihf-gcc -print-sysroot
/home/andrew/x-tools/arm-cortex_a8-linux-gnueabihf/arm-cortex_a8-linux-gnueabihf/sysroot
你會在sysroot中找到以下子目錄:
- lib: 包含C庫和動態(tài)鏈接器/加載器的共享對象,ld-linux
- usr/lib: C庫的靜態(tài)庫存檔文件,以及隨后可能安裝的任何其他庫。
- usr/include: 包含所有庫的頭文件
- usr/bin: 包含在目標機上運行的實用程序,如ldd命令
- usr/share: 用來進行本地化和國際化
- sbin: 提供ldconfig工具,用于優(yōu)化庫的加載路徑。
很明顯,這些工具中的一些在開發(fā)主機上需要用來編譯程序,而另一些,例如共享庫和ld-linux,在運行時需要在目標上使用。
工具鏈中的其他工具
下面是調(diào)用GNU工具鏈中其他各種組件的命令列表,并附有簡要說明:
- addr2line: 通過讀取可執(zhí)行文件中的調(diào)試符號表,將程序地址轉(zhuǎn)換為文件名和數(shù)字。在對系統(tǒng)崩潰報告中打印出來的地址進行解碼時,它非常有用。
- ar: 歸檔工具,用于創(chuàng)建靜態(tài)庫。
- as: 這是GNU的匯編程序。
- c++filt: 這是用來拆解C++和Java的符號。
- cpp: 這是C語言的預(yù)處理器,用于擴展#define、#include和其他類似的指令。你很少需要單獨使用它。
- elfedit: 這是用來更新ELF文件的ELF頭。
- g++: 這是GNU的C++前端,它假定源文件中含有C++代碼。
- gcc: 這是 GNU C 前端,它假定源文件包含 C 代碼。
- gcov: 代碼覆蓋工具。
- gdb: 這是 GNU 調(diào)試器。
- gprof: 程序剖析工具。
- ld: 這是GNU的連接器。
- nm: 它列出對象文件中的符號。
- objcopy: 用來復(fù)制和翻譯對象文件。
- objdump: 用來顯示對象文件的信息。
- ranlib: 它在靜態(tài)庫中創(chuàng)建或修改索引,使鏈接階段更快。
- readelf: 它顯示ELF對象格式的文件信息。
- size: 這列出了部分大小和總大小。
- strings: 這顯示文件中的可打印字符的字符串。
- strip: 用于剝離調(diào)試符號表的對象文件,從而使其更小。通常情況下,你會剝離所有被放到目標上的可執(zhí)行代碼。
C庫
C庫不是單一的庫文件。它由四個主要部分組成,共同實現(xiàn)POSIX API:
- libc:主C庫,包含眾所周知的POSIX函數(shù),如printf、open、close、read、write,等等。
- libm: 包含數(shù)學(xué)函數(shù),如cos、exp和log
- libpthread: 包含了所有的POSIX線程函數(shù),名稱以pthread開頭的POSIX線程函數(shù)。
- librt: 擁有POSIX的實時擴展,包括共享內(nèi)存和異步I/O
第一個庫,libc,總是被鏈接進去的,但其他庫必須用-l選項明確地鏈接。-l的參數(shù)是去掉lib后的庫名。例如,通過調(diào)用sin()來計算正弦函數(shù)的程序可以用-lm鏈接到libm:
$ arm-cortex_a8-linux-gnueabihf-gcc myprog.c -o myprog -lm
你可以通過使用readelf命令來驗證哪些庫已經(jīng)在這個或其他程序中被鏈接:
$ arm-cortex_a8-linux-gnueabihf-readelf -a code/helloworld | grep "Shared library"
0x00000001 (NEEDED) Shared library: [libc.so.6]
共享庫需要運行時鏈接器:
$ arm-cortex_a8-linux-gnueabihf-readelf -a code/helloworld | grep "program interpreter"
[Requesting program interpreter: /lib/ld-linux-armhf.so.3]
鏈接庫
你為Linux編寫的任何應(yīng)用程序,無論是C語言還是C++語言,都會與lic 庫鏈接。你甚至不需要告訴gcc或g++去做,因為它總是鏈接libc。其他你可能想要鏈接的庫必須通過-l選項明確指定。
庫的代碼可以用兩種不同的方式連接:靜態(tài)的,意味著你的應(yīng)用程序所調(diào)用的所有庫函數(shù)和它們的依賴關(guān)系都是從庫的歸檔文件中提取并綁定到你的可執(zhí)行文件中;動態(tài)的,意味著對庫文件和這些文件中的函數(shù)的引用是在代碼中生成的,但實際的連接是在運行時動態(tài)進行的。
靜態(tài)庫
如果你正在構(gòu)建小程序,靜態(tài)鏈接會更簡單,避免復(fù)制運行時庫文件和鏈接程序。它也會更小,因為只鏈接你的應(yīng)用程序使用的代碼,而不是提供整個C庫。如果你需要在存放運行庫的文件系統(tǒng)可用之前運行程序,靜態(tài)鏈接也很有用。
你可以通過在命令行中添加-static來靜態(tài)鏈接所有庫:
$ arm-cortex_a8-linux-gnueabihf-gcc -static helloworld.c -o helloworld-static
$ ls -lh he*
-rwxrwxr-x 1 andrew andrew 12K 6月 21 15:39 helloworld
-rwxrwxr-x 1 andrew andrew 123 6月 26 15:58 helloworld.c
-rwxrwxr-x 1 andrew andrew 2.9M 6月 26 15:58 helloworld-static
二進制文件的大小急劇增加:
靜態(tài)鏈接從庫存檔中提取代碼,通常命名為lib[name].a。在前面的例子中,它是libc.a,它在[sysroot]/usr/lib中:
$ export SYSROOT=$(arm-cortex_a8-linux-gnueabihf-gcc -print-sysroot)
$ cd $SYSROOT
$ ls -l usr/lib/libc.a
-rw-r--r-- 1 frank frank 31871066 Oct 23 15:16 usr/lib/libc.a
創(chuàng)建靜態(tài)庫就像使用ar命令創(chuàng)建對象文件的歸檔一樣簡單。如果我有兩個名為test1.c和test2.c的源文件,我想創(chuàng)建一個名為libtest.a的靜態(tài)庫,那么我將做如下操作:
$ arm-cortex_a8-linux-gnueabihf-gcc -c test1.c
$ arm-cortex_a8-linux-gnueabihf-gcc -c test2.c
$ arm-cortex_a8-linux-gnueabihf-ar rc libtest.a test1.o test2.o
$ ls -l
total 24
-rw-rw-r-- 1 frank frank 2392 Oct 9 09:28 libtest.a
-rw-rw-r-- 1 frank frank 116 Oct 9 09:26 test1.c
-rw-rw-r-- 1 frank frank 1080 Oct 9 09:27 test1.o
-rw-rw-r-- 1 frank frank 121 Oct 9 09:26 test2.c
-rw-rw-r-- 1 frank frank 1088 Oct 9 09:27 test2.o
$ arm-cortex_a8-linux-gnueabihf-gcc helloworld.c -ltest -L../libs -I../libs -o helloworld
靜共享庫
部署庫的更常見的方式是在運行時鏈接的共享對象,這可以更有效地利用存儲和系統(tǒng)內(nèi)存,因為只需要加載一份代碼。它還使更新庫文件變得容易,而不必重新鏈接所有使用它們的程序。
共享庫的目標代碼必須是獨立于位置的,這樣運行時鏈接器就可以自由地在內(nèi)存中的下一個空閑地址處找到它。要做到這一點,在gcc中加入-fPIC參數(shù),然后用-shared選項來鏈接它:
$ arm-cortex_a8-linux-gnueabihf-gcc -fPIC -c test1.c
$ arm-cortex_a8-linux-gnueabihf-gcc -fPIC -c test2.c
$ arm-cortex_a8-linux-gnueabihf-gcc -shared -o libtest.so test1.o test2.o
這樣就創(chuàng)建了共享庫,libtest.so。要用這個庫鏈接一個應(yīng)用程序,你可以添加-ltest,就像上一節(jié)中提到的靜態(tài)情況一樣,但這次代碼不包括在可執(zhí)行文件中。相反,有一個庫的引用,運行時鏈接器必須解決這個問題:
$ arm-cortex_a8-linux-gnueabihf-gcc helloworld.c -ltest -L../libs -I../libs -o helloworld
$ MELP/list-libs helloworld
[Requesting program interpreter: /lib/ld-linux-armhf.so.3]
0x00000001 (NEEDED) Shared library: [libtest.so.6]
0x00000001 (NEEDED) Shared library: [libc.so.6]
這個程序的運行時鏈接器是/lib/ld-linux-armhf.so.3,它必須存在于目標的文件系統(tǒng)中。鏈接器將在默認的搜索路徑中尋找libtest.so: /lib和/usr/lib。如果你想讓它也尋找其他目錄中的庫,你可以在LD_LIBRARY_PATH殼變量中放置用冒號分隔的路徑列表:
$ export LD_LIBRARY_PATH=/opt/lib:/opt/usr/lib
共享庫的版本號
共享庫的好處之一是,它們可以獨立于使用它們的程序而被更新。
庫的更新有兩種類型:
- 向后兼容的方式修復(fù)錯誤或添加新功能的更新。
- 破壞與現(xiàn)有程序的兼容性的更新
GNU/Linux有版本管理方案來處理這兩種情況。
每個庫都有發(fā)布版本和接口號。發(fā)行版本是簡單的字符串,附加在庫的名稱上;例如,JPEG圖像庫libjpeg目前的發(fā)行版本是8.2.2,所以庫的名稱是libjpeg.so.8.2.2。名為libjpeg.so的符號鏈接到libjpeg.so.8.2.2,這樣,當你用-ljpeg編譯程序時,就會與當前的版本鏈接。如果你安裝了8.2.3版本,鏈接就會更新,你就會用這個版本的鏈接。
現(xiàn)在假設(shè)9.0.0版本出現(xiàn)了,它打破了向后的兼容性?,F(xiàn)在libjpeg.so的鏈接指向libjpeg.so.9.0.0,所以任何新的程序都會與新的版本鏈接,當libjpeg的接口發(fā)生變化時,可能會出現(xiàn)編譯錯誤,開發(fā)人員可以修復(fù)。
目標機上任何沒有重新編譯的程序都會以某種方式失敗,因為它們?nèi)栽谑褂门f的接口。這時,soname的對象就有幫助了。soname編碼了庫建立時的接口號,運行時鏈接器在加載庫時使用它。它的格式是:<庫名>.so.<接口號>。對于libjpeg.so.8.2.2,子名是libjpeg.so.8,因為libjpeg共享庫建立時的接口號是8:
$ readelf -a /usr/lib/x86_64-linux-gnu/libjpeg.so.8.2.2 | grep SONAME
0x000000000000000e (SONAME) Library soname: [libjpeg.so.8]
任何用它編譯的程序都會在運行時請求libjpeg.so.8,這將是目標上對libjpeg.so.8.2.2的符號鏈接。當libjpeg的9.0.0版本被安裝時,它的子名將是libjpeg.so.9,因此有可能在同一個系統(tǒng)上安裝兩個不兼容的同一庫的版本。用libjpeg.so.8..鏈接的程序?qū)⒓虞dlibjpeg.so.8,而用libjpeg.so.9..鏈接的程序?qū)⒓虞dlibjpeg.so.9。
這就是為什么,當你看/usr/lib/x86_64-linux-gnu/libjpeg*的目錄列表時,你會發(fā)現(xiàn)這四個文件:
- libjpeg.a: 這是用于靜態(tài)連接的庫存檔。
- libjpeg.so -> libjpeg.so.8.2.2: 這是符號鏈接,用于動態(tài)鏈接。
- libjpeg.so.8 -> libjpeg.so.8.2.2: 這是符號鏈接,在運行時加載庫時使用。
- libjpeg.so.8.2.2: 這是實際的共享庫,在編譯時和運行時都使用。
前兩個庫只需要在主機上構(gòu)建,后兩個庫在運行時需要在目標機上使用。
雖然你可以直接從命令行中調(diào)用各種GNU交叉編譯工具,但這種技術(shù)并不能超越helloworld這。為了真正有效地進行交叉編譯,我們需要把交叉工具鏈和構(gòu)建系統(tǒng)結(jié)合起來。
參考資料
- 軟件測試精品書籍文檔下載持續(xù)更新 https://github.com/china-testing/python-testing-examples 請點贊,謝謝!
- 本文涉及的python測試開發(fā)庫 謝謝點贊! https://github.com/china-testing/python_cn_resouce
- python精品書籍下載 https://github.com/china-testing/python_cn_resouce/blob/main/python_good_books.md
交叉編譯的藝術(shù)
擁有有效的交叉工具鏈是起點,而不是終點。在某些時候,你會想開始交叉編譯你在目標上需要的各種工具、應(yīng)用程序和庫。它們中的許多都是開源包,每一個都有自己的編譯方法和自己的特殊性。
有一些常見的構(gòu)建系統(tǒng),包括以下幾種:
- 純粹的makefiles,其中的工具鏈通常由make變量CROSS_COMPILE控制
- Autotools: GNU構(gòu)建系統(tǒng)
- CMake(https://cmake.org)
即使是構(gòu)建基本的嵌入式Linux系統(tǒng),也需要Autotools和makefiles。CMake是跨平臺的,多年來被越來越多的人采用,特別是在C++社區(qū)。在本節(jié)中,我們將介紹這三種構(gòu)建工具。
簡單的makefile
一些重要的軟件包的交叉編譯非常簡單,包括Linux內(nèi)核、U-Boot引導(dǎo)程序和BusyBox。對于每一軟件包,你只需要把工具鏈的前綴放在make變量CROSS_COMPILE中,例如,arm-cortex_a8-linux-gnueabi-。注意后面的破折號-。
因此,要編譯BusyBox,你可以這樣輸入:
$ make CROSS_COMPILE=arm-cortex_a8-linux-gnueabihf-
或者,你可以把它設(shè)置為shell變量:
$ export CROSS_COMPILE=arm-cortex_a8-linux-gnueabihf-
$ make
在U-Boot和Linux的情況下,你還必須將make變量ARCH設(shè)置為它們所支持的機器架構(gòu)之一。
Autotools和CMake都可以生成makefile。Autotools只生成makefiles,而CMake則支持其他構(gòu)建項目的方式,這取決于我們所針對的
我們的目標平臺(在我們的例子中嚴格來說是Linux)。
Autotools
Autotools這個名字是指一組工具,在許多開源項目中被用作構(gòu)建系統(tǒng)。這些組件,連同相應(yīng)的項目頁面,如下所示:
- GNU Autoconf (https://www.gnu.org/software/autoconf/autoconf.html)
- GNU Automake (https://www.gnu.org/savannah-checkouts/gnu/automake/)
- GNU Libtool (https://www.gnu.org/software/libtool/libtool.html)
- Gnulib (https://www.gnu.org/software/gnulib/)
Autotools的作用是抹平軟件包可能被編譯的不同類型的系統(tǒng)之間的差異,考慮到不同版本的編譯器、不同版本的庫、不同位置的頭文件以及與其他軟件包的依賴關(guān)系。
使用Autotools的軟件包有一個名為configure的腳本,它檢查依賴關(guān)系,并根據(jù)發(fā)現(xiàn)的情況生成makefiles。configure腳本還可以讓你有機會啟用或禁用某些功能。你可以通過運行./configure --help找到所提供的選項。
要配置、構(gòu)建和安裝本地操作系統(tǒng)的軟件包,你通常要運行以下三個命令:
$ ./configure
$ make
$ sudo make install
Autotools也能夠處理交叉開發(fā)。你可以通過設(shè)置這些shell變量來影響配置的腳本的行為:
- CC: C編譯器的命令。
- CFLAGS: 額外的C編譯器標志。
- CXX: C++編譯器命令。
- CXXFLAGS: 額外的C++編譯器標志。
- LDFLAGS: 額外的鏈接器標志;例如,如果你在非標準的目錄
- 下有庫,你將把它添加到庫搜索路徑上 加入 -L
- 。
- LIBS: 包含一個要傳遞給鏈接器的額外庫的列表;例如、 -lm表示數(shù)學(xué)庫。
- CPPFLAGS: 包含C/C++預(yù)處理器的標志;例如,你可以添加-I
來搜索非標準目錄中的頭文件 。 - CPP:要使用的C預(yù)處理器。有時只設(shè)置CC變量就足夠了,如下所示:
$ CC=arm-cortex_a8-linux-gnueabihf-gcc ./configure
在其他時候,這將導(dǎo)致類似這樣的錯誤:
[…]
checking for suffix of executables...
checking whether we are cross compiling... configure: error: in '/home/frank/sqlite-autoconf-3330000':
configure: error: cannot run C compiled programs.
If you meant to cross compile, use '--host'.
See 'config.log' for more details
失敗的原因是,configure經(jīng)常試圖通過編譯代碼片段并運行它們來發(fā)現(xiàn)工具鏈的能力,如果程序已經(jīng)被交叉編譯,這就無法工作。
注意:當你進行交叉編譯時,把 --host=
Autotools理解在編譯軟件包時可能涉及的三種不同類型的機器:
- Build: 構(gòu)建軟件包的計算機,默認為當前機器。
- Host: 程序?qū)⒁\行的計算機。對于本地編譯,這部分留空,默認為與Build相同的計算機。當你進行交叉編譯時,把它設(shè)置為你的工具鏈的元組。
- Target: 程序?qū)⑸纱a的計算機。你會在構(gòu)建交叉編譯器時設(shè)置這個。
因此,為了交叉編譯,你只需要覆蓋主機,如下所示:
$ CC=arm-cortex_a8-linux-gnueabihf-gcc ./configure --host=arm-cortex_a8-linux-gnueabihf
最后要注意的是,默認的安裝目錄是
你通常會把它安裝在
配置典型的Autotools軟件包的完整命令如下:
$ CC=arm-cortex_a8-linux-gnueabihf-gcc ./configure --host=arm-cortex_a8-linux-gnueabihf
例子--SQLite
$ wget http://www.sqlite.org/2020/sqlite-autoconf-3330000.tar.gz
$ tar xf sqlite-autoconf-3330000.tar.gz
$ cd sqlite-autoconf-3330000
$ CC=arm-cortex_a8-linux-gnueabihf-gcc ./configure --host=arm-cortex_a8-linux-gnueabihf --prefix=/usr # 日志config.log
$ make # 或 $ make DESTDIR=$(arm-cortex_a8-linux-gnueabihf-gcc -print-sysroot) install
你可能會發(fā)現(xiàn),最后的命令會因為文件權(quán)限錯誤而失敗。crosstool-NG工具鏈默認是只讀的,這就是為什么在構(gòu)建它時將CT_PREFIX_DIR_RO設(shè)置為y是很有用的。另一個常見的問題是,工具鏈被安裝在系統(tǒng)目錄下,例如/opt或/usr/local。在這種情況下,你在運行安裝時將需要root權(quán)限。
安裝后,你應(yīng)該發(fā)現(xiàn)各種文件已經(jīng)被添加到你的工具鏈中:
/usr/bin: sqlite3: 這是一個SQLite的命令行接口,你可以在目標機上安裝和運行。 /usr/lib: libsqlite3.so.0.8.6, libsqlite3.so.0, libsqlite3.s, libsqlite3.la, libsqlite3.a: 這些是共享和靜態(tài)庫。 /usr/lib/pkgconfig: sqlite3.pc: 這是軟件包的配置文件,如下一節(jié)所述。 /usr/lib/include: sqlite3.h, sqlite3ext.h: 這些是頭文件。 /usr/share/man/man1: sqlite3.1: 這是手冊頁。
現(xiàn)在你可以編譯使用sqlite3的程序,在 鏈接階段添加-lsqlite3:
$ arm-cortex_a8-linux-gnueabihf-gcc -lsqlite3 sqite-test.c -o sqite-test
$ arm-cortex_a8-linux-gnueabihf-gcc -lsqlite3 sqlite-test.c -o sqlite-test
這里,sqlite-test.c是調(diào)用SQLite函數(shù)的假想程序。由于sqlite3已經(jīng)被安裝到系統(tǒng)根中,編譯器會順利找到頭文件和庫文件。如果它們被安裝在其他地方,你將不得不添加-L
當然,也會有運行時的依賴性,你將不得不在目標目錄中安裝適當?shù)奈募惭b到目標目錄中,如第5章 "建立根文件系統(tǒng) "中所述。
為了交叉編譯一個庫或包,它的依賴項首先需要被交叉編譯。Autotools依靠pkg-config工具來收集關(guān)于由Autotools交叉編譯的軟件包的重要信息。
包的配置
包配置工具pkg-config(https://www.freedesktop.org/wiki/Software/pkg-config/)通過在[sysroot]/usr/lib/pkgconfig中保存Autotools包的數(shù)據(jù)庫,幫助跟蹤哪些包被安裝,以及每個包需要哪些編譯標志。例如,SQLite3的數(shù)據(jù)庫被命名為sqlite3.pc,包含了其他需要使用它的包所需要的基本信息:
$ cat $(arm-cortex_a8-linux-gnueabihf-gcc -print-sysroot)/usr/lib/pkgconfig/sqlite3.pc
# Package Information for pkg-config
prefix=/usr
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include
Name: SQLite
Description: SQL database engine
Version: 3.33.0
Libs: -L${libdir} -lsqlite3
Libs.private: -lm -ldl -lpthread
Cflags: -I${includedir}
你可以使用 pkg-config 來提取信息,并將其直接反饋給 gcc。就像libsqlite3這樣的庫而言,你想知道庫的名字(--libs)和任何特殊的C標志(--cflags):
$ pkg-config sqlite3 --libs --cflags
Package sqlite3 was not found in the pkg-config search path.
Perhaps you should add the directory containing 'sqlite3.pc'
to the PKG_CONFIG_PATH environment variable
No package 'sqlite3' found
失敗的原因是它在主機的系統(tǒng)根中尋找,而主機上還沒有安裝libsqlite3的開發(fā)包。你需要通過設(shè)置PKG_CONFIG_LIBDIR殼變量將其指向目標工具鏈的系統(tǒng)根:
$ export PKG_CONFIG_LIBDIR=$(arm-cortex_a8-linux-gnueabihf-gcc \
-print-sysroot)/usr/lib/pkgconfig
$ pkg-config sqlite3 --libs --cflags
-lsqlite3
現(xiàn)在的輸出是-lsqlite3。在這種情況下,你已經(jīng)知道了,但通常你不會知道,所以這是有價值的技術(shù)。編譯的最終命令將是
如下:
$ export PKG_CONFIG_LIBDIR=$(arm-cortex_a8-linux-gnueabihf-gcc \
-print-sysroot)/usr/lib/pkgconfig
$ arm-cortex_a8-linux-gnueabihf-gcc $(pkg-config sqlite3 --cflags --libs) \
sqlite-test.c -o sqlite-test
許多配置腳本會讀取由pkg-config生成的信息。這在交叉編譯時可能會導(dǎo)致錯誤,我們接下來會看到。
交叉編譯的問題
sqlite3是行為良好的軟件包,可以很好地進行交叉編譯,但并不是所有的軟件包都是如此。典型的痛點包括以下幾個方面:
- zlib等庫的自制構(gòu)建系統(tǒng),其配置腳本的行為與上一節(jié)所述的Autotools configure不一樣
- 配置腳本從主機上讀取 pkg-config 信息、頭文件和其他文件,而無視 --host 覆蓋。
- 堅持嘗試運行交叉編譯代碼的腳本
每種情況都需要仔細分析錯誤,給配置腳本增加參數(shù)以提供正確的信息,或者給代碼打補丁以完全避免問題。請記住,軟件包可能有許多依賴關(guān)系,特別是那些使用GTK或Qt的圖形界面的程序,或者處理多媒體內(nèi)容的程序。舉個例子,mplayer,這是一個流行的播放多媒體內(nèi)容的工具,與100多個庫有依賴關(guān)系。要想把它們?nèi)拷⑵饋?,需要花費數(shù)周的時間。
因此,我不建議以這種方式為目標手動交叉編譯組件,除非沒有其他選擇,或者需要編譯的軟件包數(shù)量很少。更好的方法是使用Buildroot或Yocto項目這樣的構(gòu)建工具,或者通過為你的目標架構(gòu)設(shè)置本地構(gòu)建環(huán)境來完全避免這個問題?,F(xiàn)在你可以明白為什么像Debian這樣的發(fā)行版總是在本地編譯了。
CMake
CMake更像是一個元構(gòu)建系統(tǒng),因為它依賴于底層平臺的本地工具來構(gòu)建軟件。在Windows上,CMake可以為Microsoft Visual Studio生成項目文件,在macOS上,它可以為Xcode生成項目文件。與每個主要平臺的主要IDE集成不是簡單的任務(wù),這解釋了CMake作為領(lǐng)先的跨平臺構(gòu)建系統(tǒng)解決方案的成功。CMake也可以在Linux上運行,在那里它可以和你選擇的交叉編譯工具鏈一起使用。
要配置、構(gòu)建和安裝本地Linux操作系統(tǒng)的軟件包,請運行以下命令:
$ cmake .
$ make
$ sudo make install
在Linux上,本地構(gòu)建工具是GNU make,因此CMake默認生成makefile文件供我們構(gòu)建使用。很多時候,我們想進行源外構(gòu)建,以便對象文件和其他構(gòu)建工件與源文件保持分離。
要在名為_build的子目錄中配置源外構(gòu)建,請運行
以下命令:
$ mkdir _build
$ cd _build
$ cmake ..
這將在CMakeLists.txt所在的項目目錄下的_build子目錄中生成makefiles。CMakeLists.txt文件相當于基于Autotools的項目的CMake配置腳本。
然后,我們可以從_build目錄中構(gòu)建項目的源代碼,并像以前一樣安裝軟件包:
$ make
$ sudo make install
CMake使用絕對路徑,所以一旦生成了makefiles,_build子目錄就不能被復(fù)制或移動,否則任何后續(xù)的make步驟都可能失敗。注意,CMake默認將軟件包安裝到系統(tǒng)目錄中,如/usr/bin,即使是源外構(gòu)建。
要生成 makefiles 以使 make 將軟件包安裝在 _build 子目錄中,請用下面的命令替換之前的 cmake 命令:
$ cmake .. -D CMAKE_INSTALL_PREFIX=../_build
我們不再需要在make install前加上sudo,因為我們不需要升高權(quán)限來復(fù)制包文件到_build目錄中。
同樣地,我們可以使用另一個CMake命令行選項來生成用于交叉編譯的makefiles:
$ cmake .. -D CMAKE_C_COMPILER="/usr/local/share/x-tools/arm-cortex_a8-linux-gnueabihf-gcc"
但是用CMake進行交叉編譯的最佳做法是創(chuàng)建工具鏈文件,除了針對嵌入式Linux的其他相關(guān)變量外,還要設(shè)置CMAKE_C_COMPILER和CMAKE_CXX_COMPILER。
當我們以模塊化的方式設(shè)計我們的軟件時,CMake的工作效果最好,它在庫和庫之間強制執(zhí)行
清楚地定義了庫和組件之間的API邊界。
下面是一些在CMake中反復(fù)出現(xiàn)的關(guān)鍵術(shù)語:
- target: 一個軟件組件,如庫或可執(zhí)行文件。
- properties: 包括構(gòu)建目標所需的源文件、編譯器選項和鏈接庫。
- package:CMake文件,用于配置目標: CMake文件,用于配置外部目標的構(gòu)建,就像它被定義在你的CMakeLists.txt本身一樣。
例如,如果我們有一個名為dummy的基于CMake的可執(zhí)行文件,需要依賴SQLite,我們可以定義以下CMakeLists.txt:
cmake_minimum_required (VERSION 3.0)
project (Dummy)
add_executable(dummy dummy.c)
find_package (SQLite3)
target_include_directories(dummy PRIVATE ${SQLITE3_INCLUDE_DIRS})
target_link_libraries (dummy PRIVATE ${SQLITE3_LIBRARIES})
CMake為流行的C和C++包配備了大量的搜索器,包括OpenSSL、Boost和protobuf,這使得本地開發(fā)比我們只使用純粹的makefiles更有成效。
PRIVATE修飾符可以防止諸如頭文件和標志等細節(jié)泄露到假目標之外。當被構(gòu)建的目標是庫而不是可執(zhí)行文件時,使用PRIVATE更有意義。在使用CMake定義你自己的目標時,把目標看作是模塊,并試圖最小化其暴露的表面區(qū)域。只有在絕對必要時才使用PUBLIC修飾符,并對僅有頭文件的庫使用INTERFACE修飾符。
將你的應(yīng)用程序建模為一個具有目標之間的邊的依賴圖。這個圖不僅應(yīng)該包括你的應(yīng)用程序直接鏈接到的庫,還應(yīng)該包括任何橫向的依賴關(guān)系。為了獲得最佳效果,請刪除圖中的任何循環(huán)或其他不必要的獨立性。在你開始編碼之前,通常最好進行這個練習(xí)。一個干凈的、易于維護的CMakeLists.txt和一個無人問津的亂七八糟的東西之間,稍加計劃就能產(chǎn)生不同的效果。
總結(jié)
工具鏈始終是你的起點;接下來的一切都取決于是否有有效的、可靠的工具鏈。
你可以從什么都不做開始,只用工具鏈--也許是用crosstool-NG構(gòu)建的,或者是從Linaro下載的--用它來編譯你在目標上需要的所有軟件包?;蛘吣憧梢垣@得工具鏈,作為使用Buildroot或Yocto項目等構(gòu)建系統(tǒng)從源代碼生成的發(fā)行版的一部分。小心那些作為硬件包的一部分免費提供給你的工具鏈或發(fā)行版;它們通常配置很差而且沒有得到維護。文章來源:http://www.zghlxwxcb.cn/news/detail-515893.html
一旦你有了一個工具鏈,你就可以用它來構(gòu)建你的嵌入式Linux系統(tǒng)的其他組件。在下一章中,你將學(xué)習(xí)引導(dǎo)程序,它將你的設(shè)備帶入生活并開始啟動過程。我們將使用本章中建立的工具鏈為BeagleBone Black建立一個工作的引導(dǎo)程序。
進一步閱讀
這里有幾段視頻,記錄了寫作時交叉工具鏈和構(gòu)建系統(tǒng)的技術(shù)狀況:
Bernhard "Bero" Rosenkr?nzer的《2020年工具鏈和交叉編譯器的新視角》:https://www.youtube.com/watch?v=BHaXqXzAs0Y
用于模塊化設(shè)計的現(xiàn)代CMake,作者Mathieu Ropert:
https://www.youtube.com/watch?v=eC9-iRN2b04文章來源地址http://www.zghlxwxcb.cn/news/detail-515893.html
到了這里,關(guān)于掌握嵌入式Linux編程2工具鏈的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!