3、內核啓動(一)

  內核的實際起始函數爲 start_kernel() 函數,而後再調用其餘函數來執行啓動。再調用此函數以前,須要先將經過編譯內核得到的 zImage 進行解壓,請按成頁目錄構建等基本任務。javascript

  調用 start_kernel 的過程分爲如下三個階段:java

  1. 解壓內核映像 zImage 前的準備階段,經過與處理器版本相符的處理器類型列表,執行打開/關閉/清除緩存等任務,爲MMU構建16KB的頁目錄;
  2. 對 zImage 執行解壓縮
  3. 檢查處理器及機器信息、經過啓動加載項得到 atag 信息的有效性,而後激活 MMU 調用內核的起始函數 start_kernel()。

3.1 內核解壓

3.1.1 準備階段

  解壓縮準備階段將執行中斷禁用、分配動態內存、初始化BBS區域、初始化頁目錄、打開緩存等任務。api

  在該階段,zImage 解壓位置的下級 16KB 構建用於保存頁目錄的空間,在CP15的c2寄存器中保存頁目錄的位置。緩存

  ARM中,頁目錄將 4GB 的內存以 1MB 節區爲單位進行管理。所以,爲了管理 4GB 的內存,須要有 4096 個以 1MB爲單位的項。因爲以32位的字符爲單位管理各項,因此共須要 16KB (4字節 X 4096各項 = 16KB)。以後,向至關於頁目錄位置的項設置 cacheable 和 bufferable,使頁目錄獲得緩衝並能快速訪問。bash

  從start 標籤到解壓縮準備階段的流程圖架構

  

  • 啓動加載項必須提供5種功能
    • RAM初始化
    • 串行端口初始化
    • 查找機器類別
    • 構建  tagged list 內核
    • 將控制移交到內核鏡像    

3.1.1.1 進入啓動加載後結束首個啓動--start 標籤

  經過加載項完成對軟硬件的默認初始化後,最早執行的是 head.S (arch\arm\boot\compressed) 下的 start 標籤中的代碼。 完成的主要功能以下:函數

  • 從啓動加載項接收結構ID和atags信息
  • 禁用中斷
  • 初始化寄存器,跳轉到 not_relocated 標籤
  1. 從 start 標籤開始執行,共執行了 8 (rept 7 + 1) 次 "mov r0, r0" 指令(等同於 nop 指令),空出了 32 字節的用來存放 ARM 的中斷向量表的位置,而後跳轉到 "1" 標籤處。
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寄存器中。

  • CONFIG_ARM_VIRT_EXT 代表啓用了 ARM 虛擬化擴展。
  • 從 bootloader 中接收了 3 個參數,分別爲
    • R0 = 0
    • R1 = 架構 ID
    • R2 = atags 指針

  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 的值爲:

    1. 先是 pc 值和 0xf8000000 作與操做;

      注:此處與 0xf8000000 作 and 操做的緣由樣是咱們默認 zImage 被放置的位置必定在距離 PHYS_OFFSET 的 128MB 以內。

    1. 再加上 TEXT_OFFSET(內核最終存放的物理地址與內存起始處之間的偏移)

      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

相關文章
相關標籤/搜索