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

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

內核彙編相關篇為:

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

內核入口

在鏈接文件 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 中回復 百萬 可方便閱讀。

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