ARM Linux 核心啟動是嵌入式工程師必須掌握的核心知識。本文從連結腳本 vmlinux.lds 入手,追蹤 head-armv.S 組合語言入口點、處理器型號偵測、頁表建立、MMU 啟用、虛擬位址轉換,最終進入 C 語言 start_kernel() 的完整流程,並附上流程圖解析。
ENTRY(stext) // 入口點符號
SECTIONS {
. = 0xC0008000; // 核心虛擬基址
.init : { // 初始化段
_stext = .;
*(.text.init)
*(.proc.info) // 處理器資訊段
*(.arch.info) // 架構資訊段
*(.taglist)
*(.data.init)
*(.setup.init)
*(.initcall.init) // 模組初始化呼叫陣列
}
}
虛擬位址 0xC0008000 對應物理位址 0x30008000(透過 MMU 映射)。
.section ".text.init",#alloc,#execinstr
.type stext, #function
ENTRY(stext)
mov r12, r0 // 保存 r0 (通常為 ATAGS 指標)
mov r0, #F_BIT | I_BIT | MODE_SVC
msr cpsr_c, r0 // 切換到 SVC 模式,關閉中斷
bl __lookup_processor_type // 偵測處理器型號
teq r10, #0
moveq r0, #'p'
beq __error
bl __lookup_architecture_type // 偵測架構型號
teq r7, #0
moveq r0, #'a'
beq __error
bl __create_page_tables // 建立初始頁表
adr lr, __ret
add pc, r10, #12 // 跳轉至處理器特定初始化
__lookup_processor_type 從 .proc.info 區段取得處理器資訊結構體陣列r10 最终指向 __proc_info_begin(物理位址)add pc, r10, #12 跳過結構體前 12 bytes(三個 long),落在 b __arm920_setup 指令adr lr, __ret 設定返回位址
__ret:
ldr lr, __switch_data
mcr p15, 0, r0, c1, c0 // 啟用 MMU(關鍵指令)
mov r0, r0 // 指令流水線刷新 (NOP x3)
mov r0, r0
mov r0, r0
mov pc, lr // 跳轉至 __mmap_switched (虛擬位址)
__switch_data:
.long __mmap_switched
此後所有程式碼以虛擬位址執行。
b SYMBOL_NAME(start_kernel) // kernel/init/main.c
asmlinkage void __init start_kernel(void) {
lock_kernel();
printk(linux_banner);
setup_arch(&command_line);
parse_options(command_line);
trap_init();
init_IRQ();
sched_init();
softirq_init();
time_init();
// ... 大量子系統初始化
rest_init(); // 建立 init 進程,進入 idle 迴圈
}
static void __init rest_init(void) {
kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);
unlock_kernel();
current->need_resched = 1;
cpu_idle();
}
kernel_thread 建立 PID=1 的 init 進程init 執行 do_basic_setup() → do_initcalls() 掃描 .initcall.init 區段,依序呼叫所有 module_init() 註冊的驅動初始化函數init 執行 /sbin/init 或 /linuxrc → /etc/init.d/rcS → /usr/etc/rc.local| 區段 | 巨集 | 用途 |
|---|---|---|
.proc.info | __PROC_INFO | 處理器資訊結構體 |
.arch.info | MACHINE_START/END | 機器描述結構體 |
.initcall.init | __initcall(fn) | 驅動初始化函數指標陣列 |
連結器自動收集所有 __initcall 形成陣列,do_initcalls() 遍歷執行,實現驅動自動初始化。