Linux下PAM認證詳解(以centos7為例) PAM簡介(Pluggable Authentication Modules,可插拔認證模塊) Sun公司於1995年開發的一種與認證相關的通用框架機制:PAM(可插拔認證模塊)是實現認證工作的一個模塊。 因為每個服務都用到不同的認證方式,所以就需 ...
2工具鏈
工具鏈是嵌入式Linux的第一個元素,也是你項目的起點。你將用它來編譯所有將在你的設備上運行的代碼。你在這個早期階段做出的選擇將對最終結果產生深遠的影響。你的工具鏈應該能夠通過使用處理器的最佳指令集來有效地利用你的硬體。它應該支持你所需要的語言,並對攜帶型操作系統介面(POSIX)和其他系統介面有一個堅實的實現。
你的工具鏈應該在整個項目中保持不變。換句話說,一旦你選擇了你的工具鏈,就一定要堅持下去。在一個項目中以不一致的方式改變編譯器和開發庫會導致微妙的錯誤。也就是說,當發現安全缺陷或錯誤時,最好還是要更新你的工具鏈。
獲得工具鏈可以像下載和安裝TAR文件一樣簡單,也可以像從源代碼構建整個東西一樣複雜。在本章中,我採取了後一種方法,在crosstool-NG的工具的幫助下,我可以向你展示創建工具鏈的細節。稍後,在第6章 "選擇構建系統 "中,我將轉向使用構建系統生成的工具鏈,這是更常用的獲得工具鏈的方法。當我們讀到第14章 "從BusyBox runit開始 "時,我們將下載一個預先構建好的Linaro工具鏈來和Buildroot一起使用,這樣可以節省一些時間。
技術要求
基於Linux的主機系統,安裝有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或其對應物。
我推薦使用Ubuntu 20.04 LTS或更高版本,因為本章的練習在寫作時都是在該Linux發行版上測試的。以下是在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。
工具鏈的介紹
工具鏈是一套將源代碼編譯成可執行文件的工具,可以在你的目標設備上運行,包括編譯器、鏈接器和運行時庫。最初,你需要工具鏈來構建嵌入式Linux系統的其他三個要素:引導程式、內核和根文件系統。它必須能夠編譯用彙編、C和C++編寫的代碼,因為這些都是基礎開源包中使用的語言。
通常情況下,Linux的工具鏈是基於GNU項目的組件
(http://www.gnu.org),在寫這篇文章時,大多數情況下仍是如此。然而,在過去的幾年中,Clang編譯器和相關的低級虛擬機(LLVM)項目(http://llvm.org)已經發展到了現在可以替代GNU工具鏈的程度。LLVM和基於GNU的工具鏈之間的主要區別是許可;LLVM用BSD許可,而GNU用GPL。
Clang也有一些技術上的優勢,比如更快的編譯和更好的診斷,但GNU GCC的優勢在於與現有代碼庫的相容性以及對各種架構和操作系統的支持。雖然花了一些年的時間,Clang現在可以編譯嵌入式Linux所需的所有組件,是GNU的可行的替代方案。要瞭解更多的情況,請看https://www.kernel.org/doc/html/latest/kbuild/llvm.html。
關於如何使用Clang進行交叉編譯,在https://clang.llvm.org/docs/CrossCompilation.html,有很好的描述。如果你想把它作為嵌入式Linux構建系統的一部分,EmbToolkit(https://embtoolkit.org)完全支持GNU和LLVM/Clang工具鏈,而且很多人正在努力將Clang用於Buildroot和Yocto項目。我將在第6章 "選擇構建系統 "中介紹嵌入式構建系統。同時,本章主要討論GNU工具鏈,因為它仍然是Linux中最流行和最成熟的工具鏈。
標準的GNU工具鏈由三個主要部分組成:
-
Binutils: 一套二進位實用程式,包括彙編器和鏈接器。它可以在http://gnu.org/software/binutils。
-
GNU編譯器集合(GCC): 這些是C和其他語言的編譯器,根據GCC的版本,包括C++、Objective-C、Objective-C++、Java、Fortran、Ada和Go。它們都使用一個共同的後端,產生彙編代碼,並將其輸入到GNU彙編器中。它可以在http://gcc.gnu.org/。
-
C庫: 基於POSIX規範的標準化應用程式介面(API),它是應用程式進入操作系統內核的主要介面。
應用程式的主要介面。正如我們在本章後面將看到的,有幾個C語言庫需要考慮。
除了這些,你還需要一份Linux內核頭文件的副本,其中包含直接訪問內核時需要的定義和常量。現在,你需要它們來編譯C語言庫,但是你以後在編寫程式或編譯與特定Linux設備交互的庫時也需要它們,例如,通過Linux幀緩衝器驅動來顯示圖形。這不是簡單的在你的內核源代碼的include目錄下複製頭文件的問題。這些頭文件只是為了在內核中使用,並且包含一些定義,如果在原始狀態下用來編譯普通的Linux應用程式,會引起衝突。
相反,你需要生成一套經過消毒的內核頭文件,我在第5章 "構建根文件系統 "中已經說明瞭這一點。
內核頭文件是否由你將要使用的Linux的確切版本生成,通常並不重要。因為內核介面總是向後相容的,所以只需要頭文件來自與你在目標上使用的內核相同或更早的內核。
大多數人認為GNU調試器(GDB)也是工具鏈的一部分,而且通常在這時就已經建立了。我將在第19章 "使用GDB進行調試 "中討論GDB。
工具鏈的類型
對於我們的目的,有兩種類型的工具鏈:
- 原生
這種工具鏈與它生成的程式在同一類型的系統(有時是同一實際系統)上運行。這是台式機和伺服器的通常情況,它在某些類別的嵌入式設備上也開始流行。例如,運行Debian for ARM的Raspberry Pi就有自我托管的本地編譯器。
- 交叉
這種工具鏈在與目標系統不同的類型上運行,允許在快速的桌面PC上進行開發,然後載入到嵌入式目標上進行測試。
幾乎所有的嵌入式Linux開發都是使用交叉開發工具鏈完成的,部分原因是大多數嵌入式設備由於缺乏計算能力、記憶體和存儲空間而不太適合進行程式開發,但也因為它使主機和目標環境分離。當主機和目標機使用相同的架構時,例如x86_64,後一點尤其重要。在這種情況下,在主機上進行本地編譯並簡單地將二進位文件複製到目標機上是很誘人的。
這在一定程度上是可行的,但很可能主機發行版比目標版更經常地收到更新,或者為目標版編寫代碼的不同工程師對主機開發庫的版本略有不同。隨著時間的推移,開發系統和目標系統將出現分歧,你將違反工具鏈應該在項目的整個生命周期內保持不變的原則。如果你能確保主機和目標構建環境是同步的,你就能使這種方法發揮作用。然而,更好的方法是保持主機和目標的分離,而交叉工具鏈就是這樣做的方法。
然而,有一個支持本地開發的反駁理由。交叉開發會造成交叉編譯你的目標所需的所有庫和工具的負擔。我們將在後面的 "交叉編譯的藝術 "一節中看到,交叉開發並不總是簡單的,因為許多開源包並不是被設計成以這種方式構建的。集成構建工具,包括Buildroot和Yocto項目,通過封裝規則來幫助你交叉編譯一系列你在典型的嵌入式系統中需要的軟體包,但是如果你想編譯大量的額外軟體包,那麼最好是原生編譯它們。例如,使用交叉編譯器為Raspberry Pi或BeagleBone構建一個Debian發行版將是非常困難的。相反,它們是被本地編譯的。
從頭開始創建一個本地構建環境並不容易。首先,你仍然需要交叉編譯器來在目標上創建本地構建環境,然後用它來構建軟體包。然後,為了在合理的時間內進行本地構建,你將需要一個由配置良好的目標板組成的構建農場,或者你可以使用Quick EMUlator(QEMU)來模擬目標。
CPU架構
工具鏈必鬚根據目標CPU的能力來構建,這包括以下內容:
- CPU架構: ARM、無互鎖流水線階段的微處理器(MIPS Microprocessor without Interlocked Pipelined Stages)、x86_64,等等。
- Big-或little-endian操作: 有些CPU可以在兩種模式下運行,但每種模式下的機器代碼是不同的。
- 浮點支持: 並非所有版本的嵌入式處理器都實現了硬體浮點單元,在這種情況下,工具鏈必須被配置為調用軟體浮點庫。
- 應用二進位介面(Application Binary Interface): 用於在函數調用之間傳遞參數的調用慣例。
在許多體繫結構中,ABI在整個處理器系列中是不變的。值得註意的例外是ARM。ARM 架構在 2000 年代末過渡到擴展應用二進位介面 (EABI),導致以Extended Application Binary Interface前的 ABI 被命名為舊應用二進位介面 (OABI Old Application Binary Interface )。雖然OABI現在已經過時了,但你會繼續看到對EABI的引用。從那時起,根據浮點參數的傳遞方式,EABI已經一分為二。
最初的EABI使用通用(整數)寄存器,而較新的Extended Application Binary Interface Hard-Float(EABIHF)使用浮點寄存器。EABIHF的浮點運算速度明顯更快,因為它不需要在整數和浮點寄存器之間進行複製,但它與沒有浮點單元的CPU不相容。因此,你必須在兩個不相容的ABI之間做出選擇;你不能混合兩者,因此你必須在這個階段做出決定。
GNU在工具鏈中的每個工具的名稱上使用了一個首碼,它標識了可以生成的各種組合。它由一個用破折號隔開的三或四個組件組成,如下麵所述:
- CPU: 這是CPU架構,如ARM、MIPS或x86_64。如果CPU有兩種endian模式,可以通過添加el來區分小端模式或eb來區分大端模式。很好的例子是小位元組的MIPS,mipsel,和大位元組的ARM,armeb。
- 供應商: 這標識了工具鏈的提供者。例子包括buildroot、poky,或者只是未知。有時它被完全忽略了。
- 內核: 對於我們的目的,它總是linux。
- 作業系統: 用戶空間組件的名稱,可能是gnu或musl。ABI也可以附加在這裡,所以對於ARM工具鏈,你可以看到gnueabi、gnueabihf、musleabi或musleabihf。
你可以通過使用gcc的-dumpmachine選項找到構建工具鏈時使用的元組。例如,你可以在主機上看到以下內容:
$ gcc -dumpmachine
x86_64-linux-gnu
這個元組表示CPU為x86_64,內核為linux,用戶空間為gnu。
重要提示:
當一個本地編譯器安裝在機器上時,通常會在工具鏈中創建沒有首碼的工具鏈接,這樣你就可以用gcc命令調用C編譯器。
下麵是一個使用交叉編譯器的例子:
$ mipsel-unknown-linux-gnu-gcc -dumpmachine
mipsel-unknown-linux-gnu-表示小-endian MIPS的CPU,未知的供應商,linux的內核,和gnu的用戶空間。
選擇C庫
Unix操作系統的編程介面是由C語言定義的,現在由POSIX標准定義。C庫是該介面的實現;它是Linux程式進入內核的門戶,如下圖所示。即使你用其他語言編寫程式,也許是Java或Python,各自的運行時支持庫最終都要調用C庫,如圖所示:
每當C庫需要內核的服務時,它將使用內核的系統調用介面在用戶空間和內核空間之間轉換。可以繞過C庫,直接進行內核系統調用,但是這很麻煩,幾乎沒有必要。
有幾個C庫可以選擇。主要的選擇有以下幾種:
-
glibc: 這是標準的GNU C庫,可以在https://gnu.org/software/libc。它很大,而且直到最近還不是很好配置,但它是POSIX API的最完整的實現。它的許可證是LGPL 2.1。
-
musl libc: https://musl.libc.org。musl libc庫相對較新,但作為GNU libc的小型和符合標準的替代品,它已經得到了很多關註。對於記憶體和存儲空間有限的系統來說,它是一個不錯的選擇。它有一個MIT許可證。
-
uClibc-ng: https://uclibc-ng.org。u實際上是一個希臘的mu字元,表示這是微控制器的C庫。它最初是為了與uClinux(用於沒有記憶體管理單元的CPU的Linux)一起使用而開發的,但後來被改編為可用於完整的Linux。uClibc-ng庫是原uClibc項目(https://uclibc.org)的,不幸的分支是該項目已經年久失修了。兩者都是以LGPL 2.1授權的。
-
eglibc: http://www.eglibc.org/home。現在已經過時了,eglibc是glibc的分支,做了一些修改,使其更適合於嵌入式使用。在其他方面,eglibc增加了配置選項和對glibc沒有覆蓋的架構的支持,特別是PowerPC e500 CPU內核。在2.20版本中,eglibc的代碼庫被合併回glibc中。eglibc庫不再被維護。
那麼,該選擇哪一個呢?我的建議是,只有當你使用uClinux時才使用uClibc-ng。如果你的存儲空間或記憶體非常有限,那麼musl libc是一個不錯的選擇,否則就使用glibc:
你對C庫的選擇可能會限制你對工具鏈的選擇,因為並不是所有預置的工具鏈都支持所有的C庫。
尋找工具鏈
對於你的交叉開發工具鏈,你有三個選擇:你可以找到符合你需求的現成的工具鏈;你可以使個由嵌入式構建工具生成的工具鏈,這在第6章,選擇構建系統中有所涉及;或者你可以按照本章後面的描述,自己創建。
預製的交叉工具鏈是一個有吸引力的選擇,因為你只需要下載和安裝它,但是你被限制在那個特定的工具鏈的配置上,而且你要依賴於你得到它的人或組織。
最有可能的是,這將是其中之一:
- SoC或電路板供應商。大多數供應商提供Linux工具鏈。
- 致力於為某個特定架構提供系統級支持的聯盟。例如,Linaro, (https://www.linaro.org)有針對ARM架構的預建工具鏈。
- 第三方Linux工具供應商,如Mentor Graphics、TimeSys或MontaVista。
- 桌面Linux發行版的交叉工具包。例如,基於 Debian 的發行版有用於交叉編譯 ARM、MIPS 和 PowerPC 目標的軟體包。
- 集成的嵌入式構建工具產生的二進位SDK。Yocto項目在http://downloads.yoctoproject.org/releases/yocto/yocto-[版本]/toolchain有一些例子。
- 已經找不到的論壇的鏈接。
在所有這些情況下,你必須決定所提供的預建工具鏈是否符合你的要求。它是否使用你喜歡的C庫?供應商是否會給你提供安全修複和錯誤的更新,請記住我在第一章 "起步 "中對支持和更新的評論。如果你的答案是否定的,那麼你應該考慮創建你自己的。
不幸的是,建立工具鏈不是一件容易的事。如果你真的想自己來做這件事,可以看看Cross Linux From Scratch(https://trac.clfs.org)。在那裡,你可以找到關於如何創建每個組件的分步說明。
一個更簡單的選擇是使用crosstool-NG,它將這個過程封裝成一組腳本,並有菜單驅動的前端。不過,你仍然需要相當程度的知識,只是為了做出正確的選擇。
使用Buildroot或Yocto項目這樣的構建系統更簡單,因為它們會在構建過程中生成一個工具鏈。這是我的首選方案,正如我在第6章 "選擇構建系統 "中所示。
隨著crosstool-NG的興起,建立你自己的工具鏈當然是一個有效和可行的選擇。接下來讓我們看看如何做到這一點。
使用crosstool-NG構建工具鏈
參見:https://crosstool-ng.github.io/docs/install/
幾年前,Dan Kegel寫了一套用於生成跨開發工具鏈的腳本和makefiles,並稱之為crosstool(http://kegel.com/crosstool/)。2007年,Yann E. Morin在此基礎上創建了下一代的crosstool,即crosstool-NG(https://crosstool-ng.github.io)。今天,它是迄今為止從源頭上創建獨立的交叉工具鏈的最方便的方法。
在本節中,我們將使用crosstool-NG來為BeagleBone Black和QEMU。
安裝crosstool-NG
在你從源代碼構建crosstool-NG之前,你首先需要在你的主機上安裝本地工具鏈和一些構建工具。關於crosstool-NG的完整構建和運行時依賴項,請參見本章開頭的技術要求部分。
為BeagleBone Black構建工具鏈
Crosstool-NG可以建立許多不同組合的工具鏈。為了使初始配置更容易,它附帶了一套涵蓋許多常見使用情況的樣本。使用bin/ct-ng list-samples來生成這個列表。
BeagleBone Black有一個TI AM335x SoC,它包含一個ARM Cortex A8內核和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二進位介面,以整數寄存器傳遞浮點參數。我們更傾向於使用硬體浮點寄存器來實現這一目的,因為這樣可以加快有浮點和雙倍參數類型的函數調用速度。你以後可以改變配置,所以現在你應該選擇這個目標配置:
$ 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
該菜單系統是基於Linux內核的menuconfig,因此,任何配置過內核的人都會熟悉用戶界面的導航。如果沒有,請參閱第4章,配置和構建內核,瞭解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二進位介面,原因在前面已經討論過了。第三項是成功構建Linux內核所需要的。括弧里的名字是存儲在配置文件中的配置標簽。當你做了修改後,退出menuconfig菜單,並像以前一樣保存配置。
現在你可以使用crosstool-NG來獲取、配置,並按照你的規範來構建組件,輸入以下命令:
$ bin/ct-ng build
構建將花費大約半小時,之後你會發現你的工具鏈出現在~/x-tools/arm-cortex_a8-linux-gnueabihf。 如果出現下載zlib失敗,參考https://github.com/crosstool-ng/crosstool-ng/issues/1337,手動下載拷貝即可。
為QEMU構建工具鏈
在QEMU目標上,你將模擬具有ARM926EJ-S處理器內核的ARM-versatile PB評估板,它實現了ARMv5TE指令集。你需要生成符合規範的crosstool-NG工具鏈。該程式與BeagleBone Black的程式非常相似。
你首先要運行bin/ct-ng list-samples來找到一個好的基礎配置來工作。沒有精確的配置,所以使用通用的目標,arm-unknown-linux-gnueabi。你選擇它,如圖所示,先運行distclean以確保沒有以前構建時留下的工件:
$ 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)。
現在,用這裡的命令構建工具鏈:
$ bin/ct-ng build
和以前一樣,構建將需要大約半小時。工具鏈將被安裝在~/x-tools/arm-unknown-linux-gnueabi。
工具鏈的剖析
ARM Cortex A8工具鏈在~/x-tools/arm-cortex_a8-linux-gnueabihf/bin目錄下。在那裡,你會發現交叉編譯器,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命令列印文件的類型來確認它已經被交叉編譯了:
$ 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發現很多東西。例如,要找到版本,你可以使用--版本:
$ 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)
值得註意的是以下內容:
- --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內核生成的。
- --with-float=hard:為浮點單元生成操作代碼,並使用VFP寄存器作為參數。
- --enable-threads=posix:這將啟用POSIX線程。
這些是編譯器的預設設置。你可以在gcc命令行中覆蓋其中的大部分。例如,如果你想為不同的CPU編譯,你可以通過在命令行中加入-mcpu來覆蓋配置的設置--with-cpu,如下所示:
$ arm-cortex_a8-linux-gnueabihf-gcc -mcpu=cortex-a5 helloworld.c -o helloworld
你可以使用 --target-help 列印出可用的特定架構選項的範圍,如下所示:
$ arm-cortex_a8-linux-gnueabihf-gcc --target-help
果你計劃為每個目標創建工具鏈,那麼在一開始就設置好是有意義的,因為這將減少以後出錯的風險。另一方面,如果你想構建通用的工具鏈,並且準備在為特定目標構建時提供正確的設置,那麼你應該使基礎工具鏈通用化,這就是Yocto項目處理事情的方式。前面的例子都是遵循Buildroot的理念。
sysroot、庫和頭文件
工具鏈的系sysroot是包含庫、頭文件和其他配置文件的子目錄的目錄。它可以在配置工具鏈時通過 --with-sysroot= 設置,也可以在命令行中通過 --sysroot= 設置。 你可以通過使用 -print-sysroot 看到預設的系統根目錄的位置:
$ 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庫和動態鏈接器/載入器的共用對象,ld-linux
- usr/lib: C庫的靜態庫存檔文件,以及隨後可能安裝的任何其他庫。
- usr/include: 包含所有庫的頭文件
- usr/bin: 包含在目標機上運行的實用程式,如ldd命令
- usr/share: 用來進行本地化和國際化
- sbin: 提供ldconfig工具,用於優化庫的載入路徑。
很明顯,這些工具中的一些在開發主機上需要用來編譯程式,而另一些,例如共用庫和ld-linux,在運行時需要在目標上使用。
工具鏈中的其他工具
下麵是調用GNU工具鏈中其他各種組件的命令列表,並附有簡要說明:
- addr2line: 通過讀取可執行文件中的調試符號表,將程式地址轉換為文件名和數字。在對系統崩潰報告中列印出來的地址進行解碼時,它非常有用。
- ar: 歸檔工具,用於創建靜態庫。
- as: 這是GNU的彙編程式。
- c++filt: 這是用來拆解C++和Java的符號。
- cpp: 這是C語言的預處理器,用於擴展#define、#include和其他類似的指令。你很少需要單獨使用它。
- elfedit: 這是用來更新ELF文件的ELF頭。
- g++: 這是GNU的C++前端,它假定源文件中含有C++代碼。
- gcc: 這是 GNU C 前端,它假定源文件包含 C 代碼。
- gcov: 代碼覆蓋工具。
- gdb: 這是 GNU 調試器。
- gprof: 程式剖析工具。
- ld: 這是GNU的連接器。
- nm: 它列出對象文件中的符號。
- objcopy: 用來複制和翻譯對象文件。
- objdump: 用來顯示對象文件的信息。
- ranlib: 它在靜態庫中創建或修改索引,使鏈接階段更快。
- readelf: 它顯示ELF對象格式的文件信息。
- size: 這列出了部分大小和總大小。
- strings: 這顯示文件中的可列印字元的字元串。
- strip: 用於剝離調試符號表的對象文件,從而使其更小。通常情況下,你會剝離所有被放到目標上的可執行代碼。
C庫
C庫不是單一的庫文件。它由四個主要部分組成,共同實現POSIX API:
- libc:主C庫,包含眾所周知的POSIX函數,如printf、open、close、read、write,等等。
- libm: 包含數學函數,如cos、exp和log
- libpthread: 包含了所有的POSIX線程函數,名稱以pthread開頭的POSIX線程函數。
- librt: 擁有POSIX的實時擴展,包括共用記憶體和非同步I/O
第一個庫,libc,總是被鏈接進去的,但其他庫必須用-l選項明確地鏈接。-l的參數是去掉lib後的庫名。例如,通過調用sin()來計算正弦函數的程式可以用-lm鏈接到libm:
$ arm-cortex_a8-linux-gnueabihf-gcc myprog.c -o myprog -lm
你可以通過使用readelf命令來驗證哪些庫已經在這個或其他程式中被鏈接:
$ 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編寫的任何應用程式,無論是C語言還是C++語言,都會與lic 庫鏈接。你甚至不需要告訴gcc或g++去做,因為它總是鏈接libc。其他你可能想要鏈接的庫必須通過-l選項明確指定。
庫的代碼可以用兩種不同的方式連接:靜態的,意味著你的應用程式所調用的所有庫函數和它們的依賴關係都是從庫的歸檔文件中提取並綁定到你的可執行文件中;動態的,意味著對庫文件和這些文件中的函數的引用是在代碼中生成的,但實際的連接是在運行時動態進行的。
靜態庫
如果你正在構建小程式,靜態鏈接會更簡單,避免複製運行時庫文件和鏈接程式。它也會更小,因為只鏈接你的應用程式使用的代碼,而不是提供整個C庫。如果你需要在存放運行庫的文件系統可用之前運行程式,靜態鏈接也很有用。
你可以通過在命令行中添加-static來靜態鏈接所有庫:
$ 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
二進位文件的大小急劇增加:
靜態鏈接從庫存檔中提取代碼,通常命名為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
創建靜態庫就像使用ar命令創建對象文件的歸檔一樣簡單。如果我有兩個名為test1.c和test2.c的源文件,我想創建一個名為libtest.a的靜態庫,那麼我將做如下操作:
$ 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
靜共用庫
部署庫的更常見的方式是在運行時鏈接的共用對象,這可以更有效地利用存儲和系統記憶體,因為只需要載入一份代碼。它還使更新庫文件變得容易,而不必重新鏈接所有使用它們的程式。
共用庫的目標代碼必須是獨立於位置的,這樣運行時鏈接器就可以自由地在記憶體中的下一個空閑地址處找到它。要做到這一點,在gcc中加入-fPIC參數,然後用-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
這樣就創建了共用庫,libtest.so。要用這個庫鏈接一個應用程式,你可以添加-ltest,就像上一節中提到的靜態情況一樣,但這次代碼不包括在可執行文件中。相反,有一個庫的引用,運行時鏈接器必須解決這個問題:
$ 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,它必須存在於目標的文件系統中。鏈接器將在預設的搜索路徑中尋找libtest.so: /lib和/usr/lib。如果你想讓它也尋找其他目錄中的庫,你可以在LD_LIBRARY_PATH殼變數中放置用冒號分隔的路徑列表:
$ export LD_LIBRARY_PATH=/opt/lib:/opt/usr/lib
共用庫的版本號
共用庫的好處之一是,它們可以獨立於使用它們的程式而被更新。
庫的更新有兩種類型:
- 向後相容的方式修複錯誤或添加新功能的更新。
- 破壞與現有程式的相容性的更新
GNU/Linux有版本管理方案來處理這兩種情況。
每個庫都有發佈版本和介面號。發行版本是簡單的字元串,附加在庫的名稱上;例如,JPEG圖像庫libjpeg目前的發行版本是8.2.2,所以庫的名稱是libjpeg.so.8.2.2。名為libjpeg.so的符號鏈接到libjpeg.so.8.2.2,這樣,當你用-ljpeg編譯程式時,就會與當前的版本鏈接。如果你安裝了8.2.3版本,鏈接就會更新,你就會用這個版本的鏈接。
現在假設9.0.0版本出現了,它打破了向後的相容性。現在libjpeg.so的鏈接指向libjpeg.so.9.0.0,所以任何新的程式都會與新的版本鏈接,當libjpeg的介面發生變化時,可能會出現編譯錯誤,開發人員可以修複。
目標機上任何沒有重新編譯的程式都會以某種方式失敗,因為它們仍在使用舊的介面。這時,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,因此有可能在同一個系統上安裝兩個不相容的同一庫的版本。用libjpeg.so.8..鏈接的程式將載入libjpeg.so.8,而用libjpeg.so.9..鏈接的程式將載入libjpeg.so.9。
這就是為什麼,當你看/usr/lib/x86_64-linux-gnu/libjpeg*的目錄列表時,你會發現這四個文件:
- libjpeg.a: 這是用於靜態連接的庫存檔。
- libjpeg.so -> libjpeg.so.8.2.2: 這是符號鏈接,用於動態鏈接。
- libjpeg.so.8 -> libjpeg.so.8.2.2: 這是符號鏈接,在運行時載入庫時使用。
- libjpeg.so.8.2.2: 這是實際的共用庫,在編譯時和運行時都使用。
前兩個庫只需要在主機上構建,後兩個庫在運行時需要在目標機上使用。
雖然你可以直接從命令行中調用各種GNU交叉編譯工具,但這種技術並不能超越helloworld這。為了真正有效地進行交叉編譯,我們需要把交叉工具鏈和構建系統結合起來。
參考資料
- 軟體測試精品書籍文檔下載持續更新 https://github.com/china-testing/python-testing-examples 請點贊,謝謝!
- 本文涉及的python測試開發庫 謝謝點贊! https://github.com/china-testing/python_cn_resouce
- python精品書籍下載 https://github.com/china-testing/python_cn_resouce/blob/main/python_good_books.md
交叉編譯的藝術
擁有有效的交叉工具鏈是起點,而不是終點。在某些時候,你會想開始交叉編譯你在目標上需要的各種工具、應用程式和庫。它們中的許多都是開源包,每一個都有自己的編譯方法和自己的特殊性。
有一些常見的構建系統,包括以下幾種:
- 純粹的makefiles,其中的工具鏈通常由make變數CROSS_COMPILE控制
- Autotools: GNU構建系統
- CMake(https://cmake.org)
即使是構建基本的嵌入式Linux系統,也需要Autotools和makefiles。CMake是跨平臺的,多年來被越來越多的人採用,特別是在C++社區。在本節中,我們將介紹這三種構建工具。
簡單的makefile
一些重要的軟體包的交叉編譯非常簡單,包括Linux內核、U-Boot引導程式和BusyBox。對於每一軟體包,你只需要把工具鏈的首碼放在make變數CROSS_COMPILE中,例如,arm-cortex_a8-linux-gnueabi-。註意後面的破折號-。
因此,要編譯BusyBox,你可以這樣輸入:
$ make CROSS_COMPILE=arm-cortex_a8-linux-gnueabihf-
或者,你可以把它設置為shell變數:
$ export CROSS_COMPILE=arm-cortex_a8-linux-gnueabihf-
$ make
在U-Boot和Linux的情況下,你還必須將make變數ARCH設置為它們所支持的機器架構之一。
Autotools和CMake都可以生成makefile。Autotools只生成makefiles,而CMake則支持其他構建項目的方式,這取決於我們所針對的
我們的目標平臺(在我們的例子中嚴格來說是Linux)。
Autotools
Autotools這個名字是指一組工具,在許多開源項目中被用作構建系統。這些組件,連同相應的項目頁面,如下所示:
- 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的作用是抹平軟體包可能被編譯的不同類型的系統之間的差異,考慮到不同版本的編譯器、不同版本的庫、不同位置的頭文件以及與其他軟體包的依賴關係。
使用Autotools的軟體包有一個名為configure的腳本,它檢查依賴關係,並根據發現的情況生成makefiles。configure腳本還可以讓你有機會啟用或禁用某些功能。你可以通過運行./configure --help找到所提供的選項。
要配置、構建和安裝本地操作系統的軟體包,你通常要運行以下三個命令:
$ ./configure
$ make
$ sudo make install
Autotools也能夠處理交叉開發。你可以通過設置這些shell變數來影響配置的腳本的行為:
- CC: C編譯器的命令。
- CFLAGS: 額外的C編譯器標誌。
- CXX: C++編譯器命令。
- CXXFLAGS: 額外的C++編譯器標誌。
- LDFLAGS: 額外的鏈接器標誌;例如,如果你在非標準的目錄
下有庫,你將把它添加到庫搜索路徑上 加入 -L 。 - LIBS: 包含一個要傳遞給鏈接器的額外庫的列表;例如、 -lm表示數學庫。
- CPPFLAGS: 包含C/C++預處理器的標誌;例如,你可以添加-I
來搜索非標準目錄中的頭文件 。 - CPP:要使用的C預處理器。有時只設置CC變數就足夠了,如下所示:
$ CC=arm-cortex_a8-linux-gnueabihf-gcc ./configure
在其他時候,這將導致類似這樣的錯誤:
[…]
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經常試圖通過編譯代碼片段並運行它們來發現工具鏈的能力,如果程式已經被交叉編譯,這就無法工作。
註意:當你進行交叉編譯時,把 --host=
Autotools理解在編譯軟體包時可能涉及的三種不同類型的機器:
- Build: 構建軟體包的電腦,預設為當前機器。
- Host: 程式將要運行的電腦。對於本地編譯,這部分留空,預設為與Build相同的電腦。當你進行交叉編譯時,把它設置為你的工具鏈的元組。
- Target: 程式將生成代碼的電腦。你會在構建交叉編譯器時設置這個。
因此,為了交叉編譯,你只需要覆蓋主機,如下所示:
$ 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
你可能會發現,最後的命令會因為文件許可權錯誤而失敗。crosstool-NG工具鏈預設是只讀的,這就是為什麼在構建它時將CT_PREFIX_DIR_RO設置為y是很有用的。另一個常見的問題是,工具鏈被安裝在系統目錄下,例如/opt或/usr/local。在這種情況下,你在運行安裝時將需要root許可權。
安裝後,你應該發現各種文件已經被添加到你的工具鏈中:
/usr/bin: sqlite3: 這是一個SQLite的命令行介面,你可以在目標機上安裝和運行。 /usr/lib: libsqlite3.so.0.8.6, libsqlite3.so.0, libsqlite3.s, libsqlite3.la, libsqlite3.a: 這些是共用和靜態庫。 /usr/lib/pkgconfig: sqlite3.pc: 這是軟體包的配置文件,如下一節所述。 /usr/lib/include: sqlite3.h, sqlite3ext.h: 這些是頭文件。 /usr/share/man/man1: sqlite3.1: 這是手冊頁。
現在你可以編譯使用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是調用SQLite函數的假想程式。由於sqlite3已經被安裝到系統根中,編譯器會順利找到頭文件和庫文件。如果它們被安裝在其他地方,你將不得不添加-L
當然,也會有運行時的依賴性,你將不得不在目標目錄中安裝適當的文件安裝到目標目錄中,如第5章 "建立根文件系統 "中所述。
為了交叉編譯一個庫或包,它的依賴項首先需要被交叉編譯。Autotools依靠pkg-config工具來收集關於由Autotools交叉編譯的軟體包的重要信息。
包的配置
$ 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
失敗的原因是它在主機的系統根中尋找,而主機上還沒有安裝libsqlite3的開發包。你需要通過設置PKG_CONFIG_LIBDIR殼變數將其指向目標工具鏈的系統根:
$ export PKG_CONFIG_LIBDIR=$(arm-cortex_a8-linux-gnueabihf-gcc \
-print-sysroot)/usr/lib/pkgconfig
$ pkg-config sqlite3 --libs --cflags
-lsqlite3
現在的輸出是-lsqlite3。在這種情況下,你已經知道了,但通常你不會知道,所以這是有價值的技術。編譯的最終命令將是
如下:
$ 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生成的信息。這在交叉編譯時可能會導致錯誤,我們接下來會看到。
交叉編譯的問題
sqlite3是行為良好的軟體包,可以很好地進行交叉編譯,但並不是所有的軟體包都是如此。典型的痛點包括以下幾個方面:
- zlib等庫的自製構建系統,其配置腳本的行為與上一節所述的Autotools configure不一樣
- 配置腳本從主機上讀取 pkg-config 信息、頭文件和其他文件,而無視 --host 覆蓋。
- 堅持嘗試運行交叉編譯代碼的腳本
每種情況都需要仔細分析錯誤,給配置腳本增加參數以提供正確的信息,或者給代碼打補丁以完全避免問題。請記住,軟體包可能有許多依賴關係,特別是那些使用GTK或Qt的圖形界面的程式,或者處理多媒體內容的程式。舉個例子,mplayer,這是一個流行的播放多媒體內容的工具,與100多個庫有依賴關係。要想把它們全部建立起來,需要花費數周的時間。
因此,我不建議以這種方式為目標手動交叉編譯組件,除非沒有其他選擇,或者需要編譯的軟體包數量很少。更好的方法是使用Buildroot或Yocto項目這樣的構建工具,或者通過為你的目標架構設置本地構建環境來完全避免這個問題。現在你可以明白為什麼像Debian這樣的發行版總是在本地編譯了。
CMake
CMake更像是一個元構建系統,因為它依賴於底層平臺的本地工具來構建軟體。在Windows上,CMake可以為Microsoft Visual Studio生成項目文件,在macOS上,它可以為Xcode生成項目文件。與每個主要平臺的主要IDE集成不是簡單的任務,這解釋了CMake作為領先的跨平臺構建系統解決方案的成功。CMake也可以在Linux上運行,在那裡它可以和你選擇的交叉編譯工具鏈一起使用。
要配置、構建和安裝本地Linux操作系統的軟體包,請運行以下命令:
$ cmake .
$ make
$ sudo make install
在Linux上,本地構建工具是GNU make,因此CMake預設生成makefile文件供我們構建使用。很多時候,我們想進行源外構建,以便對象文件和其他構建工件與源文件保持分離。
要在名為_build的子目錄中配置源外構建,請運行
以下命令:
$ mkdir _build
$ cd _build
$ cmake ..
這將在CMakeLists.txt所在的項目目錄下的_build子目錄中生成makefiles。CMakeLists.txt文件相當於基於Autotools的項目的CMake配置腳本。
然後,我們可以從_build目錄中構建項目的源代碼,並像以前一樣安裝軟體包:
$ make
$ sudo make install
CMake使用絕對路徑,所以一旦生成了makefiles,_build子目錄就不能被覆制或移動,否則任何後續的make步驟都可能失敗。註意,CMake預設將軟體包安裝到系統目錄中,如/usr/bin,即使是源外構建。
要生成 makefiles 以使 make 將軟體包安裝在 _build 子目錄中,請用下麵的命令替換之前的 cmake 命令:
$ cmake .. -D CMAKE_INSTALL_PREFIX=../_build
我們不再需要在make install前加上sudo,因為我們不需要升高許可權來複制包文件到_build目錄中。
同樣地,我們可以使用另一個CMake命令行選項來生成用於交叉編譯的makefiles:
$ cmake .. -D CMAKE_C_COMPILER="/usr/local/share/x-tools/arm-cortex_a8-linux-gnueabihf-gcc"
但是用CMake進行交叉編譯的最佳做法是創建工具鏈文件,除了針對嵌入式Linux的其他相關變數外,還要設置CMAKE_C_COMPILER和CMAKE_CXX_COMPILER。
當我們以模塊化的方式設計我們的軟體時,CMake的工作效果最好,它在庫和庫之間強制執行
清楚地定義了庫和組件之間的API邊界。
下麵是一些在CMake中反覆出現的關鍵術語:
- target: 一個軟體組件,如庫或可執行文件。
- properties: 包括構建目標所需的源文件、編譯器選項和鏈接庫。
- package:CMake文件,用於配置目標: CMake文件,用於配置外部目標的構建,就像它被定義在你的CMakeLists.txt本身一樣。
例如,如果我們有一個名為dummy的基於CMake的可執行文件,需要依賴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,這使得本地開發比我們只使用純粹的makefiles更有成效。
PRIVATE修飾符可以防止諸如頭文件和標誌等細節泄露到假目標之外。當被構建的目標是庫而不是可執行文件時,使用PRIVATE更有意義。在使用CMake定義你自己的目標時,把目標看作是模塊,並試圖最小化其暴露的錶面區域。只有在絕對必要時才使用PUBLIC修飾符,並對僅有頭文件的庫使用INTERFACE修飾符。
將你的應用程式建模為一個具有目標之間的邊的依賴圖。這個圖不僅應該包括你的應用程式直接鏈接到的庫,還應該包括任何橫向的依賴關係。為了獲得最佳效果,請刪除圖中的任何迴圈或其他不必要的獨立性。在你開始編碼之前,通常最好進行這個練習。一個乾凈的、易於維護的CMakeLists.txt和一個無人問津的亂七八糟的東西之間,稍加計劃就能產生不同的效果。
總結
工具鏈始終是你的起點;接下來的一切都取決於是否有有效的、可靠的工具鏈。
你可以從什麼都不做開始,只用工具鏈--也許是用crosstool-NG構建的,或者是從Linaro下載的--用它來編譯你在目標上需要的所有軟體包。或者你可以獲得工具鏈,作為使用Buildroot或Yocto項目等構建系統從源代碼生成的發行版的一部分。小心那些作為硬體包的一部分免費提供給你的工具鏈或發行版;它們通常配置很差而且沒有得到維護。
一旦你有了一個工具鏈,你就可以用它來構建你的嵌入式Linux系統的其他組件。在下一章中,你將學習引導程式,它將你的設備帶入生活並開始啟動過程。我們將使用本章中建立的工具鏈為BeagleBone Black建立一個工作的引導程式。
進一步閱讀
這裡有幾段視頻,記錄了寫作時交叉工具鏈和構建系統的技術狀況:
Bernhard "Bero" Rosenkränzer的《2020年工具鏈和交叉編譯器的新視角》:https://www.youtube.com/watch?v=BHaXqXzAs0Y
用於模塊化設計的現代CMake,作者Mathieu Ropert:
https://www.youtube.com/watch?v=eC9-iRN2b04