嵌入式 Linux 系統開發涵蓋多個關鍵環節,從 Bootloader 初始化硬體到 Linux 核心接管系統控制,再到使用者空間應用程式的執行,每個階段都至關重要。本文將探討這些核心元件,並提供實用的建構。Bootloader 負責初始化硬體並載入核心映像,U-Boot 作為常用的 Bootloader,其小型化、快速啟動、可移植性、可組態性和可除錯性是其主要優勢。Linux 核心版本管理、下載與編譯流程也是建構系統的基礎,瞭解不同版本型別以及如何透過命令列操作至關重要。系統呼叫作為應用程式與核心的橋樑,透過 C 執行期函式庫提供各種功能,glibc 作為常用的 C 函式庫,提供了豐富的系統功能。系統分享函式庫則封裝了特定的系統功能,簡化了應用程式開發。根檔案系統的結構和 FHS 標準確保了系統的穩定性和相容性。最後,文章以 Raspberry Pi 為例,說明瞭如何組態和建構嵌入式 Linux 系統,包括啟用硬體周邊裝置和使用建構框架等實用技巧。
建構嵌入式Linux系統
Linux核心是目前為止最大且最成功的開源專案之一,其龐大的變動率和眾多的個人貢獻者顯示出它擁有一個活躍且充滿活力的社群,不斷推動著核心的演進。這種變動率持續增加,參與的開發者和公司數量也在不斷增長。開發過程已經證明它能夠在更高的速度下無故障執行。
Linux核心與GNU軟體以及其他許多開源元件共同提供了一個完全免費的作業系統,稱為GNU/Linux。嵌入式Linux則是在嵌入式系統中使用Linux核心和各種開源元件。
嵌入式Linux被廣泛應用於各種嵌入式系統中,例如消費電子產品(如機頂盒、智慧電視、個人錄影機、車載資訊娛樂系統)、網路裝置(如路由器、交換器、無線接入點或無線路由器)、機器控制、工業自動化、導航裝置、太空飛行軟體以及醫療儀器等。
在嵌入式系統中使用Linux有許多優點,以下是其中一些好處:
- Linux的主要優勢在於能夠重複使用元件。Linux由於其模組化和可組態性而具有可擴充套件性。
- 開源:無需支付版稅或授權費用。
- 已移植到廣泛的硬體架構、平台和裝置上。
- 廣泛支援各種應用程式和通訊協定(例如TCP/IP堆積疊、USB堆積疊、圖形工具包函式庫)。
- 來自活躍的開發者社群的龐大支援。
嵌入式Linux系統的主要元件包括Bootloader、核心、系統呼叫介面、C執行期函式庫、系統分享函式庫和根檔案系統。下面將對這些元件進行更詳細的介紹。
Bootloader
在嵌入式裝置上啟動Linux需要一小段機器特定的程式碼來初始化系統。Linux要求Bootloader程式碼完成很少的工作,儘管許多Bootloader提供了廣泛的額外功能。最低需求包括:
- 組態記憶體系統。
- 將核心映像和裝置樹載入到正確的位址。
- 可選地將初始RAM磁碟載入到正確的記憶體位址。
- 設定核心命令列和其他引數(例如裝置樹、機器型別)。
通常,Bootloader除了這些基本任務外,還會初始化一個串列控制檯供核心使用。
有多種Bootloader選項,各種大小和形狀都有。U-Boot是ARM Linux的標準Bootloader。U-Boot原始碼位於https://source.denx.de/u-boot/u-boot。
以下是U-Boot的一些主要功能:
- 小型化:U-Boot是一個Bootloader,其在系統中的主要目的是載入作業系統。這意味著U-Boot是執行特定任務所必需的,但不值得耗費大量資源。通常,U-Boot儲存在相對較小的NOR快閃記憶體中,與通常用於儲存作業系統和應用程式的大型NAND裝置相比,NOR快閃記憶體較為昂貴。一個可用且有用的U-Boot組態,包括基本的互動式命令直譯器、支援透過乙太網路下載以及對快閃記憶體的程式設計能力,應該能夠在不超過128 KB的空間內實作。
內容解密:
此段落描述了U-Boot的小型化設計理念,重點在於其作為一個載入作業系統的工具,需要精簡資源佔用。提到了NOR快閃記憶體與NAND裝置的比較,以及U-Boot的基本功能需求。
- 快速啟動:終端使用者對執行U-Boot不感興趣,在大多數嵌入式系統中,他們甚至不知道U-Boot的存在。使用者希望執行某些應用程式碼,並希望在開啟裝置後盡快執行。僅在U-Boot內需要時才初始化裝置,例如,不初始化乙太網路介面,除非U-Boot透過乙太網路執行下載;不初始化任何IDE或USB裝置,除非U-Boot實際嘗試從這些裝置載入檔案等。
內容解密:
這段描述了U-Boot快速啟動的設計原則,強調了僅在必要時初始化裝置,以加快啟動速度和提高效率。
- 可移植性:U-Boot是一個Bootloader,但它也是一個用於板級啟動、生產測試和其他與硬體開發密切相關的活動的工具。到目前為止,它已經被移植到大約30個不同的處理器家族上的數百個不同的板子上。
內容解密:
此段落闡述了U-Boot具有高度的可移植性,能夠在多種不同的硬體平台上執行,這使得它成為一個非常有用的工具。
- 可組態性:U-Boot是一個功能強大的工具,具有許多極其有用的功能。每個板的維護者或使用者將不得不決定哪些功能對於他們特定的板級組態是重要的,以及為了滿足當前的需求和限制應該包含哪些功能。
內容解密:
這裡強調了U-Boot的可組態性,允許使用者根據自己的需求選擇所需的功能,以適應不同的硬體組態和需求。
- 可除錯性:U-Boot不僅是一個工具,它經常也被用於硬體啟動,因此除錯U-Boot往往意味著你不知道自己是在追蹤U-Boot軟體中的問題還是在執行的硬體中的問題。乾淨且易於理解和除錯的程式碼對每個人來說都非常重要。U-Boot的一個重要功能是盡可能早地在啟動過程中啟用輸出到(通常是串列)控制檯,即使這會在其他方面(如記憶體佔用)造成權衡。所有初始化步驟都應在實際開始之前列印一些“開始執行此操作”的訊息,並在完成時列印一些“完成”的訊息。這樣做的目的是,您總是可以看到發生問題時正在執行的初始化步驟。這不僅在軟體開發期間很重要,而且對於現場處理故障硬體的維修人員也很重要。U-Boot應該可以使用簡單的JTAG或BDM裝置進行除錯。它應該使用簡單的單執行緒執行模型。
內容解密:
本段描述了U-Boot的可除錯性設計,包括盡早啟用控制檯輸出、在初始化步驟中提供明確的進度訊息,以及使用簡單的執行模型,以方便除錯工作。
Linux 核心系統建構
Linux 核心是整個 Linux 系統的核心,負責管理硬體、執行使用者程式以及維護系統的整體安全性和完整性。由 Linus Torvalds 在 1991 年首次發布後,Linux 核心的發展便成為了一個龐大的合作軟體專案。定期發布的穩定更新版本為 Linux 使用者帶來了顯著的新功能、裝置支援以及效能改進。
Linux 核心的主要特性與架構
Linux 核心具備現代 Unix 實作的所有特性,包括真正的多工處理、虛擬記憶體、分享程式函式庫、需求載入、分享的寫入時複製(copy-on-write)可執行檔、適當的記憶體管理和多層網路功能(包括 IPv4 和 IPv6)。最初為 32 位元 x86-based PC(386 或更高版本)開發,如今 Linux 也能在多種其他處理器架構上執行,包括 32 位元和 64 位元版本。
核心子系統
Linux 核心由多個子系統組成,主要包括:
/arch/<arch>:與架構相關的程式碼。/ipc:行程間通訊。/mm:記憶體管理。/fs:檔案系統。/include:Linux 核心標頭檔。/init:Linux 初始化(包括 main.c)。/block:Linux 核心區塊層程式碼。/net:網路功能。/lib:核心常用的輔助函式。/kernel:核心通用結構。/drivers:內建驅動程式(不包含可載入模組)。
Linux 核心的版本管理
Linux 核心有多種版本類別,包括:
Prepatch(預發布版本):主要針對其他核心開發者和 Linux 愛好者。它們需要從原始碼編譯,通常包含需要在穩定版本中測試的新功能。這些版本由 Linus Torvalds 維護和發布。
Mainline(主線版本):由 Linus Torvalds 維護,所有新功能都在此樹中引入,大約每 2-3 個月發布一次新版本。
Stable(穩定版本):在每個主線版本發布後被視為穩定版本。任何對穩定版本的錯誤修復都會從主線樹中反向移植並由指定的穩定核心維護者應用。
Longterm(長期維護版本):通常會有多個長期維護的核心版本,用於對較舊的核心樹進行錯誤修復的反向移植。這些版本只會應用重要的錯誤修復,發布頻率較低。
如何判斷您正在執行的核心版本
要了解您正在執行的核心版本,可以在啟動處理器後執行 uname -r 命令。如果您沒有從 www.kernel.org 下載、編譯並安裝自己的核心版本,那麼您很可能正在執行一個發行版核心。
下載與編譯 Linux 核心
Linux 核心的官方原始碼託管在 www.kernel.org。您可以直接從 kernel.org 網站下載壓縮的 tar.xz 檔案,或是透過 git(核心的首選原始碼控制系統)下載。
程式碼下載範例
# 從 kernel.org 下載最新的穩定版核心原始碼
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.x.x.tar.xz
# 解壓縮原始碼
tar -xvf linux-5.x.x.tar.xz
# 切換到原始碼目錄
cd linux-5.x.x
編譯核心範例
# 組態核心編譯選項
make menuconfig
# 編譯核心
make -j$(nproc)
# 安裝模組
sudo make modules_install
# 安裝核心
sudo make install
內容解密:
wget命令用於從 kernel.org 下載最新的穩定版核心原始碼壓縮檔。tar -xvf命令用於解壓縮下載的原始碼壓縮檔。cd命令用於切換到解壓縮後的原始碼目錄。make menuconfig命令用於組態核心的編譯選項,這是一個互動式的選單,可以選擇需要的功能和模組。make -j$(nproc)命令用於編譯核心,其中-j$(nproc)表示根據 CPU 核心數自動決定平行編譯的任務數,以加快編譯速度。sudo make modules_install命令用於安裝編譯好的核心模組到系統中。sudo make install命令用於安裝新的核心到系統中。
建構系統
在本文中,您將使用長期支援的 Linux 核心 5.4.y 版本來開發各種驅動程式。
系統呼叫介面與 C 執行期函式庫
系統呼叫(System Call)是應用程式與 Linux 核心之間的基本介面。系統呼叫是使用者空間應用程式與核心互動的唯一方式,是使用者空間與核心空間之間的橋樑。嚴格區分使用者空間與核心空間可確保使用者空間程式無法自由存取核心內部資源,從而保證系統的安全性和穩定性。系統呼叫提升了使用者程式的許可權。
系統呼叫介面通常不是直接被呼叫(儘管可以),而是透過 C 執行期函式庫中的包裝函式(Wrapper Function)來呼叫。這些包裝函式有的只是對系統呼叫的薄層封裝(僅檢查和設定呼叫引數),而其他的則增加了額外的功能。下圖展示了一些系統呼叫及其描述:
C 執行期函式庫(C-Standard Library)定義了巨集、型別定義和函式,用於字串處理、數學函式、輸入/輸出處理、記憶體分配等多種依賴作業系統服務的功能。執行期函式庫透過抽象化作業系統的系統呼叫介面,為應用程式提供了對作業系統資源和功能的存取。
有多個 C 執行期函式庫可供選擇,如 glibc、uClibc、eglibc、dietlibc 和 newlib。在建立交叉編譯工具鏈時必須選擇 C 函式庫,因為 GCC 編譯器是針對特定的 C 函式庫進行編譯的。
GNU C 函式庫(glibc)是預設的 C 函式庫,例如在 Yocto 專案中使用。glibc 主要設計為可移植且高效能的 C 函式庫,遵循包括 ISO C11 和 POSIX.1-2008 在內的所有相關標準。它還具有國際化功能,是已知最完整的國際化介面之一。您可以在 https://www.gnu.org/software/libc/manual/ 找到 glibc 的手冊。
系統分享函式庫
系統分享函式庫是在程式啟動時被載入的函式庫。當分享函式庫正確安裝後,之後啟動的所有程式都會自動使用新的分享函式庫。系統分享函式庫通常與使用者空間應用程式連結,以提供對特定系統功能的存取。這種系統功能可以是自包含的,如壓縮或加密演算法,也可以需要存取底層核心資源或硬體。在後一種情況下,函式庫提供了一個簡單的 API,以抽象化核心或直接驅動程式存取的複雜性。
每個分享函式庫都有一個特殊的名稱,稱為「soname」。soname 的字首為「lib」,後面跟著函式庫的名稱、短語「.so」,然後是一個句點和一個版本號,每當介面變更時,版本號就會遞增(作為一個特殊的例外,最底層的 C 函式庫不以「lib」開頭)。完全合格的 soname 包括其所在的目錄作為字首。在一個運作中的系統中,完全合格的 soname 只是一個指向分享函式庫「真實名稱」的符號連結。
每個分享函式庫還有一個「真實名稱」,也就是包含實際函式庫程式碼的檔案名稱。真實名稱在 soname 後面新增一個句點、一個次要編號、另一個句點和發行編號。最後一個句點和發行編號是可選的。次要編號和發行編號透過讓您準確知道安裝了哪些版本的函式庫來支援組態控制。請注意,這些編號可能與檔案中用於描述函式庫的編號不同,儘管這確實使事情變得更容易。
此外,還有編譯器在請求函式庫時使用的名稱(稱之為「連結器名稱」),它只是沒有任何版本編號的 soname。
LSB(Linux Standard Base)規範要求以下系統分享函式庫必須在所有符合 LSB 規範的系統上可用:
- Libc:標準 C 函式庫(C 執行期函式庫)。基本的語言支援和作業系統平台服務,直接存取作業系統系統呼叫介面。
- Libm:數學函式庫。由 System V、ANSI C、POSIX 等定義的常見基本數學函式和浮點數環境常式。
- Libpthread:POSIX 執行緒函式庫。現在的功能在 libc 中,為了提供向後相容性而維護。
- Libdl:動態連結器函式庫。現在的功能在 libc 中,為了提供向後相容性而維護。
- Libcrypt:密碼學函式庫。加密和解密處理常式。
- Libpam:PAM(可插拔身份驗證模組)函式庫。PAM 的常式。
- Libz:壓縮/解壓縮函式庫。一般用途的資料壓縮和解壓縮功能。
- Libncurses:CRT 螢幕處理和最佳化套件。整體螢幕、視窗和緩衝區操作;輸出到視窗和緩衝區;讀取終端輸入;控制終端和遊標輸入輸出選項;環境查詢常式;色彩操作;使用軟標籤鍵。
- Libutil:系統效用函式庫。在各種系統守護程式中使用的各種與系統相關的效用常式。抽象化的功能大多與偽終端和登入會計相關。
這些函式庫被放置在以下標準的根檔案系統位置:
- /lib:啟動所需的函式庫。
- /usr/lib:大多數系統函式庫。
- /usr/local/lib:非系統函式庫。
根檔案系統
根檔案系統是儲存檔案層次結構中所有檔案(包括裝置節點)的地方。根檔案系統被掛載為 /,包含所有函式庫、應用程式和資料。
根檔案系統的資料夾結構由 FHS(檔案系統層次結構標準)定義。FHS 定義了許多檔案型別和目錄的名稱、位置和許可權,從而確保不同 Linux 發行版之間的相容性,並允許應用程式對特定系統檔案和組態的位置做出假設。
嵌入式 Linux 根檔案系統通常包括以下內容:
- /bin:啟動過程中需要的命令,可能會被普通使用者使用(可能在啟動後)。#### 內容解密: 本章節主要介紹了建構 Linux 系統的基本元件,包括系統呼叫介面、C 執行期函式庫、系統分享函式庫以及根檔案系統。其中,C 執行期函式庫為應用程式提供了對作業系統資源和功能的存取,而系統分享函式庫則封裝了特定的系統功能,為應用程式提供了簡單的 API 以存取這些功能。根檔案系統則是儲存檔案層次結構中所有檔案的地方,其資料夾結構由 FHS 定義,以確保不同 Linux 發行版之間的相容性。
程式碼範例:
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
內容解密:
這段程式碼是一個簡單的 C 程式,使用 printf 函式輸出 “Hello, World!\n”。printf 是 C 標準函式庫中的一個函式,用於格式化輸出。在這個例子中,它被用來輸出一個簡單的字串。這個程式展示瞭如何使用 C 執行期函式庫中的函式來與作業系統互動。
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam rectangleBackgroundColor #F5F5F5
skinparam rectangleBorderColor #333333
skinparam arrowColor #333333
title 內容解密:
rectangle "系統呼叫" as node1
rectangle "C 執行期函式庫" as node2
rectangle "存取" as node3
node1 --> node2
node2 --> node3
@enduml此圖示展示了應用程式如何透過系統呼叫與 Linux 核心互動,以及如何透過 C 執行期函式庫和系統分享函式庫來存取核心資源。
內容解密:
此圖示闡述了應用程式與 Linux 核心之間的互動關係,以及 C 執行期函式庫和系統分享函式庫在其中扮演的角色。應用程式透過系統呼叫與核心互動,同時也可以透過 C 執行期函式庫間接存取核心資源,或者透過系統分享函式庫來存取特定的系統功能。這種架構保證了系統的安全性和穩定性,同時也為應用程式提供了豐富的功能和資源。
建構系統章節1:深入理解Linux檔案系統與啟動流程
Linux系統的檔案系統結構是其高效運作的基礎。瞭解不同目錄的作用對於系統管理和開發至關重要。
Linux檔案系統結構解析
Linux的檔案系統採用階層式結構,從根目錄(/)開始,各個子目錄承擔不同的功能:
- /bin:存放系統必備的執行檔,如shell、基本命令等。
- /sbin:包含系統管理的執行檔,通常只有root可以執行。
- /etc:存放系統設定檔,包括網路設定、使用者帳號等重要資訊。
- /home:使用者個人目錄,相當於Windows的「我的檔案」。
- /root:root使用者的家目錄,通常不對其他使用者開放。
- /lib:存放系統必要的函式庫和核心模組。
- /dev:裝置檔案,用於與系統中的硬體裝置互動。
- /tmp:暫存檔案目錄,系統和程式會在此存放臨時資料。
- /boot:開機所需檔案,包括核心映像檔等。
- /mnt:暫時掛載檔案系統的掛載點。
- /opt:額外安裝的應用程式套件存放處。
- /usr:次要的檔案系統階層,包含額外的程式和資料。
- /var:可變動的資料,如日誌檔、快取等。
- /sys 和 /proc:虛擬檔案系統,分別用於輸出裝置和驅動程式資訊,以及代表核心的目前狀態。
Linux啟動流程詳解
以BCM2837 SoC為例,Linux的啟動流程包括以下主要階段:
- 硬體初始化:當電源開啟或硬體重置時,VideoCore GPU啟動,並執行片上啟動ROM中的第一階段啟動程式。此時,DRAM記憶體控制器被停用。
- 第二階段啟動程式載入:第一階段啟動程式從SD卡讀取第二階段啟動程式(bootcode.bin),載入到GPU的L2快取並執行。第二階段啟動程式設定DRAM記憶體控制器,然後載入GPU韌體(start.elf)並執行。
- 核心映像檔載入:GPU韌體讀取系統設定引數(config.txt),載入核心映像檔(kernel7.img)和裝置樹檔案(bcm2710-rpi-3-b.dtb)到DRAM,並釋放ARM CPU的重置訊號。
- 核心初始化:核心映像檔在ARM CPU上執行,進行低階核心初始化,包括啟用MMU、建立初始記憶體分頁表和設定快取。然後,系統切換到非架構特定的核心啟動函式start_kernel()。
- start_kernel()執行:該函式負責初始化核心核心元件、靜態編譯的驅動程式,掛載根檔案系統,並執行第一個使用者程式init。
為Raspberry Pi建構嵌入式Linux系統
建構嵌入式Linux系統需要經過以下步驟:
選擇交叉工具鏈:工具鏈是開發過程的起點,用於建構所有後續的軟體套件。交叉編譯器是一種能夠在不同平台上產生可執行程式碼的編譯器。
選擇軟體套件:根據需求選擇合適的bootloader、核心和根檔案系統。
組態和建構軟體套件:根據特定硬體和需求組態所選的軟體套件,並進行建構。
佈署到裝置:將建構好的軟體套件佈署到目標裝置上。
建構嵌入式Linux系統有多種方法,包括手動建構、使用完整的發行版(如Ubuntu/Debian)、以及使用建構框架(如Buildroot、Yocto)。使用建構框架可以簡化自定義和重複建構的過程。
使用Raspberry Pi OS
Raspberry Pi OS是根據Debian的最佳化作業系統,適用於Raspberry Pi硬體。它包含了超過35,000個預編譯的軟體套件。安裝Raspberry Pi OS需要下載對應的核心版本映像檔,並使用工具如Etcher寫入Micro SD卡。安裝完成後,可以透過修改config.txt檔案或使用Raspberry Pi Configuration應用程式來啟用所需的周邊裝置,如UART、SPI和I2C。
實作步驟範例
# 檢視已掛載的磁碟裝置
~$ lsblk
# 建立掛載點
~$ mkdir ~/mnt
~$ mkdir ~/mnt/fat32
~$ mkdir ~/mnt/ext4
# 掛載Micro SD卡的第一分割區(FAT32)到~/mnt/fat32
~$ sudo mount /dev/mmcblk0p1 ~/mnt/fat32
# 檢視FAT32分割區中的檔案,確認config.txt存在
~$ ls -l ~/mnt/fat32/
# 編輯config.txt以啟用所需的外設
~$ cd ~/mnt/fat32/
~/mnt/fat32$ sudo nano config.txt
# 新增以下設定
dtparam=i2c_arm=on
dtparam=spi=on
dtoverlay=spi0-cs
# Enable UART
enable_uart=1
kernel=kernel7.img
# 解除安裝FAT32分割區
~$ sudo umount ~/mnt/fat32
內容解密:
此範例展示瞭如何掛載Micro SD卡的FAT32分割區,編輯config.txt以啟用I2C、SPI和UART等外設,並正確解除安裝分割區。這些步驟對於組態Raspberry Pi OS至關重要,能夠確保系統正確識別和使用連線的外設。
綜上所述,建構嵌入式Linux系統需要深入瞭解Linux檔案系統結構、啟動流程,以及如何根據特定硬體需求進行組態和最佳化。透過選擇適當的工具鏈、軟體套件和建構方法,可以高效地建立一個穩定且功能豐富的嵌入式Linux系統。