天天看點

v87.01 鴻蒙核心源碼分析 (核心啟動篇) | 從彙編到main() | 百篇部落格分析 OpenHarmony 源碼

本篇關鍵詞:核心重定位、MMU、SVC棧、熱啟動、核心映射表

  • ​​ ​​

這應該是系列篇最難寫的一篇,全是彙編代碼,需大量的底層知識,涉及協處理器,核心鏡像重定位,建立核心映射表,初始化 CPU 模式棧,熱啟動,到最後熟悉的 main() 。

Uboot

在 PC寄存器 指向核心第一行指令之前,需要先将核心從 flash 加載到記憶體指定位置,這部分工作由引導程式 uboot (與硬體強相關) 完成,而引導代碼并不在 kernel_liteos_a (與硬體弱相關) 工程中,而在工程 ​​ u-boot-2020.01中 ,前往 >>​​ ,是以系列篇不對引導程式詳細說明,後續有機會再分析。引導結束後舞台就交給了核心 reset_vector 處 ,一切從此開始。

核心入口

在連結檔案 liteos.ld 中可知核心的入口位址為 ​

​ENTRY(reset_vector)​

​ , 分别出現在reset_vector_mp.S (多核啟動) 和 reset_vector_up.S(單核啟動),系列篇研究多核啟動的情況。代碼可結合 (協處理器篇) 看更容易懂。

reset_vector: //鴻蒙開機代碼
    /* clear register TPIDRPRW */
    mov     r0, #0                    //r0 = 0
    mcr     p15, 0, r0, c13, c0, 4    //複位線程辨別符寄存器TPIDRPRW , 不複位将導緻系統不能啟動
    /* do some early cpu setup: i/d cache disable, mmu disabled */
    mrc     p15, 0, r0, c1, c0, 0    //System Control Register-SCTLR | 讀取系統控制寄存器内容
    bic     r0, #(1<<12)            //禁用指令緩存功能
    bic     r0, #(1<<2 | 1<<0)        //禁用資料和TLB的緩存功能(bit2) | mmu功能(bit0)
    mcr     p15, 0, r0, c1, c0, 0    //寫系統控制寄存器

    /* enable fpu+neon 一些系統寄存器的操作
    | 使能浮點運算(floating point unit)和 NEON就是一種基于SIMD思想的ARM技術,相比于ARMv6或之前的架構,
    NEON結合了64-bit和128-bit的SIMD指令集,提供128-bit寬的向量運算(vector operations)*/
#ifndef LOSCFG_TEE_ENABLE        //Trusted Execution Environment   可信執行環境
    MRC    p15, 0, r0, c1, c1, 2 //非安全模式通路寄存器 (Non-Secure Access Control Register - NSACR)
    ORR    r0, r0, #0xC00        //使能安全和非安全通路協處理器10和11(Coprocessor 10和11)
    BIC    r0, r0, #0xC000       //設定bit15為0,不會影響修改CPACR.ASEDIS寄存器位(控制Advanced SIMD功能)| bit14 reserved
    MCR    p15, 0, r0, c1, c1, 2

    LDR    r0, =(0xF << 20)      //允許在EL0和EL1下,通路協處理器10和11(控制Floating-point和Advanced SIMD特性)
    MCR    p15, 0, r0, c1, c0, 2
    ISB
#endif
    MOV    r3, #0x40000000        //EN, bit[30] 設定FPEXC的EN位來使能FPU
    VMSR   FPEXC, r3            //浮點異常控制寄存器 (Floating-Point Exception Control register | B4.1.57) 

    /* r11: delta of physical address and virtual address | 計算虛拟位址和實體位址之間的內插補點,目的是為了建立映射關系表 */
    adr     r11, pa_va_offset //擷取pa_va_offset變量實體位址,由于這時候mmu已經被關閉,是以這個值就表示pa_va_offset變量的實體位址。
                              /*adr 是一條小範圍的位址讀取僞指令,它将基于PC的相對偏移的位址值讀到目标寄存器中。
                               *編譯源程式時,彙編器首先計算目前PC值(目前指令位置)到exper的距離,然後用一條ADD或者SUB指令替換這條僞指令,
                               *例如:ADD register,PC,#offset_to_exper 注意,标号exper與指令必須在同一代碼段
                               */
    ldr     r0, [r11]          //r0 = *r11 擷取pa_va_offset變量虛拟位址
    sub     r11, r11, r0      //實體位址-虛拟位址 = 映射偏移量 放入r11

    mrc     p15, 0, r12, c0, c0, 5      /* Multiprocessor Affinity Register-MPIDR */
    and     r12, r12, #MPIDR_CPUID_MASK //掩碼過濾
    cmp     r12, #0                        //主要核0判斷
    bne     secondary_cpu_init            //初始化CPU次核
    /*
     * adr是小範圍的位址讀取僞指令,它将基于PC寄存器相對偏移的位址值讀取到寄存器中,
     * 例如: 0x00000004     : adr     r4, __exception_handlers
     * 則此時PC寄存器的值為: 0x00000004 + 8(在三級流水線時,PC和執行位址相差8),
     * adr指令和辨別__exception_handlers的位址相對固定,二者偏移量若為offset,
     * 最後r4 = (0x00000004 + 8) + offset
    */

    /* if we need to relocate to proper location or not | 如果需要重新安裝到合适的位置*/
    adr     r4, __exception_handlers            /* r4: base of load address | 加載基址*/
    ldr     r5, =SYS_MEM_BASE                   /* r5: base of physical address | 實體基址*/
    subs    r12, r4, r5                         /* r12: delta of load address and physical address | 二者偏移量*/
    beq     reloc_img_to_bottom_done            /* if we load image at the bottom of physical address | 不相等就需要重定位 */
    
    /* we need to relocate image at the bottom of physical address | 需要知道拷貝的大小*/
    ldr     r7, =__exception_handlers           /* r7: base of linked address (or vm address) | 連結位址基位址*/
    ldr     r6, =__bss_start                    /* r6: end of linked address (or vm address),由于目前階段有用的資料是中斷向量表+代碼段+隻讀資料段+資料段,
                                                   是以隻需複制[__exception_handlers,__bss_start]這段資料到記憶體基址處 */
    sub     r6, r7                              /* r6: delta of linked address (or vm address) | 核心鏡像大小 */
    add     r6, r4                              /* r6: end of load address | 說明需拷貝[ r4,r4+r6 ] 區間内容到 [ r5,r5+r6 ]*/

reloc_img_to_bottom_loop://重定位鏡像到核心實體記憶體基位址,将核心從加載位址拷貝到記憶體基址處
    ldr     r7, [r4], #4    // 類似C語言 *r5 = *r4 , r4++ , r5++ 
    str     r7, [r5], #4    // #4 代表32位的指令長度,此時在拷貝核心代碼區内容
    cmp     r4, r6          /* 拷貝完成條件. r4++ 直到等于r6 (加載結束位址) 完成拷貝動作 */
    bne     reloc_img_to_bottom_loop
    sub     pc, r12                             /* 重新校準pc寄存器, 無縫跳到了拷貝後的指令位址處執行 r12是重定位鏡像前核心加載基位址和核心實體記憶體基位址的內插補點 */
    nop        // 注意執行完成sub       pc, r12後,新的PC寄存器也指向了     nop ,nop是僞彙編指令,等同于 mov r0 r0 通常用于控制時序的目的,強制記憶體對齊,防止流水線災難,占據分支指令延遲                        
    sub     r11, r11, r12                       /* r11: eventual address offset | 最終位址映射偏移量, 用于建構MMU頁表 */
//核心總大小 __bss_start - __exception_handlers
reloc_img_to_bottom_done:
#ifdef LOSCFG_KERNEL_MMU 
    ldr     r4, =g_firstPageTable               /* r4: physical address of translation table and clear it
                                                   核心頁表是用數組g_firstPageTable存儲 見于los_arch_mmu.c */
    add     r4, r4, r11                         //計算g_firstPageTable頁表實體位址
    mov     r0, r4                                //因為預設r0 将作為memset_optimized的第一個參數
    mov     r1, #0                                //第二個參數,清0
    mov     r2, #MMU_DESCRIPTOR_L1_SMALL_ENTRY_NUMBERS //第三個參數是L1表的長度
    bl      memset_optimized                    /* optimized memset since r0 is 64-byte aligned | 将核心頁表空間清零*/

    ldr     r5, =g_archMmuInitMapping            //記錄映射關系表
    add     r5, r5, r11                         //擷取g_archMmuInitMapping的實體位址
init_mmu_loop:                                    //初始化核心頁表
    ldmia   r5!, {r6-r10}                       /* r6 = phys, r7 = virt, r8 = size, r9 = mmu_flags, r10 = name | 傳參: 實體位址、虛拟位址、映射大小、映射屬性、名稱*/
    cmp     r8, 0                               /* if size = 0, the mmu init done | 完成條件 */
    beq     init_mmu_done                        //标志寄存器中Z标志位等于零時跳轉到     init_mmu_done處執行
    bl      page_table_build                    //建立頁表
    b       init_mmu_loop                        //循環繼續
init_mmu_done:
    orr     r8, r4, #MMU_TTBRx_FLAGS            /* r8 = r4 and set cacheable attributes on translation walk | 設定緩存*/
    ldr     r4, =g_mmuJumpPageTable             /* r4: jump pagetable vaddr | 頁表虛拟位址*/
    add     r4, r4, r11                
    ldr     r4, [r4]
    add     r4, r4, r11                         /* r4: jump pagetable paddr | 頁表實體位址*/

    /* build 1M section mapping, in order to jump va during turing on mmu:pa == pa, va == pa */
    /* 從目前PC開始建立1MB空間的段映射,分别建立實體位址和虛拟位址方式的段映射頁表項
     * 核心臨時頁表在系統 使能mmu -> 切換到虛拟位址運作 這段時間使用
     */
    mov     r6, pc
    mov     r7, r6                              /* r7: pa (MB aligned)*/
    lsr     r6, r6, #20                         /* r6: pa l1 index */
    ldr     r10, =MMU_DESCRIPTOR_KERNEL_L1_PTE_FLAGS
    add     r12, r10, r6, lsl #20               /* r12: pa |flags */
    str     r12, [r4, r7, lsr #(20 - 2)]        /* jumpTable[paIndex] = pt entry */
    rsb     r7, r11, r6, lsl #20                /* r7: va */
    str     r12, [r4, r7, lsr #(20 - 2)]        /* jumpTable[vaIndex] = pt entry */

    bl      mmu_setup                           /* set up the mmu | 核心映射表已經建立好了,此時可以啟動MMU工作了*/
#endif
    /* clear out the interrupt and exception stack and set magic num to check the overflow 
    |exc_stack|位址高位
    |svc_stack|位址低位
    清除中斷和異常堆棧并設定magic num檢查溢出 */
    ldr     r0, =__svc_stack        //stack_init的第一個參數 __svc_stack表示棧頂
    ldr     r1, =__exc_stack_top    //stack_init的第二個參數 __exc_stack_top表示棧底, 這裡會有點繞, top表高位址位
    bl      stack_init              //初始化各個cpu不同模式下的棧空間
    //設定各個棧頂魔法數字
    STACK_MAGIC_SET __svc_stack, #OS_EXC_SVC_STACK_SIZE, OS_STACK_MAGIC_WORD //中斷棧底設成"燙燙燙燙燙燙"
    STACK_MAGIC_SET __exc_stack, #OS_EXC_STACK_SIZE, OS_STACK_MAGIC_WORD     //異常棧底設成"燙燙燙燙燙燙"

warm_reset: //熱啟動 Warm Reset, warm reboot, soft reboot, 在不關閉電源的情況,由軟體控制重新開機計算機
    /* initialize CPSR (machine state register) */
    mov    r0, #(CPSR_IRQ_DISABLE|CPSR_FIQ_DISABLE|CPSR_SVC_MODE) /* 禁止IRQ中斷 | 禁止FIQ中斷 | 管理模式-作業系統使用的保護模式 */
    msr    cpsr, r0    //設定CPSR寄存器

    /* Note: some functions in LIBGCC1 will cause a "restore from SPSR"!! */
    msr    spsr, r0 //設定SPSR寄存器

    /* get cpuid and keep it in r12 */
    mrc     p15, 0, r12, c0, c0, 5        //R12儲存CPUID 
    and     r12, r12, #MPIDR_CPUID_MASK //掩碼操作擷取目前cpu id

    /* set svc stack, every cpu has OS_EXC_SVC_STACK_SIZE stack | 設定 SVC棧 */
    ldr    r0, =__svc_stack_top //注意這是棧底,高位址位
    mov    r2, #OS_EXC_SVC_STACK_SIZE //棧大小
    mul    r2, r2, r12 
    sub    r0, r0, r2                   /* 算出目前core的中斷棧棧頂位置,寫入所屬core的sp */
    mov    sp, r0

    LDR    r0, =__exception_handlers    
    MCR    p15, 0, r0, c12, c0, 0       /* Vector Base Address Register - VBAR */

    cmp    r12, #0                        //CPU是否為主核
    bne    cpu_start                    //不相等就跳到從核處理分支

clear_bss:                                //主核處理.bss段清零
    ldr    r0, =__bss_start
    ldr    r2, =__bss_end
    mov    r1, #0
    sub    r2, r2, r0
    bl     memset
#if defined(LOSCFG_CC_STACKPROTECTOR_ALL) || \
    defined(LOSCFG_CC_STACKPROTECTOR_STRONG) || \
    defined(LOSCFG_CC_STACKPROTECTOR)
    bl     __stack_chk_guard_setup
#endif

#ifdef LOSCFG_GDB_DEBUG
    /* GDB_START - generate a compiled_breadk,This function will get GDB stubs started, with a proper environment */
    bl     GDB_START
    .word  0xe7ffdeff
#endif

    bl     main                //帶LR的子程式跳轉, LR = pc - 4, 執行C層main函數      

解讀

  • 第一步: 操作 CP15 協處理器 TPIDRPRW 寄存器,它被 ARM 設計儲存目前運作線程的 ID值,在ARMv7 架構中才新出現,需PL1權限以上才能通路,而硬體不會從内部去改變它的值,也就是說這是一個直接暴露給工程師操作維護的一個寄存器,在鴻蒙核心中被用于記錄線程結構體的開始位址,可以搜尋 OsCurrTaskSet 來跟蹤哪些地方會切換目前任務以便更好的了解核心。
  • 第二步: 系統控制寄存器(SCTLR),B4.1.130 SCTLR, System Control Register 它提供了系統的最進階别控制,高到了玉皇大帝級别,代碼中将 ​

    ​0​

    ​、​

    ​2​

    ​、​

    ​12​

    ​位寫 ​

    ​0​

    ​。對應關閉 MMU 、資料緩存 、指令緩存 功能。
  • 第三步: 對浮點運算​

    ​FPU​

    ​的設定,在安全模式下使用​

    ​FPU​

    ​,須定義​

    ​NSACR​

    ​、​

    ​CPACR​

    ​、​

    ​FPEXC​

    ​ 三個寄存器
  • 第四步: 計算虛拟位址和實體位址的偏移量,為何要計算它呢 ? 主要目的是為了建立虛拟位址和實體位址的映射關系,因為在 MMU啟動之後,運作位址(PC寄存器指向的位址)将變成虛拟位址,使用虛拟位址就離不開映射表,是以兩個位址的映射關系需要在MMU啟動前就建立好,而有了偏移量就可以建立映射表。但需先搞清楚 連結位址 和 運作位址 兩個概念。
  • 連結位址由連結器确定,連結器會将所有輸入的.o檔案連結成一個格式的.bin檔案,它們都是ELF格式, 連結器給每條指令/資料都賦與一個位址,這個位址叫連結位址,它可以是相對的也可以是絕對的。但它們之間的内部距離是固定的,連結具體過程可翻看(重定位篇)和(連結腳本篇)
  • 運作位址由加載器确定,核心鏡像首先通過燒錄工具将核心燒錄到flash指定的位置,開機後由boot loader工具,例如uboot,将核心鏡像加載到指定位址後開始執行真正的核心代碼,這個位址叫運作位址。

兩個位址往往不一樣,而核心設計者希望它們是一樣的,那有沒有辦法檢測二者是否一樣呢? 答案是 : 當然有的 ,通過一個變量在連結時将其連結位址變成變量的内容 ,無論中間怎麼加載變量的内容是不會變的,而擷取運作位址是很容易擷取的,其實就是PC寄存器的位址,二者一減,加載偏了多少不就出來了

pa_va_offset: 
  .word   . //定義一個4位元組的pa_va_offset 變量, 連結器生成一個連結位址, . 表示 pa_va_offset = 連結位址 舉例: 在位址 0x17321796 中儲存了 0x17321796 值

adr     r11, pa_va_offset //代碼已執行至此,指令将擷取 pa_va_offset 的運作位址(可能不是`0x17321796`) 給r11
ldr     r0, [r11] // [r11]中存的是連結位址 `0x17321796`, 它不會随加載器變化的
sub     r11, r11, r0 // 二者相減得到了偏移位址      
  • 第五步: 将核心代碼從 __exception_handlers 處移到 SYS_MEM_BASE處,長度是 __bss_start - __exception_handlers , __exception_handlers是加載後的開始位址, 由加載器決定, 而SYS_MEM_BASE 是系統定義的記憶體位址, 可由系統內建商指定配置, 他們希望核心從這裡運作。 下圖為核心鏡像布局
  • v87.01 鴻蒙核心源碼分析 (核心啟動篇) | 從彙編到main() | 百篇部落格分析 OpenHarmony 源碼
  • 具體代碼如下:
/* if we need to relocate to proper location or not | 如果需要重新安裝到合适的位置*/
  adr     r4, __exception_handlers            /* r4: base of load address | 加載基址*/
  ldr     r5, =SYS_MEM_BASE                   /* r5: base of physical address | 實體基址*/
  subs    r12, r4, r5                         /* r12: delta of load address and physical address | 二者偏移量*/
  beq     reloc_img_to_bottom_done            /* if we load image at the bottom of physical address | 不相等就需要重定位 */

  /* we need to relocate image at the bottom of physical address | 需要知道拷貝的大小*/
  ldr     r7, =__exception_handlers           /* r7: base of linked address (or vm address) | 連結位址基位址*/
  ldr     r6, =__bss_start                    /* r6: end of linked address (or vm address),由于目前階段有用的資料是中斷向量表+代碼段+隻讀資料段+資料段,
                             是以隻需複制[__exception_handlers,__bss_start]這段資料到記憶體基址處 */
  sub     r6, r7                              /* r6: delta of linked address (or vm address) | 核心鏡像大小 */
  add     r6, r4                              /* r6: end of load address | 說明需拷貝[ r4,r4+r6 ] 區間内容到 [ r5,r5+r6 ]*/

  reloc_img_to_bottom_loop://重定位鏡像到核心實體記憶體基位址,将核心從加載位址拷貝到記憶體基址處
      ldr     r7, [r4], #4  // 類似C語言 *r5 = *r4 , r4++ , r5++ 
      str     r7, [r5], #4  // #4 代表32位的指令長度,此時在拷貝核心代碼區内容
      cmp     r4, r6          /* 拷貝完成條件. r4++ 直到等于r6 (加載結束位址) 完成拷貝動作 */
      bne     reloc_img_to_bottom_loop
      sub     pc, r12                             /* 重新校準pc寄存器, 無縫跳到了拷貝後的指令位址處執行 r12是重定位鏡像前核心加載基位址和核心實體記憶體基位址的內插補點 */
      nop   // 注意執行完成sub       pc, r12後,新的PC寄存器也指向了   nop ,nop是僞彙編指令,等同于 mov r0 r0 通常用于控制時序的目的,強制記憶體對齊,防止流水線災難,占據分支指令延遲            
      sub     r11, r11, r12                       /* r11: eventual address offset | 最終位址偏移量 */      
  • 第六步: 在打開MMU必須要做好虛拟位址和實體位址的映射關系 , 需建構頁表 , 關于頁表可翻看 虛實映射篇, 具體代碼如下
#ifdef LOSCFG_KERNEL_MMU 
ldr     r4, =g_firstPageTable               /* r4: physical address of translation table and clear it
                                                   核心頁表是用數組g_firstPageTable存儲 見于los_arch_mmu.c */
add     r4, r4, r11                         //計算g_firstPageTable頁表實體位址
mov     r0, r4                              //因為預設r0 将作為memset_optimized的第一個參數
mov     r1, #0                              //第二個參數,清0
mov     r2, #MMU_DESCRIPTOR_L1_SMALL_ENTRY_NUMBERS //第三個參數是L1表的長度
bl      memset_optimized                    /* optimized memset since r0 is 64-byte aligned | 将核心頁表空間清零*/

ldr     r5, =g_archMmuInitMapping           //記錄映射關系表
add     r5, r5, r11                         //擷取g_archMmuInitMapping的實體位址
init_mmu_loop:                                  //初始化核心頁表
    ldmia   r5!, {r6-r10}                       /* r6 = phys, r7 = virt, r8 = size, r9 = mmu_flags, r10 = name | 實體位址、虛拟位址、映射大小、映射屬性、名稱*/
    cmp     r8, 0                               /* if size = 0, the mmu init done */
    beq     init_mmu_done                       //标志寄存器中Z标志位等于零時跳轉到     init_mmu_done處執行
    bl      page_table_build                    //建立頁表
    b       init_mmu_loop                       //循環繼續
init_mmu_done:
    orr     r8, r4, #MMU_TTBRx_FLAGS            /* r8 = r4 and set cacheable attributes on translation walk | 設定緩存*/
    ldr     r4, =g_mmuJumpPageTable             /* r4: jump pagetable vaddr | 頁表虛拟位址*/
    add     r4, r4, r11             
    ldr     r4, [r4]
    add     r4, r4, r11                         /* r4: jump pagetable paddr | 頁表實體位址*/

    /* build 1M section mapping, in order to jump va during turing on mmu:pa == pa, va == pa */
    /* 從目前PC開始建立1MB空間的段映射,分别建立實體位址和虛拟位址方式的段映射頁表項
    * 核心臨時頁表在系統 使能mmu -> 切換到虛拟位址運作 這段時間使用
    */
    mov     r6, pc
    mov     r7, r6                              /* r7: pa (MB aligned)*/
    lsr     r6, r6, #20                         /* r6: pa l1 index */
    ldr     r10, =MMU_DESCRIPTOR_KERNEL_L1_PTE_FLAGS
    add     r12, r10, r6, lsl #20               /* r12: pa |flags */
    str     r12, [r4, r7, lsr #(20 - 2)]        /* jumpTable[paIndex] = pt entry */
    rsb     r7, r11, r6, lsl #20                /* r7: va */
    str     r12, [r4, r7, lsr #(20 - 2)]        /* jumpTable[vaIndex] = pt entry */

    bl      mmu_setup                           /* set up the mmu | 核心映射表已經建立好了,此時可以啟動MMU工作了*/
#endif      
  • 第七步: 使能MMU, 有了頁表就可以使用虛拟位址了
mmu_setup:  //啟動MMU工作
    mov     r12, #0                             /* TLB Invalidate All entries - TLBIALL */
    mcr     p15, 0, r12, c8, c7, 0              /* Set c8 to control the TLB and set the mapping to invalid */
    isb
    mcr     p15, 0, r12, c2, c0, 2              /* Translation Table Base Control Register(TTBCR) = 0x0
                                                [31] :0 - Use the 32-bit translation system(虛拟位址是32位)
                                                [5:4]:0 - use TTBR0和TTBR1
                                                [2:0]:0 - TTBCR.N為0;
                                                例如:TTBCR.N為0,TTBR0[31:14-0] | VA[31-0:20] | descriptor-type[1:0]組成32位頁表描述符的位址,
                                                        VA[31:20]可以覆寫4GB的位址空間,是以TTBR0頁表是16KB,不使用TTBR1;
                                                例如:TTBCR.N為1,TTBR0[31:14-1] | VA[31-1:20] | descriptor-type[1:0]組成32位頁表描述符的位址,
                                                        VA[30:20]可以覆寫2GB的位址空間,是以TTBR0頁表是8KB,TTBR1頁表是8KB(頁表位址必須16KB對齊);
                                                */
    isb
    orr     r12, r4, #MMU_TTBRx_FLAGS           //将臨時頁表屬性[6:0]和基位址[31:14]放到r12
    mcr     p15, 0, r12, c2, c0, 0              /* Set attributes and set temp page table */
    isb
    mov     r12, #0x7                           /* 0b0111 */
    mcr     p15, 0, r12, c3, c0, 0              /* Set DACR with 0b0111, client and manager domian */
    isb
    mrc    p15, 0, r12, c1, c0, 1               /* ACTLR, Auxlliary Control Register */
    orr    r12, r12, #(1 << 6)                  /* SMP, Enables coherent requests to the processor. */
    orr    r12, r12, #(1 << 2)                  /* Enable D-side prefetch */
    orr    r12, r12, #(1 << 11)                 /* Global BP Enable bit */
    mcr    p15, 0, r12, c1, c0, 1               /* ACTLR, Auxlliary Control Register */
    dsb
    /*
    * 開始使能MMU,使用的是核心臨時頁表,這時cpu通路記憶體不管是取指令還是通路資料都是需要經過mmu來翻譯,
    * 但是在mmu使能之前cpu使用的都是核心的實體位址,即使現在使能了mmu,cpu通路的位址值還是核心的實體位址值(這裡僅僅從數值上來看),
    * 而又由于mmu使能了,是以cpu會把這個值當做虛拟位址的值到頁表中去找其對應的實體位址來通路。
    * 是以現在明白了為什麼要在核心臨時頁表裡建立一個核心實體位址和虛拟位址一一映射的頁表項了吧,因為建立了一一映射,
    * cpu通路的位址經過mmu翻譯得到的還是和原來一樣的值,這樣在cpu真正使用虛拟位址之前也能正常運作。
    */
    mrc     p15, 0, r12, c1, c0, 0
    bic     r12, #(1 << 29 | 1 << 28)           /* disable access flag[bit29],ap[0]是通路權限位,支援全部的通路權限類型
                                                disable TEX remap[bit28],使用TEX[2:0]與C Bbit控制memory region屬性 */
    orr     r12, #(1 << 0)                      /* mmu enable */
    bic     r12, #(1 << 1)
    orr     r12, #(1 << 2)                     /* D cache enable */
    orr     r12, #(1 << 12)                    /* I cache enable */
    mcr     p15, 0, r12, c1, c0, 0              /* Set SCTLR with r12: Turn on the MMU, I/D cache Disable TRE/AFE */
    isb
    ldr     pc,  =1f                            /* Convert to VA | 1表示标号,f表示forward(往下) - pc值取往下辨別符“1”的虛拟位址(跳轉到辨別符“1”處)
                                                因為之前已經在核心臨時頁表中建立了核心虛拟位址和實體位址的映射關系,是以接下來cpu切換到虛拟位址空間 */
    1:
        mcr     p15, 0, r8, c2, c0, 0               /* Go to the base address saved in C2: Jump to the page table */
        isb                                         //r8中儲存的是核心L1頁表基位址和flags,r8寫入到TTBR0實作臨時頁表和核心頁表的切換
        mov     r12, #0
        mcr     p15, 0, r12, c8, c7, 0              /* TLB Invalidate All entries - TLBIALL(Invalidate all EL1&0 regime stage 1 and 2 TLB entries) */
        isb
        sub     lr,  r11                            /* adjust lr with delta of physical address and virtual address | 
                                                    lr中儲存的是mmu使能之前傳回位址的實體位址值,這時需要轉換為虛拟位址,轉換算法也很簡單,虛拟位址 = 實體位址 - r11 */
        bx      lr                                  //傳回      
  • 第八步: 設定異常和中斷棧 ,初始化棧内值和棧頂值
//初始化棧内值
    ldr     r0, =__svc_stack        //stack_init的第一個參數 __svc_stack表示棧頂
    ldr     r1, =__exc_stack_top    //stack_init的第二個參數 __exc_stack_top表示棧底, 這裡會有點繞, top表高位址位
    bl      stack_init              //初始化各個cpu不同模式下的棧空間
    //設定各個棧頂魔法數字
    STACK_MAGIC_SET __svc_stack, #OS_EXC_SVC_STACK_SIZE, OS_STACK_MAGIC_WORD //中斷棧底設成"燙燙燙燙燙燙"
    STACK_MAGIC_SET __exc_stack, #OS_EXC_STACK_SIZE, OS_STACK_MAGIC_WORD     //異常棧底設成"燙燙燙燙燙燙"
stack_init:
    ldr     r2, =OS_STACK_INIT  //0xCACACACA
    ldr     r3, =OS_STACK_INIT
    /* Main loop sets 32 bytes at a time. | 主循環一次設定 32 個位元組*/
stack_init_loop:
    .irp    offset, #0, #8, #16, #24
    strd    r2, r3, [r0, \offset]    /* 等價于strd r2, r3, [r0, 0], strd r2, r3, [r0, 8], ... , strd r2, r3, [r0, 24] */
    .endr
    add     r0, #32         //加跳32個位元組,說明在位址範圍上 r1 > r0 ==> __exc_stack_top > __svc_stack
    cmp     r0, r1          //是否到棧底
    blt     stack_init_loop
    bx      lr
      
//初始化棧頂值
excstack_magic:
    mov     r3, #0 //r3 = 0
excstack_magic_loop:
    str     r2, [r0]   //棧頂設定魔法數字
    add     r0, r0, r1 //定位到棧底
    add     r3, r3, #1 //r3++
    cmp     r3, #CORE_NUM //棧空間等分成core_num個空間,是以每個core的棧頂需要magic num
    blt     excstack_magic_loop
    bx      lr
/* param0 is stack top, param1 is stack size, param2 is magic num */
.macro STACK_MAGIC_SET param0, param1, param2
    ldr     r0, =\param0
    mov     r1, \param1
    ldr     r2, =\param2
    bl      excstack_magic
.endm
STACK_MAGIC_SET __svc_stack, #OS_EXC_SVC_STACK_SIZE, OS_STACK_MAGIC_WORD //中斷棧底設成"燙燙燙燙燙燙"
STACK_MAGIC_SET __exc_stack, #OS_EXC_STACK_SIZE, OS_STACK_MAGIC_WORD     //異常棧底設成"燙燙燙燙燙燙"      
  • 第九步: 熱啟動
warm_reset: //熱啟動 Warm Reset, warm reboot, soft reboot, 在不關閉電源的情況,由軟體控制重新開機計算機
  /* initialize CPSR (machine state register) */
  mov    r0, #(CPSR_IRQ_DISABLE|CPSR_FIQ_DISABLE|CPSR_SVC_MODE) /* 禁止IRQ中斷 | 禁止FIQ中斷 | 管理模式-作業系統使用的保護模式 */
  msr    cpsr, r0

  /* Note: some functions in LIBGCC1 will cause a "restore from SPSR"!! */
  msr    spsr, r0

  /* get cpuid and keep it in r12 */
  mrc     p15, 0, r12, c0, c0, 5    //R12儲存CPUID 
  and     r12, r12, #MPIDR_CPUID_MASK //掩碼操作擷取目前cpu id

  /* set svc stack, every cpu has OS_EXC_SVC_STACK_SIZE stack */
  ldr    r0, =__svc_stack_top
  mov    r2, #OS_EXC_SVC_STACK_SIZE
  mul    r2, r2, r12
  sub    r0, r0, r2                   /* 算出目前core的中斷棧棧頂位置,寫入所屬core的sp */
  mov    sp, r0

  LDR    r0, =__exception_handlers
  MCR    p15, 0, r0, c12, c0, 0       /* Vector Base Address Register - VBAR */

  cmp    r12, #0
  bne    cpu_start                    //從核處理分支      
  • 第十步: 進入 C 語言的 main()
bl     main                //帶LR的子程式跳轉, LR = pc - 4, 執行C層main函數

LITE_OS_SEC_TEXT_INIT INT32 main(VOID)//由主CPU執行,預設0号CPU 為主CPU 
  {
      UINT32 ret = OsMain();
      if (ret != LOS_OK) {
          return (INT32)LOS_NOK;
      }

      CPU_MAP_SET(0, OsHwIDGet());//設定CPU映射,參數0 代表0号CPU

      OsSchedStart();//排程開始

      while (1) {
          __asm volatile("wfi");//WFI: wait for Interrupt 等待中斷,即下一次中斷發生前都在此hold住不幹活
      }
  }      

百文說核心 | 抓住主脈絡

  • 百文相當于摸出核心的肌肉和器官系統,讓人開始豐滿有立體感,因是直接從注釋源碼起步,在加注釋過程中,每每有心得處就整理,慢慢形成了以下文章。内容立足源碼,常以生活場景打比方盡可能多的将核心知識點置入某種場景,具有畫面感,容易了解記憶。說别人能聽得懂的話很重要! 百篇部落格絕不是百度教條式的在說一堆诘屈聱牙的概念,那沒什麼意思。更希望讓核心變得栩栩如生,倍感親切。
  • 與代碼需不斷​

    ​debug​

    ​​一樣,文章内容會存在不少錯漏之處,請多包涵,但會反複修正,持續更新,​

    ​v**.xx​

    ​ 代表文章序号和修改的次數,精雕細琢,言簡意赅,力求打造精品内容。
  • 百文在 < 鴻蒙研究站 | 開源中國 | 部落格園 | 51cto | csdn | 知乎 | 掘金 > 站點釋出,鴻蒙研究站 | weharmonyos中回複百文可友善閱讀。
  • v87.01 鴻蒙核心源碼分析 (核心啟動篇) | 從彙編到main() | 百篇部落格分析 OpenHarmony 源碼

按功能子產品:

基礎知識 程序管理 任務管理 記憶體管理
​​雙向連結清單​​​ ​​核心概念​​​ ​​源碼結構​​​ ​​位址空間​​​ ​​計時機關​​​ ​​優雅的宏​​​ ​​鈎子架構​​​ ​​位圖管理​​​ ​​POSIX​​​ ​​main函數​​ ​​排程故事​​​ ​​程序控制塊​​​ ​​程序空間​​​ ​​線性區​​​ ​​紅黑樹​​​ ​​程序管理​​​ ​​Fork程序​​​ ​​程序回收​​​ ​​Shell編輯​​​ ​​Shell解析​​ ​​任務控制塊​​​ ​​并發并行​​​ ​​就緒隊列​​​ ​​排程機制​​​ ​​任務管理​​​ ​​用棧方式​​​ ​​軟體定時器​​​ ​​控制台​​​ ​​遠端登入​​​ ​​協定棧​​ ​​記憶體規則​​​ ​​實體記憶體​​​ ​​記憶體概念​​​ ​​虛實映射​​​ ​​頁表管理​​​ ​​靜态配置設定​​​ ​​TLFS算法​​​ ​​記憶體池管理​​​ ​​原子操作​​​ ​​圓整對齊​​
通訊機制 檔案系統 硬體架構 核心彙編
​​通訊總覽​​​ ​​自旋鎖​​​ ​​互斥鎖​​​ ​​快鎖使用​​​ ​​快鎖實作​​​ ​​讀寫鎖​​​ ​​信号量​​​ ​​事件機制​​​ ​​信号生産​​​ ​​信号消費​​​ ​​消息隊列​​​ ​​消息封裝​​​ ​​消息映射​​​ ​​共享記憶體​​ ​​檔案概念​​​ ​​檔案故事​​​ ​​索引節點​​​ ​​VFS​​​ ​​檔案句柄​​​ ​​根檔案系統​​​ ​​挂載機制​​​ ​​管道檔案​​​ ​​檔案映射​​​ ​​寫時拷貝​​ ​​晶片模式​​​ ​​ARM架構​​​ ​​指令集​​​ ​​協處理器​​​ ​​工作模式​​​ ​​寄存器​​​ ​​多核管理​​​ ​​中斷概念​​​ ​​中斷管理​​ ​​編碼方式​​​ ​​彙編基礎​​​ ​​彙編傳參​​​ ​​連結腳本​​​ ​​核心啟動​​​ ​​程序切換​​​ ​​任務切換​​​ ​​中斷切換​​​ ​​異常接管​​​ ​​缺頁中斷​​
編譯運作 調測工具
​​編譯過程​​​ ​​編譯建構​​​ ​​GN文法​​​ ​​忍者無敵​​​ ​​ELF格式​​​ ​​ELF解析​​​ ​​靜态連結​​​ ​​重定位​​​ ​​動态連結​​​ ​​程序映像​​​ ​​應用啟動​​​ ​​系統調用​​​ ​​VDSO​​

百萬注源碼 | 處處扣細節

  • 百萬漢字注解核心目的是要看清楚其毛細血管,細胞結構,等于在拿放大鏡看核心。核心并不神秘,帶着問題去源碼中找答案是很容易上瘾的,你會發現很多文章對一些問題的解讀是錯誤的,或者說不深刻難以自圓其說,你會慢慢形成自己新的解讀,而新的解讀又會碰到新的問題,如此層層遞進,滾滾向前,拿着放大鏡根本不願意放手。

繼續閱讀