1. 程式人生 > >三、內核啟動(一)

三、內核啟動(一)

ddc 解壓縮 star m283 using 獲得 eterm obj cmode

  內核的實際起始函數為 start_kernel() 函數,然後再調用其他函數來執行啟動。再調用此函數之前,需要先將通過編譯內核獲得的 zImage 進行解壓,請按成頁目錄構建等基本任務。

  調用 start_kernel 的過程分為以下三個階段:

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

3.1 內核解壓

3.1.1 準備階段

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

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

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

  從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處執行

  2. 保存 cpsr 的值到 r9 中,保存架構 ID 和 atags 指針分別到 r7 和 r8 中。

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 指針

  當中還有未貼出來的代碼,不相關的

  這裏將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以及r9、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

三、內核啟動(一)