linux核心啟動過程——基於S3C2410 (1)zImage自解壓 本文以流行的Samsung公司的S3C2410,mini2440平台和linux-2.6.29為例,介紹如何在ZIX嵌入式開發環境下探索linux核心啟動過程。 Linux核心啟動一般由外部的bootloader引導,也可以在核心頭部嵌入一個loader,實際的應用中這兩種方式都會經常遇到。所以要瞭解核心啟動最開始的過程,必須對bootloader如何引導核心有所熟悉。下面我們從u-boot載入linux核心的代碼開始分析(關於u-boot 自身的啟動流程,請參考u-boot 啟動過程 —— 基於S3C2410)。 1.處理器核心載入代碼在u-boot的do_bootm_linux函數裡,實現了處理器架構相關的linux核心載入代碼,特別是tags傳遞。 該函數中,在(u-boot-1.6)lib_arm/armlinux.c的90行調用了getenv將bootargs環境變數儲存在commandline char *commandline = getenv ("bootargs"); 然後解析uImage檔案頭,並且按照頭中的定義分解和載入uImage。所以這部分代碼的運行取決於uImage檔案是如何產生的,本文不做過多敘述,可參考另文瞭解u-boot使用。接下來進行tags設定工作,分別調用了
- setup_start_tag()
- setup_memory_tag()
- setup_commandline_tag()
- setup_initrd_tag()
- setup_end_tag()
然後對TLB、cache等進行invalid操作,這是通過在lib_arm/armlinux.c的268行調用cleanup_before_linux()(cpu/arm920t/108)實現,然後即可跳入從uImage中分解出來的核心Image或zImage入口 cleanup_before_linux (); theKernel (0, machid, bd->bi_boot_params); /* does not return */ return; 在s3c2410平台上,該入口theKernel一般是物理地址0x30008000。如果我們使用zImage自解壓核心映像,對應的代碼正是自解壓頭,位置在核心源碼linux-2.6.29的arch/arm/boot/compressed/head.S第 114行的start符號 start: .type start,#function .rept 8 mov r0, r0 .endr b 1f .word 0x016f2818 @ Magic numbers to help the loader .word start @ absolute load/run zImage address .word _edata @ zImage end address 1: mov r7, r1 @ save architecture ID mov r8, r2 @ save atags pointer 這也標誌著u-boot將系統完全的交給了OS,bootloader生命終止。之後代碼在133行會讀取cpsr並判斷是否處理器處於supervisor模式——從u-boot進入kernel,系統已經處於SVC32模式;而利用angel進入則處於user模式,還需要額外兩條指令。之後是再次確認中斷關閉,並完成cpsr寫入 mrs r2, cpsr @ get current mode tst r2, #3 @ not user? bne not_angel mov r0, #0x17 @ angel_SWIreason_EnterSVC swi 0x123456 @ angel_SWI_ARM not_angel: mrs r2, cpsr @ turn off interrupts to orr r2, r2, #0xc0 @ prevent angel from running msr cpsr_c, r2 然後在LC0地址(157行)處將分段資訊匯入r0-r6、ip、sp等寄存器,並檢查代碼是否運行在與連結時相同的目標地址(162行),以決定是否進行處理。由於現在很少有人不使用loader和tags,將zImage燒寫到rom直接從0x0位置執行,所以這個處理是必須的(但是zImage的頭現在也保留了不用loader也可開機能力)。arm架構下自解壓頭一般是連結在0x0地址而被載入到0x30008000運行,所以要修正這個變化。涉及到
- r5寄存器存放的zImage基地址
- r6和r12(即ip寄存器)存放的got(global offset table)
- r2和r3存放的bss段起止地址
- sp棧指標地址
很簡單,這些寄存器統統被加上一個你也能猜到的位移地址 0x30008000。該地址是s3c2410相關的,其他的ARM處理器可以參考下表
- PXA2xx是0xa0008000
- IXP2x00和IXP4xx是0x00008000
- Freescale i.MX31/37是0x80008000
- TI davinci DM64xx是0x80008000
- TI omap系列是0x80008000
- AT91RM/SAM92xx系列是0x20008000
- Cirrus EP93xx是0x00008000
這些操作發生在代碼172行開始的地方,下面只粘貼一部分 add r5, r5, r0 add r6, r6, r0 add ip, ip, r0 後面在211行進行bss段的清零工作 not_relocated: mov r0, #0 1: str r0, [r2], #4 @ clear bss str r0, [r2], #4 str r0, [r2], #4 str r0, [r2], #4 cmp r2, r3 blo 1b 然後224行,開啟cache,並為後面解壓縮設定64KB的臨時malloc空間 bl cache_on mov r1, sp @ malloc space above stack add r2, sp, #0x10000 @ 64k max
接下來238行進行檢查,確定核心解壓縮後的Image目標地址是否會覆蓋到zImage頭,如果是則準備將zImage頭轉移到解壓出來的核心後面 cmp r4, r2 bhs wont_overwrite sub r3, sp, r5 @ > compressed kernel size add r0, r4, r3, lsl #2 @ allow for 4x expansion cmp r0, r5 bls wont_overwrite mov r5, r2 @ decompress after malloc space mov r0, r5 mov r3, r7 bl decompress_kernel 真實情況——在大多數的應用中,核心編譯都會把壓縮的zImage和非壓縮的Image連結到同樣的地址,s3c2410平台下即是0x30008000。這樣做的好處是,人們不用關心核心是Image還是zImage,放到這個位置執行就OK,所以在解壓縮後zImage頭必須為真正的核心讓路。 在250行解壓完畢,核心長度傳回值存放在r0寄存器裡。在核心末尾空出128位元組的棧空間用,並且使其長度128位元組對齊。 add r0, r0, #127 + 128 @ alignment + stack bic r0, r0, #127 @ align the kernel length 算出搬移代碼的參數:計算核心末尾地址並存放於r1寄存器,需要搬移代碼原來地址放在r2,需要搬移的長度放在r3。然後執行搬移,並設定好sp指標指向新的棧(原來的棧也會被核心覆蓋掉) add r1, r5, r0 @ end of decompressed kernel adr r2, reloc_start ldr r3, LC1 add r3, r2, r3 1: ldmia r2!, {r9 - r14} @ copy relocation code stmia r1!, {r9 - r14} ldmia r2!, {r9 - r14} stmia r1!, {r9 - r14} 注意:對於uncompressed kernel是以arch/arm/boot/head.S開始的.對於核心解壓縮部分的code,在 arch/arm/boot/compressed/head.S中經過上面的zImage自解壓然後核心再從arch/arm/boot/head.S中啟動,本文不做討論. |