Windows XP Windows 7 Windows 2003 Windows Vista Windows 綜合教學 Linux 系統教學
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟體教學
您现在的位置: Windows教程網 >> Windows教程 >  >> Linux 系統教學 >> Linux 教學

ARM Linux 啟動流程深度剖析:從連結腳本到 start_kernel

編輯:Linux 教學

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 映射)。

二、組合語言入口點 (head-armv.S)


.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 設定返回位址

四、MMU 啟用與虛擬位址切換


__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

此後所有程式碼以虛擬位址執行。

五、進入 C 語言世界:start_kernel()


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 迴圈
}

六、rest_init():誕生第一個使用者空間進程


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.infoMACHINE_START/END機器描述結構體
.initcall.init__initcall(fn)驅動初始化函數指標陣列

連結器自動收集所有 __initcall 形成陣列,do_initcalls() 遍歷執行,實現驅動自動初始化。

Copyright © Windows教程網 All Rights Reserved