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

語言: CN / TW / HK

本篇關鍵詞:核心重定位、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 它提供了系統的最高級別控制,高到了玉皇大帝級別,程式碼中將 0212位寫 0。對應關閉 MMU資料快取指令快取 功能。

  • 第三步: 對浮點運算FPU的設定,在安全模式下使用FPU,須定義NSACRCPACRFPEXC 三個暫存器

  • 第四步: 計算虛擬地址和實體地址的偏移量,為何要計算它呢 ? 主要目的是為了建立虛擬地址和實體地址的對映關係,因為在 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 是系統定義的記憶體地址, 可由系統整合商指定配置, 他們希望核心從這裡執行。 下圖為核心映象佈局 具體程式碼如下:

        /* 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 中回覆 百文 可方便閱讀。

按功能模組:

基礎知識 程序管理 任務管理 記憶體管理
雙向連結串列 核心概念 原始碼結構 地址空間 計時單位 優雅的巨集 鉤子框架 點陣圖管理 POSIX main函式 排程故事 程序控制塊 程序空間 線性區 紅黑樹 程序管理 Fork程序 程序回收 Shell編輯 Shell解析 任務控制塊 併發並行 就緒佇列 排程機制 任務管理 用棧方式 軟體定時器 控制檯 遠端登入 協議棧 記憶體規則 實體記憶體 記憶體概念 虛實對映 頁表管理 靜態分配 TLFS演算法 記憶體池管理 原子操作 圓整對齊
通訊機制 檔案系統 硬體架構 核心彙編
通訊總覽 自旋鎖 互斥鎖 快鎖使用 快鎖實現 讀寫鎖 訊號量 事件機制 訊號生產 訊號消費 訊息佇列 訊息封裝 訊息對映 共享記憶體 檔案概念 檔案故事 索引節點 VFS 檔案控制代碼 根檔案系統 掛載機制 管道檔案 檔案對映 寫時拷貝 晶片模式 ARM架構 指令集 協處理器 工作模式 暫存器 多核管理 中斷概念 中斷管理 編碼方式 彙編基礎 彙編傳參 連結指令碼 核心啟動 程序切換 任務切換 中斷切換 異常接管 缺頁中斷
編譯執行 調測工具
編譯過程 編譯構建 GN語法 忍者無敵 ELF格式 ELF解析 靜態連結 重定位 動態連結 程序映像 應用啟動 系統呼叫 VDSO 模組監控 日誌跟蹤 系統安全 測試用例

百萬注原始碼 | 處處扣細節

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

  • < gitee | github | coding | gitcode > 四大碼倉推送 | 同步官方原始碼,鴻蒙研究站 | weharmonyos 中回覆 百萬 可方便閱讀。

據說喜歡點贊分享的,後來都成了大神。:)

「其他文章」