Linux內核啟動流程我們分上下兩篇來理解:
上篇是板級引導階段,一般是用匯編實現。
下篇是通用內核啟動階段,一般是C語言實現。
本文先講解上篇,大家看到匯編不用擔心看不懂,在內核啟動階段,沒有特別復雜的流程,都是順序執行,只需一句一句閱讀代碼即可。
如何理解板級引導階段?Linux內核支持不同的芯片架構,比如我們熟知的ARM架構、Intel的X86架構、MPIS架構、RISC-V架構等等,可以在Linux內核源碼的arch目錄看到不同的芯片架構源碼放在對應的文件夾內。每種芯片架構的目錄下都包含boot、configs、kernel、lib、Kconfig等文件或目錄。
我們以ARM為例,源碼放在arch/arm目錄下,如下圖所示。
1、文件入口
ARM架構的Linux內核的鏈接腳本文件放在arch/arm/kernel/vmlinux.lds,鏈接腳本定義了整個內核編譯之后的鏈接過程,決定了一個可執行程序文件的入口和各個section的存儲位置。Line 21的OUTPUT_ARCH(arm)指示了該鏈接腳本針對的是ARM芯片架構,Line 22的ENTRY(stext)指明了內核入口為stext。因此,我們分析Linux內核啟動流程,就從stext入口分析。
2、函數入口
ENTRY(stext)是Linux內核的入口函數,該函數定義在arch/arm/kernel/head.S文件。
根據代碼注釋,stext是Kernel startup entry point,一般地,它從解壓代碼中獲取,這里解壓代碼是指按照Linux內核壓縮格式提取內核可執行文件,它的啟動要求如下:
- 關閉MMU
- 關閉D-cache
- 不關心I-cache
- R0=0
- R1=機器ID
- R2=atags或設備樹文件地址
3、調用safe_svcmode_maskall函數,確保安全進入SVC模式并屏蔽所有中斷
4、獲取處理器ID并找到對應的procinfo
主要實現函數是__lookup_processor_type。
Linux 內核將每種處理器都抽象為一個struct proc_info_list,因此可以通過process id 來找到對應的 proc_info 結構,__lookup_processor_type 函數在__lookup_processor_type_data中找到對應處理器的 proc info,將其保存到 R5 寄存器中。
5、調用函數__vet_atags 驗證 atags 或設備樹(dtb)的合法性
6、調用__create_page_tables創建頁表
7、準備啟動start_kernel
start_kernel函數是在__mmap_switched函數內調用的。
上圖的line 146指示將__mmap_switched的地址賦給R13,在line 158的__enable_mmu函數內的__turn_mmu_on函數內執行R13,成功進入start_kernel.
__INIT
__mmap_switched:
mov r7, r1
mov r8, r2
mov r10, r0
adr r4, __mmap_switched_data
mov fp, #0
#if defined(CONFIG_XIP_DEFLATED_DATA)
ARM( ldr sp, [r4], #4 )
THUMB( ldr sp, [r4] )
THUMB( add r4, #4 )
bl __inflate_kernel_data @ decompress .data to RAM
teq r0, #0
bne __error
#elif defined(CONFIG_XIP_KERNEL)
ARM( ldmia r4!, {r0, r1, r2, sp} )
THUMB( ldmia r4!, {r0, r1, r2, r3} )
THUMB( mov sp, r3 )
sub r2, r2, r1
bl memcpy @ copy .data to RAM
#endif
ARM( ldmia r4!, {r0, r1, sp} )
THUMB( ldmia r4!, {r0, r1, r3} )
THUMB( mov sp, r3 )
sub r2, r1, r0
mov r1, #0
bl memset @ clear .bss
ldmia r4, {r0, r1, r2, r3}
str r9, [r0] @ Save processor ID
str r7, [r1] @ Save machine type
str r8, [r2] @ Save atags pointer
cmp r3, #0
strne r10, [r3] @ Save control register values
mov lr, #0
b start_kernel
ENDPROC(__mmap_switched)