? ? ? ?便宜的物聯(lián)網(wǎng)板的普及意味著它不僅會(huì)控制應(yīng)用程序,還會(huì)控制整個(gè)軟件平臺(tái)。 那么,如何構(gòu)建一個(gè)針對(duì)特定用途的交叉編譯應(yīng)用程序的自定義發(fā)行版呢? 正如 Michael J. Hammel 在這里解釋的那樣,它并不像你想象的那么難。
為什么要定制?
以前,許多嵌入式項(xiàng)目都使用現(xiàn)成的發(fā)行版,然后出于種種原因,再將它們剝離到只剩下基本的必需的東西。首先,移除不需要的包以減少占用的存儲(chǔ)空間。在啟動(dòng)時(shí),嵌入式系統(tǒng)一般不需要大量的存儲(chǔ)空間以及可用存儲(chǔ)空間。在嵌入式系統(tǒng)運(yùn)行時(shí),可能從非易失性?xún)?nèi)存中拷貝大量的操作系統(tǒng)文件到內(nèi)存中。第二,移除用不到的包可以降低可能的攻擊面。如果你不需要它們就沒(méi)有必要把這些可能有漏洞的包掛在上面。最后,移除用不到包可以降低發(fā)行版管理的開(kāi)銷(xiāo)。如果在包之間有依賴(lài)關(guān)系,意味著任何一個(gè)包請(qǐng)求從上游更新,那么它們都必須保持同步。那樣可能就會(huì)出現(xiàn)驗(yàn)證噩夢(mèng)。
然而,從一個(gè)現(xiàn)有的發(fā)行版中去移除包并不像說(shuō)的那樣容易。移除一個(gè)包可能會(huì)打破與其它包保持的各種依賴(lài)關(guān)系,以及可能在上游的發(fā)行版管理中改變依賴(lài)。另外,由于一些包原生集成在引導(dǎo)或者運(yùn)行時(shí)進(jìn)程中,它們并不能輕易地簡(jiǎn)單地移除。所有這些都是項(xiàng)目之外的平臺(tái)的管理,并且有可能會(huì)導(dǎo)致意外的開(kāi)發(fā)延遲。
一個(gè)流行的選擇是使用上游發(fā)行版供應(yīng)商提供的構(gòu)建工具去構(gòu)建一個(gè)定制的發(fā)行版。無(wú)論是 Gentoo 還是 Debian 都提供這種自下而上的構(gòu)建方式。這些構(gòu)建工具中最為流行的可能是 Debian 的 debootstrap 實(shí)用程序。它取出預(yù)構(gòu)建的核心組件并允許用戶(hù)去精選出它們感興趣的包來(lái)構(gòu)建用戶(hù)自己的平臺(tái)。但是,debootstrap 最初僅在 x86 平臺(tái)上可用,雖然,現(xiàn)在有了 ARM(也有可能會(huì)有其它的平臺(tái))選項(xiàng)。debootstrap 和 Gentoo 的 catalyst 仍然需要從本地項(xiàng)目中將依賴(lài)管理移除。
一些人認(rèn)為讓別人去管理平臺(tái)軟件(像 Android 一樣)要比自己親自管理容易的多。但是,那些發(fā)行版都是多用途的,當(dāng)你在一個(gè)輕量級(jí)的、資源有限的物聯(lián)網(wǎng)設(shè)備上使用它時(shí),你可能會(huì)再三考慮從你手中被拿走的任何資源。
系統(tǒng)引導(dǎo)的基石
一個(gè)定制的 Linux 發(fā)行版要求許多軟件組件。其中第一個(gè)就是工具鏈toolchain。工具鏈?zhǔn)怯糜诰幾g軟件的一套工具集。包括(但不限于)一個(gè)編譯器、鏈接器、二進(jìn)制操作工具以及標(biāo)準(zhǔn)的 C 庫(kù)。工具鏈?zhǔn)菫橐粋€(gè)特定的目標(biāo)硬件設(shè)備專(zhuān)門(mén)構(gòu)建的。如果一個(gè)構(gòu)建在 x86 系統(tǒng)上的工具鏈想要用于樹(shù)莓派,那么這個(gè)工具鏈就被稱(chēng)為交叉編譯工具鏈。當(dāng)在內(nèi)存和存儲(chǔ)都十分有限的小型嵌入式設(shè)備上工作時(shí),最好是使用一個(gè)交叉編譯工具鏈。需要注意的是,即便是使用像 JavaScript 這樣的需要運(yùn)行在特定平臺(tái)的腳本語(yǔ)言為特定用途編寫(xiě)的應(yīng)用程序,也需要使用交叉編譯工具鏈編譯。
圖 1. 編譯依賴(lài)和引導(dǎo)順序
交叉編譯工具鏈用于為目標(biāo)硬件構(gòu)建軟件組件。需要的第一個(gè)組件是引導(dǎo)加載程序bootloader。當(dāng)計(jì)算機(jī)主板加電之后,處理器(可能有差異,取決于設(shè)計(jì))嘗試去跳轉(zhuǎn)到一個(gè)特定的內(nèi)存位置去開(kāi)始運(yùn)行軟件。那個(gè)內(nèi)存位置就是保存引導(dǎo)加載程序的地方。硬件可能有內(nèi)置的引導(dǎo)加載程序,它可能直接從它的存儲(chǔ)位置或者可能在它運(yùn)行前首先拷貝到內(nèi)存中。也可能會(huì)有多個(gè)引導(dǎo)加載程序。例如,第一階段的引導(dǎo)加載程序可能位于硬件的 NAND 或者 NOR 閃存中。它唯一的功能是設(shè)置硬件以便于執(zhí)行第二階段的引導(dǎo)加載程序——比如,存儲(chǔ)在 SD 卡中的可以被加載并運(yùn)行的引導(dǎo)加載程序。
引導(dǎo)加載程序能夠從硬件中取得足夠的信息,將 Linux 加載到內(nèi)存中并跳轉(zhuǎn)到正確的位置,將控制權(quán)有效地移交到 Linux。Linux 是一個(gè)操作系統(tǒng)。這意味著,在這種設(shè)計(jì)中,它除了監(jiān)控硬件和向上層軟件(也就是應(yīng)用程序)提供服務(wù)外,它實(shí)際上什么都不做。Linux 內(nèi)核中通常是各種各樣的固件塊。那些預(yù)編譯的軟件對(duì)象,通常包含硬件平臺(tái)使用的設(shè)備的專(zhuān)用 IP(知識(shí)資產(chǎn))。當(dāng)構(gòu)建一個(gè)定制發(fā)行版時(shí),在開(kāi)始編譯內(nèi)核之前,它可能會(huì)要求獲得一些 Linux 內(nèi)核源代碼樹(shù)沒(méi)有提供的必需的固件塊。
應(yīng)用程序保存在根文件系統(tǒng)中,這個(gè)根文件系統(tǒng)是通過(guò)編譯構(gòu)建的,它集合了各種軟件庫(kù)、工具、腳本以及配置文件。總的來(lái)說(shuō),它們都提供各種服務(wù),比如,網(wǎng)絡(luò)配置和 USB 設(shè)備掛載,這些都是將要運(yùn)行的項(xiàng)目應(yīng)用程序所需要的。
總的來(lái)說(shuō),一個(gè)完整的系統(tǒng)構(gòu)建要求下列的組件:
一個(gè)交叉編譯工具鏈
一個(gè)或多個(gè)引導(dǎo)加載程序
Linux 內(nèi)核和相關(guān)的固件塊
一個(gè)包含庫(kù)、工具以及實(shí)用程序的根文件系統(tǒng)
定制的應(yīng)用程序
使用適當(dāng)?shù)墓ぞ唛_(kāi)始構(gòu)建
交叉編譯工具鏈的組件可以手工構(gòu)建,但這是一個(gè)很復(fù)雜的過(guò)程。幸運(yùn)的是,現(xiàn)有的工具可以很容易地完成這一過(guò)程。構(gòu)建交叉編譯工具鏈的最好工具可能是 Crosstool-NG,這個(gè)工具使用了與 Linux 內(nèi)核相同的 kconfig 菜單系統(tǒng)來(lái)構(gòu)建工具鏈的每個(gè)細(xì)節(jié)和方面。使用這個(gè)工具的關(guān)鍵是,為目標(biāo)平臺(tái)找到正確的配置項(xiàng)。配置項(xiàng)通常包含下列內(nèi)容:
目標(biāo)架構(gòu),比如,是 ARM 還是 x86。
字節(jié)順序:小端字節(jié)順序(一般情況下,Intel 采用這種順序)還是大端字節(jié)順序(一般情況下,ARM 或者其它的平臺(tái)采用這種順序)。
編譯器已知的 CPU 類(lèi)型,比如,GCC 可以使用 -mcpu 或 --with-cpu。
支持的浮點(diǎn)類(lèi)型,如果有的話,比如,GCC 可以使用 -mfpu 或 --with-fpu。
二進(jìn)制實(shí)用工具binutils、C 庫(kù)以及 C 編譯器的特定版本信息。
圖 2. Crosstool-NG 配置菜單
前四個(gè)一般情況下可以從處理器制造商的文檔中獲得。對(duì)于較新的處理器,它們可能不容易找到,但是,像樹(shù)莓派或者 BeagleBoards(以及它們的后代和分支),你可以在像 嵌入式 Linux Wiki 這樣的地方找到相關(guān)信息。
二進(jìn)制實(shí)用工具、C 庫(kù)、以及 C 編譯器的版本,將與任何第三方提供的其它工具鏈分開(kāi)。首先,它們中的每一個(gè)都有多個(gè)提供者。Linaro 為最新的處理器類(lèi)型提供了最先進(jìn)的版本,同時(shí)致力于將該支持合并到像 GNU C 庫(kù)這樣的上游項(xiàng)目中。盡管你可以使用各種提供者的工具,你可能依然想去使用現(xiàn)成的 GNU 工具鏈或者相同的 Linaro 版本。
在 Crosstool-NG 中的另外的重要選擇是 Linux 內(nèi)核的版本。這個(gè)選擇將得到用于各種工具鏈組件的頭文件headers,但是它沒(méi)有必要一定與你在目標(biāo)硬件上將要引導(dǎo)的 Linux 內(nèi)核相同。選擇一個(gè)不比目標(biāo)硬件的內(nèi)核更新的 Linux 內(nèi)核是很重要的。如果可能的話,盡量選擇一個(gè)比目標(biāo)硬件使用的內(nèi)核更老的長(zhǎng)周期支持(LTS)的內(nèi)核。
對(duì)于大多數(shù)不熟悉構(gòu)建定制發(fā)行版的開(kāi)發(fā)者來(lái)說(shuō),工具鏈的構(gòu)建是最為復(fù)雜的過(guò)程。幸運(yùn)的是,大多數(shù)硬件平臺(tái)的二進(jìn)制工具鏈都可以想辦法得到。如果構(gòu)建一個(gè)定制的工具鏈有問(wèn)題,可以在線搜索像 嵌入式 Linux Wiki 這樣的地方去查找預(yù)構(gòu)建工具鏈。
引導(dǎo)選項(xiàng)
在構(gòu)建完工具鏈之后,接下來(lái)的工作是引導(dǎo)加載程序。引導(dǎo)加載程序用于設(shè)置硬件,以便于越來(lái)越復(fù)雜的軟件能夠使用這些硬件。第一階段的引導(dǎo)加載程序通常由目標(biāo)平臺(tái)制造商提供,它通常被燒錄到類(lèi)似于 EEPROM 或者 NOR 閃存這類(lèi)的在硬件上的存儲(chǔ)中。第一階段的引導(dǎo)加載程序?qū)⑹乖O(shè)備從這里(比如,一個(gè) SD 存儲(chǔ)卡)開(kāi)始引導(dǎo)。樹(shù)莓派的引導(dǎo)加載程序就是這樣的,它樣做也就沒(méi)有必要再去創(chuàng)建一個(gè)定制引導(dǎo)加載程序。
盡管如此,許多項(xiàng)目還是增加了第二階段的引導(dǎo)加載程序,以便于去執(zhí)行一個(gè)多樣化的任務(wù)。在無(wú)需使用 Linux 內(nèi)核或者像 plymouth 這樣的用戶(hù)空間工具的情況下提供一個(gè)啟動(dòng)動(dòng)畫(huà),就是其中一個(gè)這樣的任務(wù)。一個(gè)更常見(jiàn)的第二階段引導(dǎo)加載程序的任務(wù)是去提供基于網(wǎng)絡(luò)的引導(dǎo)或者使連接到 PCI 上的磁盤(pán)可用。在那種情況下,一個(gè)第三階段的引導(dǎo)加載程序,比如 GRUB,可能才是讓系統(tǒng)運(yùn)行起來(lái)所必需的。
最重要的是,引導(dǎo)加載程序加載 Linux 內(nèi)核并使它開(kāi)始運(yùn)行。如果第一階段引導(dǎo)加載程序沒(méi)有提供一個(gè)在啟動(dòng)時(shí)傳遞內(nèi)核參數(shù)的機(jī)制,那么,在第二階段的引導(dǎo)加載程序中就必須要提供。
有許多的開(kāi)源引導(dǎo)加載程序可以使用。U-Boot 項(xiàng)目 通常用于像樹(shù)莓派這樣的 ARM 平臺(tái)。CoreBoot 一般是用于像 Chromebook 這樣的 x86 平臺(tái)。引導(dǎo)加載程序是特定于目標(biāo)硬件專(zhuān)用的。引導(dǎo)加載程序的選擇總體上取決于項(xiàng)目的需求以及目標(biāo)硬件(可以去網(wǎng)絡(luò)上在線搜索開(kāi)源引導(dǎo)加載程序的列表)。
現(xiàn)在到了 Linux 登場(chǎng)的時(shí)候
引導(dǎo)加載程序?qū)⒓虞d Linux 內(nèi)核到內(nèi)存中,然后去運(yùn)行它。Linux 就像一個(gè)擴(kuò)展的引導(dǎo)加載程序:它進(jìn)行進(jìn)行硬件設(shè)置以及準(zhǔn)備加載高級(jí)軟件。內(nèi)核的核心將設(shè)置和提供在應(yīng)用程序和硬件之間共享使用的內(nèi)存;提供任務(wù)管理器以允許多個(gè)應(yīng)用程序同時(shí)運(yùn)行;初始化沒(méi)有被引導(dǎo)加載程序配置的或者是已經(jīng)配置了但是沒(méi)有完成的硬件組件;以及開(kāi)啟人機(jī)交互界面。內(nèi)核也許不會(huì)配置為在自身完成這些工作,但是,它可以包含一個(gè)嵌入的、輕量級(jí)的文件系統(tǒng),這類(lèi)文件系統(tǒng)大家熟知的有 initramfs 或者 initrd,它們可以獨(dú)立于內(nèi)核而創(chuàng)建,用于去輔助設(shè)置硬件。
內(nèi)核操作的另外的事情是去下載二進(jìn)制塊(通常稱(chēng)為固件)到硬件設(shè)備。固件是用特定格式預(yù)編譯的對(duì)象文件,用于在引導(dǎo)加載程序或者內(nèi)核不能訪問(wèn)的地方去初始化特定硬件。許多這種固件對(duì)象可以從 Linux 內(nèi)核源倉(cāng)庫(kù)中獲取,但是,還有很多其它的固件只能從特定的硬件供應(yīng)商處獲得。例如,經(jīng)常由它們自己提供固件的設(shè)備有數(shù)字電視調(diào)諧器或者 WiFi 網(wǎng)卡等。
固件可以從 initramfs 中加載,也或者是在內(nèi)核從根文件系統(tǒng)中啟動(dòng) init 進(jìn)程之后加載。但是,當(dāng)你去創(chuàng)建一個(gè)定制的 Linux 發(fā)行版時(shí),創(chuàng)建內(nèi)核的過(guò)程常常就是獲取各種固件的過(guò)程。
輕量級(jí)核心平臺(tái)
Linux 內(nèi)核做的最后一件事情是嘗試去運(yùn)行一個(gè)被稱(chēng)為 init 進(jìn)程的專(zhuān)用程序。這個(gè)專(zhuān)用程序的名字可能是 init 或者 linuxrc 或者是由加載程序傳遞給內(nèi)核的名字。init 進(jìn)程保存在一個(gè)能夠被內(nèi)核訪問(wèn)的文件系統(tǒng)中。在 initramfs 這種情況下,這個(gè)文件系統(tǒng)保存在內(nèi)存中(它可能是被內(nèi)核自己放置到那里,也可能是被引導(dǎo)加載程序放置在那里)。但是,對(duì)于運(yùn)行更復(fù)雜的應(yīng)用程序,initramfs 通常并不夠完整。因此需要另外一個(gè)文件系統(tǒng),這就是眾所周知的根文件系統(tǒng)。
圖 3. 構(gòu)建 root 配置菜單
initramfs 文件系統(tǒng)可以使用 Linux 內(nèi)核自身構(gòu)建,但是更常用的作法是,使用一個(gè)被稱(chēng)為 BusyBox 的項(xiàng)目去創(chuàng)建。BusyBox 組合許多 GNU 實(shí)用程序(比如,grep 或者 awk)到一個(gè)單個(gè)的二進(jìn)制文件中,以便于減小文件系統(tǒng)自身的大小。BusyBox 通常用于去啟動(dòng)根文件系統(tǒng)的創(chuàng)建過(guò)程。
但是,BusyBox 是特意輕量化設(shè)計(jì)的。它并不打算提供目標(biāo)平臺(tái)所需要的所有工具,甚至提供的工具也是經(jīng)過(guò)功能簡(jiǎn)化的。BusyBox 有一個(gè)“姊妹”項(xiàng)目叫做 Buildroot,它可以用于去得到一個(gè)完整的根文件系統(tǒng),提供了各種庫(kù)、實(shí)用程序,以及腳本語(yǔ)言。像 Crosstool-NG 和 Linux 內(nèi)核一樣,BusyBox 和 Buildroot 也都允許使用 kconfig 菜單系統(tǒng)去定制配置。更重要的是,Buildroot 系統(tǒng)自動(dòng)處理依賴(lài)關(guān)系,因此,選定的實(shí)用程序?qū)?huì)保證該程序所需要的軟件也會(huì)被構(gòu)建并安裝到 root 文件系統(tǒng)。
Buildroot 可以用多種格式去生成一個(gè)根文件系統(tǒng)包。但是,需要重點(diǎn)注意的是,這個(gè)文件系統(tǒng)是被歸檔的。單個(gè)的實(shí)用程序和庫(kù)并不是以 Debian 或者 RPM 格式打包進(jìn)去的。使用 Buildroot 將生成一個(gè)根文件系統(tǒng)鏡像,但是它的內(nèi)容不是單獨(dú)的包。即使如此,Buildroot 還是提供了對(duì) opkg 和 rpm 包管理器的支持的。這意味著,雖然根文件系統(tǒng)自身并不支持包管理,但是,安裝在根文件系統(tǒng)上的定制應(yīng)用程序能夠進(jìn)行包管理。
交叉編譯和腳本化
Buildroot 的其中一個(gè)特性是能夠生成一個(gè)臨時(shí)樹(shù)。這個(gè)目錄包含庫(kù)和實(shí)用程序,它可以被用于去交叉編譯其它應(yīng)用程序。使用臨時(shí)樹(shù)和交叉編譯工具鏈,使得在主機(jī)系統(tǒng)上而不是目標(biāo)平臺(tái)上對(duì) Buildroot 之外的其它應(yīng)用程序編譯成為可能。使用 rpm 或者 opkg 包管理軟件之后,這些應(yīng)用程序可以在運(yùn)行時(shí)使用包管理軟件安裝在目標(biāo)平臺(tái)的根文件系統(tǒng)上。
大多數(shù)定制系統(tǒng)的構(gòu)建都是圍繞著用腳本語(yǔ)言構(gòu)建應(yīng)用程序的想法去構(gòu)建的。如果需要在目標(biāo)平臺(tái)上運(yùn)行腳本,在 Buildroot 上有多種可用的選擇,包括 Python、PHP、Lua 以及基于 Node.js 的 JavaScript。對(duì)于需要使用 OpenSSL 加密的應(yīng)用程序也提供支持。
接下來(lái)做什么
Linux 內(nèi)核和引導(dǎo)加載程序的編譯過(guò)程與大多數(shù)應(yīng)用程序是一樣的。它們的構(gòu)建系統(tǒng)被設(shè)計(jì)為去構(gòu)建一個(gè)專(zhuān)用的軟件位。Crosstool-NG 和 Buildroot 是元構(gòu)建metabuild。元構(gòu)建是將一系列有自己構(gòu)建系統(tǒng)的軟件集合封裝為一個(gè)構(gòu)建系統(tǒng)??煽康脑獦?gòu)建包括 Yocto 和 OpenEmbedded。Buildroot 的好處是可以將更高級(jí)別的元構(gòu)建進(jìn)行輕松的封裝,以便于將定制 Linux 發(fā)行版的構(gòu)建過(guò)程自動(dòng)化。這樣做之后,將會(huì)打開(kāi) Buildroot 指向到項(xiàng)目專(zhuān)用的緩存?zhèn)}庫(kù)的選項(xiàng)。使用緩存?zhèn)}庫(kù)可以加速開(kāi)發(fā)過(guò)程,并且可以在無(wú)需擔(dān)心上游倉(cāng)庫(kù)變化的情況下提供構(gòu)建快照。
一個(gè)實(shí)現(xiàn)高級(jí)構(gòu)建系統(tǒng)的示例是 PiBox。PiBox 就是封裝了在本文中討論的各種工具的一個(gè)元構(gòu)建。它的目的是圍繞所有工具去增加一個(gè)通用的 GNU Make 目標(biāo)架構(gòu),以生成一個(gè)核心平臺(tái),這個(gè)平臺(tái)可以構(gòu)建或分發(fā)其它軟件。PiBox 媒體中心和 kiosk 項(xiàng)目是安裝在核心平臺(tái)之上的應(yīng)用層軟件的實(shí)現(xiàn),目的是用于去產(chǎn)生一個(gè)構(gòu)建平臺(tái)。Iron Man 項(xiàng)目 是為了家庭自動(dòng)化的目的而擴(kuò)展了這種應(yīng)用程序,它集成了語(yǔ)音管理和物聯(lián)網(wǎng)設(shè)備的管理。
但是,PiBox 如果沒(méi)有這些核心的軟件工具,它什么也做不了。并且,如果不去深入了解一個(gè)完整的定制發(fā)行版的構(gòu)建過(guò)程,那么你將無(wú)法正確運(yùn)行 PiBox。而且,如果沒(méi)有 PiBox 開(kāi)發(fā)團(tuán)隊(duì)對(duì)這個(gè)項(xiàng)目的長(zhǎng)期奉獻(xiàn),也就沒(méi)有 PiBox 項(xiàng)目,它完成了定制發(fā)行版構(gòu)建中的大量任務(wù)。
評(píng)論