內核的實際起始函數爲 start_kernel() 函數,而後再調用其餘函數來執行啓動。再調用此函數以前,須要先將經過編譯內核得到的 zImage 進行解壓,請按成頁目錄構建等基本任務。javascript
調用 start_kernel 的過程分爲如下三個階段:java
解壓縮準備階段將執行中斷禁用、分配動態內存、初始化BBS區域、初始化頁目錄、打開緩存等任務。api
在該階段,zImage 解壓位置的下級 16KB 構建用於保存頁目錄的空間,在CP15的c2寄存器中保存頁目錄的位置。緩存
ARM中,頁目錄將 4GB 的內存以 1MB 節區爲單位進行管理。所以,爲了管理 4GB 的內存,須要有 4096 個以 1MB爲單位的項。因爲以32位的字符爲單位管理各項,因此共須要 16KB (4字節 X 4096各項 = 16KB)。以後,向至關於頁目錄位置的項設置 cacheable 和 bufferable,使頁目錄獲得緩衝並能快速訪問。bash
從start 標籤到解壓縮準備階段的流程圖架構
經過加載項完成對軟硬件的默認初始化後,最早執行的是 head.S (arch\arm\boot\compressed) 下的 start 標籤中的代碼。 完成的主要功能以下:函數
1 start: 2 .type start,#function 3 .rept 7 4 __nop 5 .endr 6 7 mov r0, r0 8 W(b) 1f
使用.type標號來指明start的符號類型是函數類型,而後重複執行.rept到.endr之間的指令7次,這裏一共執行了7次mov r0, r0指令,共佔用了4*7 = 28個字節,這是用來存放ARM的異常向量表的。向前跳轉到標號爲1處執行this
2. 保存 cpsr 的值到 r9 中,保存架構 ID 和 atags 指針分別到 r7 和 r8 中。spa
1 1: 2 ARM_BE8( setend be ) @ go BE8 if compiled for BE8 3 AR_CLASS( mrs r9, cpsr ) 4 /* 將啓動加載項傳遞的結構ID和 atags 信息分別保存到寄存器 r7 r8 中 */ 5 mov r7, r1 @ 保存結構ID 6 mov r8, r2 @ 保存 atags 指針
當中還有未貼出來的代碼,不相關的3d
這裏將CPU的工做模式保存到r9寄存器中,將uboot經過r1傳入的機器碼保存到r7寄存器中,將啓動參數tags的地址保存到r8寄存器中。
3.繼續在標籤「1」中運行,判斷當前 CPU 的工做模式,若不是在用戶模式下,則跳轉到 "not_angel" 標籤處,不然經過 swi 指令產生軟中斷異常的方式來進入 SVC 模式。
1 2 /* 3 * Booting from Angel - need to enter SVC mode and disable 4 * FIQs/IRQs (numeric definitions from angel arm.h source). 5 * We only do this if we were in user mode on entry. 6 */ 7 mrs r2, cpsr @ 將CPSR狀態寄存器讀取,保存到R1中,即獲取當前CPU模式 8 tst r2, #3 @ 判斷CPU是否爲用戶模式 9 bne not_angel 10 mov r0, #0x17 @ angel_SWIreason_EnterSVC 11 ARM( swi 0x123456 ) @ angel_SWI_ARM 12 THUMB( svc 0xab ) @ angel_SWI_THUMB
這裏將CPU的工做模式保存到r2寄存器中,而後判斷是不是SVC模式,若是是USER模式就會經過swi指令產生軟中斷異常的方式來自動進入SVC模式。因爲我這裏在uboot中已經將CPU的模式設置爲SVC模式了,因此就直接跳到not_angel符號處執行。
4.藉助 safe_svcmode_maskall 宏, 屏蔽 IRQ、FIQ中斷,切換到 SVC 模式;將 r9 中保存的原來的 CPSR 的值保存到 SPSR 中。
1 /* 設置CPU爲SVC模式的具體操做 */ 2 not_angel: 3 /* .macro safe_svcmode_maskall reg:req 在 Assembler.h (arch\arm\include\asm)中定義*/ 4 safe_svcmode_maskall r0 5 msr spsr_cxsf, r9 @ Save the CPU boot mode in 6 @ SPSR
(1)藉助 safe_svcmode_maskall 宏, 屏蔽 IRQ、FIQ中斷,切換到 SVC 模式;將 r9 中保存的原來的 CPSR 的值保存到 SPSR 中。
arch/arm/include/asm/assembler.h
這裏的註釋已經說明了,這裏是強制將CPU的工做模式切換到SVC模式,而且關閉IRQ和FIQ中斷。而後將r9中保存的原始CPU配置保存到SPSR中。
1 /* 此處出現的 MODE_MASK、PSR_I_BIT 等常量被宏定義在 arch/arm/include/uapi/asm/ptrace.h */ 2 .macro safe_svcmode_maskall reg:req 3 #if __LINUX_ARM_ARCH__ >= 6 && !defined(CONFIG_CPU_V7M) 4 mrs \reg , cpsr 5 eor \reg, \reg, #HYP_MODE 6 tst \reg, #MODE_MASK 7 bic \reg , \reg , #MODE_MASK @ 將模式位M[4:0]清0 8 /* 經過設置低 8 位爲 110 10011,達到了關閉 IRQ、FIQ、設置 CPU 工做模式爲 SVC 模式的目標 */ 9 orr \reg , \reg , #PSR_I_BIT | PSR_F_BIT | SVC_MODE 10 THUMB( orr \reg , \reg , #PSR_T_BIT ) 11 bne 1f 12 orr \reg, \reg, #PSR_A_BIT 13 badr lr, 2f 14 msr spsr_cxsf, \reg 15 __MSR_ELR_HYP(14) 16 __ERET 17 1: msr cpsr_c, \reg 18 2: 19 #else 20 /* 21 * workaround for possibly broken pre-v6 hardware 22 * (akita, Sharp Zaurus C-1000, PXA270-based) 23 */ 24 setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, \reg 25 #endif 26 .endm
5.將內核解壓地址(ZRELADDR)保存到 R4 中,依然在 "not_angel" 標籤中運行
1 /* 字符段開始區域 */ 2 .text 3 4 #ifdef CONFIG_AUTO_ZRELADDR 5 mov r4, pc 6 and r4, r4, #0xf8000000 7 /* Determine final kernel image address. */ 8 add r4, r4, #TEXT_OFFSET 9 #else 10 ldr r4, =zreladdr 11 #endif
內核配置項AUTO_ZRELDDR表示自動計算內核解壓地址(Auto calculation of the decompressed kernelimage address),這裏沒有選擇這個配置項,因此保存到r4中的內核解壓地址就是zreladdr
(1)定義了 CONFIG_AUTO_ZRELADDR, 將在運行時計算肯定 ZRELADDR
ZRELADDR 的值爲:
注:此處與 0xf8000000 作 and 操做的緣由樣是咱們默認 zImage 被放置的位置必定在距離 PHYS_OFFSET 的 128MB 以內。
TEXT_OFFSET 定義以下所示:
File: /arch/arm/Makefile
此處的 textofs-y 定義以下所示:
即 TEXT_OFFSET 的值爲 0x00008000 = 32KB
此處之因此加上 TEXT_OFFSET 這個 32KB 的值的緣由以下圖所示:
PHY_OFFSET的值不必定爲 0x60000000,根據硬件來肯定。
(2)未定義 CONFIG_AUTO_ZRELADDR 時,直接加載 zreladdr 到 R4 中
zreladdr 的定義以下所示:
File: /arch/arm/boot/compressed/Makefile
ZERLADDR定義以下:
File: /arch/arm/boot/Makefile
看一下params_phys和initrd_phys的值,他們最終由arch/arm/mach-$(SOC)/Makefile.boot決定,我這裏使用的soc是bcm2807(bcm2835),他的Makefile.boot內容以下:
zreladdr-y := 0x00008000
params_phys-y := 0x00000100
initrd_phys-y :=0x00800000
params_phys-y和initrd_phys-y是內核參數的物理地址和initrd文件系統的物理地址。其實除了zreladdr外這些地址uboot都會傳入的。
這裏的 zreladdr-y 定義在 /arch/arm/mach-xxx/Makefile.boot 中。
好比所用的 2440
這些地址都是經過uboot 傳入進來的
6.緩存和MMU初始化cache_on的執行流程
這裏將比較當前PC地址和內核解壓地址,只有在不會自覆蓋的狀況下才會建立一個頁表,若是當前運行地址PC < 解壓地址 r4,則讀取 LC0+32 地址處的內容加載到 r0 中,不然跳轉到 cache_on 處執行緩存初始化和MMU初始化。
代碼以下,此處代碼依然在 "not_angel" 標籤中運行
1 mov r0, pc 2 cmp r0, r4 3 ldrcc r0, LC0+32 4 addcc r0, r0, pc 5 cmpcc r4, r0 6 orrcc r4, r4, #1 @ remember we skipped cache_on 7 blcs cache_on
LC0的定義以下:
LC0+32地址處的內容爲:_end -restart + 16384 + 1024*1024,所指的就是程序長度+16k的頁表長+1M的DTB空間。
繼續比較解壓地址r4(0x00008000)和當前運行程序的(結束地址+16384 + 1024*1024),若是小於則不進行緩存初始化並置位r4最低位進行標識。
分狀況總結一下:
(1) PC >= r4:直接進行緩存初始化
(2) PC < r4 && _end + 16384+ 1024*1024 > r4:不進行緩存初始化
(3) PC < r4 && _end + 16384+ 1024*1024 <= r4:執行緩存初始化
cache on 開始執行:
1 /* 2 * Turn on the cache. We need to setup some page tables so that we 3 * can have both the I and D caches on. 4 * 5 * We place the page tables 16k down from the kernel execution address, 6 * and we hope that nothing else is using it. If we're using it, we 7 * will go pop! 8 * 9 * On entry, 10 * r4 = kernel execution address 11 * r7 = architecture number 12 * r8 = atags pointer 13 * On exit, 14 * r0, r1, r2, r3, r9, r10, r12 corrupted 15 * This routine must preserve: 16 * r4, r7, r8 17 */ 18 .align 5 19 cache_on: mov r3, #8 @ cache_on function 20 b call_cache_fn
註釋中說明了,爲了開啓I Cache和D Cache,須要創建頁表(開啓MMU),而頁表使用的就是內核運行地址如下的16KB空間(對於個人環境來講地址就等於0x00004000~0x00008000)。同時在運行的過程當中r0~r3以及r九、r10和r12寄存器會被使用。
這裏首先在r3中保存打開緩存函數表項在cache操做表中的地址偏移(這裏爲8,cache操做表見後文),而後跳轉到call_cache_fn中。
1 /* 2 * Here follow the relocatable cache support functions for the 3 * various processors. This is a generic hook for locating an 4 * entry and jumping to an instruction at the specified offset 5 * from the start of the block. Please note this is all position 6 * independent code. 7 * 8 * r1 = corrupted 9 * r2 = corrupted 10 * r3 = block offset 11 * r9 = corrupted 12 * r12 = corrupted 13 */ 14 15 call_cache_fn: adr r12, proc_types 16 #ifdef CONFIG_CPU_CP15 17 mrc p15, 0, r9, c0, c0 @ get processor ID 18 #elif defined(CONFIG_CPU_V7M) 19 /* 20 * On v7-M the processor id is located in the V7M_SCB_CPUID 21 * register, but as cache handling is IMPLEMENTATION DEFINED on 22 * v7-M (if existant at all) we just return early here. 23 * If V7M_SCB_CPUID were used the cpu ID functions (i.e. 24 * __armv7_mmu_cache_{on,off,flush}) would be selected which 25 * use cp15 registers that are not implemented on v7-M. 26 */ 27 bx lr 28 #else 29 ldr r9, =CONFIG_PROCESSOR_ID 30 #endif 31 1: ldr r1, [r12, #0] @ get value 32 ldr r2, [r12, #4] @ get mask 33 eor r1, r1, r9 @ (real ^ match) 34 tst r1, r2 @ & mask 35 ARM( addeq pc, r12, r3 ) @ call cache function 36 THUMB( addeq r12, r3 ) 37 THUMB( moveq pc, r12 ) @ call cache function 38 add r12, r12, #PROC_ENTRY_SIZE 39 b 1b
首先保存cache操做表的運行地址到r12寄存器中,proc_types定義在head.s中:
1 /* 2 * Table for cache operations. This is basically: 3 * - CPU ID match 4 * - CPU ID mask 5 * - 'cache on' method instruction 6 * - 'cache off' method instruction 7 * - 'cache flush' method instruction 8 * 9 * We match an entry using: ((real_id ^ match) & mask) == 0 10 * 11 * Writethrough caches generally only need 'on' and 'off' 12 * methods. Writeback caches _must_ have the flush method 13 * defined. 14 */ 15 .align 2 16 .type proc_types,#object
表中的每一類處理器都包含如下5項(若是不存在緩存操做函數則使用「mov pc, lr」佔位):
(1) CPU ID
(2) CPU ID 位掩碼(用於匹配CPU類型用)
(3) 打開緩存「cache on」函數入口
(4) 關閉緩存「cache off」函數入口
(5) 刷新緩存「cache flush」函數入口
我所用的CPU爲ARM920T的 S3C2440,爲ARMV4T架構,通常架構以下圖:
對應的代碼爲:
若配置了CPU_CP15條件編譯項,因此這裏將從CP15中獲取CPU型號而不是從內核配置項中獲取。
而後逐條對cache操做表中的CPU類型進行匹配,若是匹配上了就跳轉到相應的函數入口執行。
遍歷 proc_types 列表,查找想對應的處理器類型,找到以後 pc = r12 + r3,r3 中存儲的是常數 8,即 pc 指向了相對應的 cache on 子例程。執行以下
1 call_cache_fn: adr r12, proc_types 2 #ifdef CONFIG_CPU_CP15 3 mrc p15, 0, r9, c0, c0 @ get processor ID 4 #elif defined(CONFIG_CPU_V7M) 5 /* 6 * On v7-M the processor id is located in the V7M_SCB_CPUID 7 * register, but as cache handling is IMPLEMENTATION DEFINED on 8 * v7-M (if existant at all) we just return early here. 9 * If V7M_SCB_CPUID were used the cpu ID functions (i.e. 10 * __armv7_mmu_cache_{on,off,flush}) would be selected which 11 * use cp15 registers that are not implemented on v7-M. 12 */ 13 bx lr 14 #else 15 ldr r9, =CONFIG_PROCESSOR_ID 16 #endif 17 1: ldr r1, [r12, #0] @ get value 18 ldr r2, [r12, #4] @ get mask 19 eor r1, r1, r9 @ (real ^ match) 20 tst r1, r2 @ & mask 21 ARM( addeq pc, r12, r3 ) @ call cache function 22 THUMB( addeq r12, r3 ) 23 THUMB( moveq pc, r12 ) @ call cache function 24 add r12, r12, #PROC_ENTRY_SIZE 25 b 1b
代碼註釋已經很清楚,以後調用 cache 函數,對應 2440 則調用函數:__armv4_mmu_cache_on