Try   HackMD

Linux 核心專題: 針對 Arm Cortex-M4 的移植和客製化

執行人: kk908676

任務簡述

將 Linux v6.14.y 移植到 STM32F429i-Discovery 平台並客製化相關的軟體組成,包含探討 Arm Cortex-M4 開機流程、記憶體佈局、中斷處理機制、輕量級 boot loader、Linux 核心組態、rootfs 建構,和降低佔用的空間等客製化。

待整理的筆記

應適度展開於本頁面

TODO: 闡述 Arm Cortex-M4 關鍵特性

至少涵蓋 Arm Cortex-M4 的開機流程、記憶體佈局 (含 XIP)、中斷處理等機制,以 STMF429i-Discovery 為例
參照: 嵌入式系統建構:開發運作於STM32的韌體程式
搭配閱讀下方網頁,以得知 Linux(-nommu) 如何移植到 Arm Cortex-M 處理器架構:

Arm Cortex-M 是款 32 位元微控制器架構,設計目標包括低功耗、高效能與即時反應,廣泛應用於物聯網裝置、工業控制、感測器節點與可穿戴裝置;支援 Thumb-2 混合指令長度設計、內建 NVIC (Nested Vectored Interrupt Controller) 、SysTick 定時器與可選用 MPU,並具備小封裝、低成本與較長的產品生命週期特性。

特性 Cortex-M Cortex-A
主要目標 低功耗、即時且可預測 高效能、豐富作業系統支援
深度睡眠 μA 等級 (極佳) mA 等級 (要考慮 SoC 整理功耗)
低活動 數百 μA 到若干 mA (如 Cortex-M0+) 數萬 mA (閒置/輕量負載)
高活動 數十 mA 到 ~100+ mA (如 Cortex-M7) 數百 mA 到若干 A (高度負載)
複雜度 簡單、較少晶片面積 較複雜、晶片面積更大,常整合 GPU
作業系統 裸機、RTOS、Linux-noMMU 完整的 Linux、Android、Windows 等

Linux-noMMU (過往稱為 uClinux 專案,針對 m68k 一類缺乏 MMU 的硬體) 是針對無 MMU 環境調整的 Linux 核心組態,其優勢包括:可重用龐大且成熟的驅動程式與應用生態,降低開發者學習成本;並藉由現有工具鏈、檔案系統與網路堆疊,快速建置複雜系統。此外,無 MMU 設計可簡化記憶體管理與啟動流程,適合資源有限但需整合 Linux 系統的專案。開發者需考量記憶體足跡、啟動時間、即時性需求與系統穩定性,並為行程隔離、中斷延遲與除錯提供替代機制,如利用 MPU、最佳化中斷優先權、採用精簡 rootfs 與串列除錯策略。

此外,單一定址空間設計消除頁表切換與快取資料不一致的風險,使行程切換與系統呼叫的延遲更可預測,對即時處理需求尤為有利;vfork 替代 fork 亦避免複製定址空間的高延遲開銷,有助於降低背景作業對即時性能的影響。在無 MMU 系統中,核心與應用共用同一實體定址空間,省去分頁表格管理、TLB 查詢與分頁異常處理,讓核心程式碼精簡、RAM 使用更有效率;應用程式直接以實體位址存取記憶體,除錯過程更直覺,跨行程共享記憶體也只需傳遞指標。所有記憶體存取皆繞過位址轉譯,消除因 TLB 未命中或分頁中斷帶來的非預期延遲,而行程切換時不必切換頁表或清除 TLB,也減少切換延遲,使中斷服務行為更可預測,對需要毫秒級甚至亞毫秒級反應的馬達控制或感測器擷取等嵌入式工作負載尤為關鍵。儘管此架構放棄 MMU 提供的行程隔離與記憶體保護,可能導致單一程式錯誤波及全域穩定性,但在 Cortex-M 這類 RAM/Flash 容量受限且追求極低功耗與高決定性的微控制器上,簡化的記憶體管理與更可預測的執行時間,往往遠超過虛擬記憶體帶來的好處;若硬體支援 MPU,則可對核心與特定行程或記憶區段給予存取權限,可彌補缺少 MMU 帶來的限制。

摘自〈Context Switching and IPC Performance Comparison between uClinux and Linux on the ARM9 based Processor〉對於 Linux + MMU (預設組態) 和 Linux no-MMU 在 ARM920T 硬體的測試表現。

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

基準測試結果顯示使用 LMbench 套件中的 lat_ctx 工具測得的情境切換延遲。評測涵蓋行程存取不同資料大小 (0 KB、1 KB、16 KB) 及不同並行的行程數量。在圖表或數據中,以實心點 (x) 表示具備 MMU 的標準 Linux 測量值,以空心圈 (o) 表示 Linux no-MMU 的測量值。

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

當資料大小設定為 0 KB 時,代表每個行程並未配置專屬的資料存取緩衝區,而僅靠暫存器與最基本的堆疊上下文切換即可完成。在具備 MMU 的標準 Linux 中,切換過程還包含頁表切換與 TLB 失效處理,而在無 MMU 的 Linux no-MMU 上,則省略這些步驟,使得在 0 KB 模式下的延遲更能反映最小化上下文保存與回復所需的時間。

對於追求毫秒級或更低切換延遲的即時系統,理解這種「無額外資料存取」的基準測試結果,有助於評估不同作業核心設計或排程策略對總體反應速度的影響。

所有行程建立機制(包括 fork, vfork, kthread_create)最終都會進入核心的 copy_process,由它根據參數為新行程分配所需資源。當使用 vfork 取代 fork 時,子代行程不會複製親代行程的記憶體定址空間,而是與親代行程共用同一實體定址空間,也就是說,子代行程對資料的任何修改,親代行程都能立即讀寫。為維護正確性,vfork 執行後會暫停親代行程,直至子代行程呼叫 exec 或自行結束。

採用 vfork 的優勢在於省去頁表複製與寫時複製機制(copy-on-write, CoW)造成的延遲與 CPU 開銷,對於緊接著要執行 exec 的流程,效能提升尤為明顯。但 vfork 僅限於「子代行程執行完畢後立即呼叫 exec」的場景,且因共用位址空間,一旦子代行程出錯,也可能影響親代行程。

POSIX 規範中另提供 posix_spawn 作為行程建立的替代方案,原為不支援 fork 的平臺設計。posix_spawn 等同於 fork 加上 exec,但可擴充參數較少、語意更簡潔,其執行時間不受親代行程記憶體大小影響,適合對行程建立延遲要求嚴格的場景。

呼叫 fork 後,核心會複製親代行程的 mm_struct、區域結構與頁表,並將所有分頁標為只讀,當任一行程嘗試寫入時才觸發寫時複製機制,將需要修改的頁面真正複製到新的實體記憶體;親代與子代行程因此共享初始全域變數、暫存器與堆疊內容,並各自擁有獨立的定址空間。相較之下,vfork 不會在啟動時執行任何頁表複製或私有化,必須等子代行程呼叫 exec 或 exit 後,親代行程才會解除阻塞並繼續執行。行程內的多執行緒則始終共用同一地址空間與全域資料。

在 Arm Cortex-M 平台上執行 Linux-noMMU 需應當考慮以下底層議題:

  1. 架構與指令集:Cortex-M 系列僅支援 Thumb‑2 指令集,未內建 MMU,必須以「單一實體定址空間」運作,並以 CONFIG_MMU=n 編譯核心。Thumb‑2 採用混合指令長度設計 (16 位元與 32 位元) ,其目標是提高指令編碼密度;需確保核心與裝置驅動均以 Thumb 模式編譯、連結,並在最小化指令集切換開銷與維持高效管線使用間取得平衡。
  2. 記憶體佈局與保護:由於晶片上 SRAM (數百 KB) 不足,通常需外接 SDRAM (容量以 16  MB 起跳) 與外部快閃記憶體裝置 (如 NOR/NAND Flash) 儲存核心映像與 rootfs (root filesystem);建置時需規劃:核心載入位置、核心映像 XIP 區段、行程堆疊 (stack) 與初始程式碼段 (.text/.data) 分區。若 MCU 具備 MPU,可透過硬體分區隔離核心與使用者空間,或對關鍵行程進行細緻地保護,以降低「一個行程當機即全體當機」之風險;若不具備 MPU,則無此機制。
  3. 中斷處理與上下文切換:Cortex-M 中斷使用 NVIC (Nested Vectored Interrupt Controller) ,需在核心中整合:中斷向量表重定向與優先權分組設定,並以 minimal context frame 保存暫存器 (R0–R12、LR、PC、xPSR)。移植時必須調整:ISR (Interrupt Service Routine) 與行程切換程式在 Privileged 與 Unprivileged 模式間的切換,以及 SysTick 定時器中斷與 PendSV 排程中斷的配合運作,以確保搶佔式多工與延遲上限符合預期且可預測
  4. 引導載入與裝置驅動程式支援:Bootloader (U-Boot 或簡易裸機程式) 須初始化時脈、SDRAM 控制器與序列埠,並將核心映像載入至預定位址;同時需設定 MPU 區段描述、重新定位中斷向量表。裝置驅動程式需針對具備相關功能的 Cortex-M MCU 特殊週邊 (如 UART、SPI、I2C;部分高階型號亦支援 SDIO 與 Ethernet MAC) 自行撰寫或修改,並在核心設定中精選驅動程式,降低映像檔的容量與記憶體佔用。
  5. 工具鏈與建置流程:採用支援 Thumb‑2 + noMMU 的交叉編譯器 (arm-none-eabi-gcc) 並配置 --specs=nosys.specs--specs=nano.specs 以縮減函式庫大小;運用 buildrootYocto 建構精簡的 rootfs (BusyBox + uClibc-ng/musl),搭配 initramfs 或外部 Flash (JFFS2/UBIFS)。BSP 應包含 Cortex-M 特定組態 (啟用 CONFIG_ARM_THUMB 選項、設定 NVIC、驅動 MPU),以加速移植並確保與 Linux 上游開發相容。

TODO: 針對 Linux no-MMU 的執行檔格式調整

參照 uClinux flat file formatMMU-less systems and FDPIC,探討 Flat File Format 和 FDPIC 等議題,應涵蓋動態連結的處理機制。
注意: 若干網頁的資訊已過時,斟酌閱讀並謹慎求證

在無 Linux no-MMU 環境裡,傳統 ELF 可執行檔(Executable and Linkable Format)面臨虛擬位址與實體位址映射缺失的挑戰:ELF 的程式區段(.text, .data, .bss)通常依賴 p_vaddr 欄位指定的虛擬定址,再由 MMU 於載入時建立對應到實體記憶體的映射,缺少這層映射便無法直接將 ELF 匯入至任意記憶體位置執行。雖然 ELF 本身並不強制要有虛擬記憶,但現有的載入器(loader)與核心 binfmt_elf 模組都假設底層具備 MMU 支援,因此在無 MMU 架構下只能倚靠額外機制才能運作。

對此,Linux no-MMU 系統多採用 bFLT (Binary Flat Format) 作為可執行檔格式。bFLT 以扁平化記憶體模型為主,亦即所有區段既無虛擬定址也無分頁映射,而是由核心載入器根據檔頭 (flat_hdr) 中的 p_mem_start, p_mem_end, p_bss_end 與堆疊大小等欄位,在一塊連續的實體 RAM 範圍中依序佈置 .text, .data.bss,再依靠重定位條目 (reloc entries) 修正程式碼或資料裡的絕對位址。elf2flt 工具會自動解析 ELF 檔案,提取這些必要欄位並生成相應的重定位表,使得在載入階段,只要把 bFLT 標頭、程式區段與重定位列表逐項讀入並依序處理,就能在任意實體位址執行原先以 ELF 編譯的程式。

在 Linux 核心原始程式碼,bFLT 載入器位於 fs/binfmt_flat.c,部分廠商也為 bFLT 增補對位置獨立程式碼 (PIC) 與共享函式庫的支援。若要在 Linux no-MMU 直接使用 ELF,可改採編譯器與連結器支援的簡易 flat-ELF 方案:將所有 PT_LOAD 程式標頭 (program header) 的 p_paddr 欄位設定為實體定址,並在核心以相同邏輯載入,省去 bFLT 格式轉換。

bFLT 的實務操作流程為:核心讀取 flat_hdr 標頭、建立夠大的連續 RAM 區塊,將 .text 與已初始化的 .data 複製到該區,再將 .bss 全部歸零,接著依序走訪重定位條目,將程式內所有絕對位址修正至實際載入基址,並配置行程堆疊。最後核心將程式參數 (argc, argv) 及環境變數放到堆疊上,跳至 header 中的入口函式地址開始執行。由於此流程不涉及 MMU,適合 RAM/Flash 容量有限、卻仍需多行程支援的 Cortex-M 或其他缺乏 MMU 的微控制器。

至於如何產生 bFLT,常見做法是先用交叉編譯器(如 arm-none-eabi-gcc)搭配 -fPIE-fPIC 產生位置獨立的 ELF,再經由 elf2flt 工具轉換。buildroot 和 Yocto 已整合 elf2flt,可在連結階段藉由 -Wl,-elf2flt 旗標自動指定,直接輸出 bFLT 而無需手動執行。

TODO: 客製化 buildroot

使用 buildroot 2025.02 或之後的版本,建構 Linux 核心和 rootfs 映像檔

TODO: 將 u-boot 換為 afboot-stm32

好處是精簡、可追蹤開機流程,可斟酌 fork
搭配「闡述 Arm Cortex-M4 關鍵特性」,解說在 STM32F4 如何初始化硬體並載入 Linux 核心

TODO: 使用 SD card reader 並調整相關核心設定

確定 Linux 核心可從 SD 卡讀取並正確啟動,包含 rootfs 也比照處理

TODO: 運用 LTO 縮減核心映像檔空間

參照 Shrinking the kernel with link-time optimization

TODO: 客製化 Linux 核心

開啟 fbdev, touchscreen, rng 等裝置,見 AdrianHuang/uclinux-robutest
整合 Linux Tiny Patches for Linux 6.9.1
OpenWrt 的修改