作者 | Michael J.hammel
譯者 | qhwdw
便宜的物聯網板的普及意味著它不僅會控制應用程式,還會控制整個軟體平臺。 那麼,如何構建一個針對特定用途的交叉編譯應用程式的自定義發行版呢? 正如 Michael J. Hammel 在這裡解釋的那樣,它並不像你想象的那麼難。
為什麼要定製?
以前,許多嵌入式專案都使用現成的發行版,然後出於種種原因,再將它們剝離到只剩下基本的必需的東西。首先,移除不需要的包以減少佔用的儲存空間。在啟動時,嵌入式系統一般不需要大量的儲存空間以及可用儲存空間。在嵌入式系統執行時,可能從非易失性記憶體中複製大量的作業系統檔案到記憶體中。第二,移除用不到的包可以降低可能的攻擊面。如果你不需要它們就沒有必要把這些可能有漏洞的包掛在上面。最後,移除用不到包可以降低發行版管理的開銷。如果在包之間有依賴關係,意味著任何一個包請求從上游更新,那麼它們都必須保持同步。那樣可能就會出現驗證噩夢。
然而,從一個現有的發行版中去移除包並不像說的那樣容易。移除一個包可能會打破與其它包保持的各種依賴關係,以及可能在上游的發行版管理中改變依賴。另外,由於一些包原生整合在引導或者執行時行程中,它們並不能輕易地簡單地移除。所有這些都是專案之外的平臺的管理,並且有可能會導致意外的開發延遲。
一個流行的選擇是使用上游發行版供應商提供的構建工具去構建一個定製的發行版。無論是 Gentoo 還是 Debian 都提供這種自下而上的構建方式。這些構建工具中最為流行的可能是 Debian 的 debootstrap 實用程式。它取出預構建的核心元件並允許使用者去精選出它們感興趣的包來構建使用者自己的平臺。但是,debootstrap 最初僅在 x86 平臺上可用,雖然,現在有了 ARM(也有可能會有其它的平臺)選項。debootstrap 和 Gentoo 的 catalyst 仍然需要從本地專案中將依賴管理移除。
一些人認為讓別人去管理平臺軟體(像 Android 一樣)要比自己親自管理容易的多。但是,那些發行版都是多用途的,當你在一個輕量級的、資源有限的物聯網裝置上使用它時,你可能會再三考慮從你手中被拿走的任何資源。
系統引導的基石
一個定製的 Linux 發行版要求許多軟體元件。其中第一個就是工具鏈。工具鏈是用於編譯軟體的一套工具集。包括(但不限於)一個編譯器、聯結器、二進位制操作工具以及標準的 C 庫。工具鏈是為一個特定的標的硬體裝置專門構建的。如果一個構建在 x86 系統上的工具鏈想要用於樹莓派,那麼這個工具鏈就被稱為交叉編譯工具鏈。當在記憶體和儲存都十分有限的小型嵌入式裝置上工作時,最好是使用一個交叉編譯工具鏈。需要註意的是,即便是使用像 JavaScript 這樣的需要執行在特定平臺的指令碼語言為特定用途編寫的應用程式,也需要使用交叉編譯工具鏈編譯。
圖 1. 編譯依賴和引導順序
交叉編譯工具鏈用於為標的硬體構建軟體元件。需要的第一個元件是引導載入程式。當計算機主機板加電之後,處理器(可能有差異,取決於設計)嘗試去跳轉到一個特定的記憶體位置去開始執行軟體。那個記憶體位置就是儲存引導載入程式的地方。硬體可能有內建的引導載入程式,它可能直接從它的儲存位置或者可能在它執行前首先複製到記憶體中。也可能會有多個引導載入程式。例如,第一階段的引導載入程式可能位於硬體的 NAND 或者 NOR 快閃記憶體中。它唯一的功能是設定硬體以便於執行第二階段的引導載入程式——比如,儲存在 SD 卡中的可以被載入並執行的引導載入程式。
引導載入程式能夠從硬體中取得足夠的資訊,將 Linux 載入到記憶體中並跳轉到正確的位置,將控制權有效地移交到 Linux。Linux 是一個作業系統。這意味著,在這種設計中,它除了監控硬體和向上層軟體(也就是應用程式)提供服務外,它實際上什麼都不做。Linux 核心[1] 中通常是各種各樣的韌體塊。那些預編譯的軟體物件,通常包含硬體平臺使用的裝置的專用 IP(知識資產)。當構建一個定製發行版時,在開始編譯核心之前,它可能會要求獲得一些 Linux 核心原始碼樹沒有提供的必需的韌體塊。
應用程式儲存在根檔案系統中,這個根檔案系統是透過編譯構建的,它集合了各種軟體庫、工具、指令碼以及配置檔案。總的來說,它們都提供各種服務,比如,網路配置和 USB 裝置掛載,這些都是將要執行的專案應用程式所需要的。
總的來說,一個完整的系統構建要求下列的元件:
使用適當的工具開始構建
交叉編譯工具鏈的元件可以手工構建,但這是一個很複雜的過程。幸運的是,現有的工具可以很容易地完成這一過程。構建交叉編譯工具鏈的最好工具可能是 Crosstool-NG[2],這個工具使用了與 Linux 核心相同的 kconfig 選單系統來構建工具鏈的每個細節和方面。使用這個工具的關鍵是,為標的平臺找到正確的配置項。配置項通常包含下列內容:
-mcpu
或 --with-cpu
。-mfpu
或 --with-fpu
。圖 2. Crosstool-NG 配置選單
前四個一般情況下可以從處理器製造商的檔案中獲得。對於較新的處理器,它們可能不容易找到,但是,像樹莓派或者 BeagleBoards(以及它們的後代和分支),你可以在像 嵌入式 Linux Wiki[3] 這樣的地方找到相關資訊。
二進位制實用工具、C 庫、以及 C 編譯器的版本,將與任何第三方提供的其它工具鏈分開。首先,它們中的每一個都有多個提供者。Linaro 為最新的處理器型別提供了最先進的版本,同時致力於將該支援合併到像 GNU C 庫這樣的上游專案中。儘管你可以使用各種提供者的工具,你可能依然想去使用現成的 GNU 工具鏈或者相同的 Linaro 版本。
在 Crosstool-NG 中的另外的重要選擇是 Linux 內核的版本。這個選擇將得到用於各種工具鏈元件的頭檔案,但是它沒有必要一定與你在標的硬體上將要引導的 Linux 核心相同。選擇一個不比標的硬體的核心更新的 Linux 內核是很重要的。如果可能的話,儘量選擇一個比標的硬體使用的核心更老的長週期支援(LTS)的核心。
對於大多數不熟悉構建定製發行版的開發者來說,工具鏈的構建是最為複雜的過程。幸運的是,大多數硬體平臺的二進位制工具鏈都可以想辦法得到。如果構建一個定製的工具鏈有問題,可以線上搜尋像 嵌入式 Linux Wiki[3] 這樣的地方去查詢預構建工具鏈。
引導選項
在構建完工具鏈之後,接下來的工作是引導載入程式。引導載入程式用於設定硬體,以便於越來越複雜的軟體能夠使用這些硬體。第一階段的引導載入程式通常由標的平臺製造商提供,它通常被燒錄到類似於 EEPROM 或者 NOR 快閃記憶體這類的在硬體上的儲存中。第一階段的引導載入程式將使裝置從這裡(比如,一個 SD 儲存卡)開始引導。樹莓派的引導載入程式就是這樣的,它樣做也就沒有必要再去建立一個定製引導載入程式。
儘管如此,許多專案還是增加了第二階段的引導載入程式,以便於去執行一個多樣化的任務。在無需使用 Linux 核心或者像 plymouth 這樣的使用者空間工具的情況下提供一個啟動動畫,就是其中一個這樣的任務。一個更常見的第二階段引導載入程式的任務是去提供基於網路的引導或者使連線到 PCI 上的磁碟可用。在那種情況下,一個第三階段的引導載入程式,比如 GRUB,可能才是讓系統執行起來所必需的。
最重要的是,引導載入程式載入 Linux 核心並使它開始執行。如果第一階段引導載入程式沒有提供一個在啟動時傳遞核心引數的機制,那麼,在第二階段的引導載入程式中就必須要提供。
有許多的開源引導載入程式可以使用。U-Boot 專案[4] 通常用於像樹莓派這樣的 ARM 平臺。CoreBoot 一般是用於像 Chromebook 這樣的 x86 平臺。引導載入程式是特定於標的硬體專用的。引導載入程式的選擇總體上取決於專案的需求以及標的硬體(可以去網路上線上搜尋開源引導載入程式的串列)。
現在到了 Linux 登場的時候
引導載入程式將載入 Linux 核心到記憶體中,然後去執行它。Linux 就像一個擴充套件的引導載入程式:它進行進行硬體設定以及準備載入高階軟體。內核的核心將設定和提供在應用程式和硬體之間共享使用的記憶體;提供任務管理器以允許多個應用程式同時執行;初始化沒有被引導載入程式配置的或者是已經配置了但是沒有完成的硬體元件;以及開啟人機互動介面。核心也許不會配置為在自身完成這些工作,但是,它可以包含一個嵌入的、輕量級的檔案系統,這類檔案系統大家熟知的有 initramfs 或者 initrd,它們可以獨立於核心而建立,用於去輔助設定硬體。
核心操作的另外的事情是去下載二進位制塊(通常稱為韌體)到硬體裝置。韌體是用特定格式預編譯的物件檔案,用於在引導載入程式或者核心不能訪問的地方去初始化特定硬體。許多這種韌體物件可以從 Linux 核心源倉庫中獲取,但是,還有很多其它的韌體只能從特定的硬體供應商處獲得。例如,經常由它們自己提供韌體的裝置有數字電視調諧器或者 WiFi 網絡卡等。
韌體可以從 initramfs 中載入,也或者是在核心從根檔案系統中啟動 init 行程之後載入。但是,當你去建立一個定製的 Linux 發行版時,建立內核的過程常常就是獲取各種韌體的過程。
輕量級核心平臺
Linux 核心做的最後一件事情是嘗試去執行一個被稱為 init 行程的專用程式。這個專用程式的名字可能是 init 或者 linuxrc 或者是由載入程式傳遞給內核的名字。init 行程儲存在一個能夠被核心訪問的檔案系統中。在 initramfs 這種情況下,這個檔案系統儲存在記憶體中(它可能是被核心自己放置到那裡,也可能是被引導載入程式放置在那裡)。但是,對於執行更複雜的應用程式,initramfs 通常並不夠完整。因此需要另外一個檔案系統,這就是眾所周知的根檔案系統。
圖 3. 構建 root 配置選單
initramfs 檔案系統可以使用 Linux 核心自身構建,但是更常用的作法是,使用一個被稱為 BusyBox[5] 的專案去建立。BusyBox 組合許多 GNU 實用程式(比如,grep 或者 awk)到一個單個的二進位制檔案中,以便於減小檔案系統自身的大小。BusyBox 通常用於去啟動根檔案系統的建立過程。
但是,BusyBox 是特意輕量化設計的。它並不打算提供標的平臺所需要的所有工具,甚至提供的工具也是經過功能簡化的。BusyBox 有一個“姊妹”專案叫做 Buildroot[6],它可以用於去得到一個完整的根檔案系統,提供了各種庫、實用程式,以及指令碼語言。像 Crosstool-NG 和 Linux 核心一樣,BusyBox 和 Buildroot 也都允許使用 kconfig 選單系統去定製配置。更重要的是,Buildroot 系統自動處理依賴關係,因此,選定的實用程式將會保證該程式所需要的軟體也會被構建並安裝到 root 檔案系統。
Buildroot 可以用多種格式去生成一個根檔案系統包。但是,需要重點註意的是,這個檔案系統是被歸檔的。單個的實用程式和庫並不是以 Debian 或者 RPM 格式打包進去的。使用 Buildroot 將生成一個根檔案系統映象,但是它的內容不是單獨的包。即使如此,Buildroot 還是提供了對 opkg 和 rpm 包管理器的支援的。這意味著,雖然根檔案系統自身並不支援包管理,但是,安裝在根檔案系統上的定製應用程式能夠進行包管理。
交叉編譯和指令碼化
Buildroot 的其中一個特性是能夠生成一個臨時樹。這個目錄包含庫和實用程式,它可以被用於去交叉編譯其它應用程式。使用臨時樹和交叉編譯工具鏈,使得在主機系統上而不是標的平臺上對 Buildroot 之外的其它應用程式編譯成為可能。使用 rpm 或者 opkg 包管理軟體之後,這些應用程式可以在執行時使用包管理軟體安裝在標的平臺的根檔案系統上。
大多數定製系統的構建都是圍繞著用指令碼語言構建應用程式的想法去構建的。如果需要在標的平臺上執行指令碼,在 Buildroot 上有多種可用的選擇,包括 Python、PHP、Lua 以及基於 Node.js 的 JavaScript。對於需要使用 OpenSSL 加密的應用程式也提供支援。
接下來做什麼
Linux 內核和引導載入程式的編譯過程與大多數應用程式是一樣的。它們的構建系統被設計為去構建一個專用的軟體位。Crosstool-NG 和 Buildroot 是元構建。元構建是將一系列有自己構建系統的軟體集合封裝為一個構建系統。可靠的元構建包括 Yocto[7] 和 OpenEmbedded[8]。Buildroot 的好處是可以將更高階別的元構建進行輕鬆的封裝,以便於將定製 Linux 發行版的構建過程自動化。這樣做之後,將會開啟 Buildroot 指向到專案專用的快取倉庫的選項。使用快取倉庫可以加速開發過程,並且可以在無需擔心上游倉庫變化的情況下提供構建快照。
一個實現高階構建系統的示例是 PiBox[9]。PiBox 就是封裝了在本文中討論的各種工具的一個元構建。它的目的是圍繞所有工具去增加一個通用的 GNU Make 標的架構,以生成一個核心平臺,這個平臺可以構建或分發其它軟體。PiBox 媒體中心和 kiosk 專案是安裝在核心平臺之上的應用層軟體的實現,目的是用於去產生一個構建平臺。Iron Man 專案[10] 是為了家庭自動化的目的而擴充套件了這種應用程式,它集成了語音管理和物聯網裝置的管理。
但是,PiBox 如果沒有這些核心的軟體工具,它什麼也做不了。並且,如果不去深入瞭解一個完整的定製發行版的構建過程,那麼你將無法正確執行 PiBox。而且,如果沒有 PiBox 開發團隊對這個專案的長期奉獻,也就沒有 PiBox 專案,它完成了定製發行版構建中的大量任務。
via: http://www.linuxjournal.com/content/custom-embedded-linux-distributions
作者:Michael J.Hammel[12] 譯者:qhwdw 校對:wxy
本文由 LCTT 原創編譯,Linux中國 榮譽推出