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

来源:https://www.cnblogs.com/weharmony/archive/2022/05/26/16312803.html
-Advertisement-
Play Games

本篇關鍵詞:內核重定位、MMU、SVC棧、熱啟動、內核映射表 內核彙編相關篇為: v74.01 鴻蒙內核源碼分析(編碼方式) | 機器指令是如何編碼的 v75.03 鴻蒙內核源碼分析(彙編基礎) | CPU上班也要打卡 v76.04 鴻蒙內核源碼分析(彙編傳參) | 如何傳遞複雜的參數 v77.01 ...


本篇關鍵詞:內核重定位、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 中回覆 百萬 可方便閱讀。

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

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 日期:2022年5月25日 註:本博客僅供參考 概念與思路 貪心演算法是指在對某一問題求解時,總是作出當前情況下的最優選擇。因此,貪心演算法考慮的不是整個問題的最優解,演算法得到的是在某一局部環境下的最優解。 貪心演算法的一般思路為: 把要求解的問題分為若幹個子問題; 對每個子問題求解,得到子問題的局部最優 ...
  • 我們一般啟用sql server資料庫要麼選擇安裝SQL Server實例和管理工具(SSMS),要麼用vs自帶的資料庫。如今net跨平臺成為趨勢,今天給大家介紹另一種我最近在玩的方式,即使用docker創建並啟用sql資料庫。 本章介紹了在window10專業版下如何利用docker創建mssql ...
  • 最近通過WPF開發項目,為了對WPF知識點進行總結,所以利用業餘時間,開發一個學生信息管理系統【Student Information Management System】。上一篇文章進行了框架搭建和模塊劃分,本文在前一篇基礎之上,繼續深入開發學生信息管理系統的資料庫和WebApi介面搭建相關內容,... ...
  • 1.前言 相信說起MongoDB很多人都知道是NoSql,非關係型之類的,但是需要註意 1.和傳統關係型資料庫Sqlserver、Mysql、Oracle相比,MongoDB身為非關係型資料庫,在數據存儲結構和數據查詢以及數據海量存儲上,擁有絕對的優勢,並且可以說它與關係型資料庫是互為優缺點,互補的 ...
  • 一 枚舉器和可枚舉類型 當我們為數組使用foreach語句時,這個語句為我們依次取出了數組中的每一個元素。 var arrInt = new int[] { 11, 12, 13, 14 }; foreach (var item in arrInt) { Console.WriteLine(item ...
  • 1.Docker基本介紹 Docker就是虛擬化的一種輕量級替代技術,基於Go語言的開源應用容器引擎。Docker的容器技術不依賴任何語言、框架或系統,可以將應用程式變成一種標準化的、可移植的、自管理的組件,並脫離伺服器硬體在任何主流系統中開發、調試和運行。 光看這個介紹還不足以知道Docker是什 ...
  • Linux查看系統硬體信息(2021.06.22) 1. CPU # 查看 cpu 的統計信息 $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian CPU(s): 64 On-li ...
  • 鏡像下載、功能變數名稱解析、時間同步請點擊 阿裡雲開源鏡像站 在VMware上搭建docker的時候報了Failed to start docker.service: Unit not found。查看了好多 博主的分享,但是因為圖片有限,不能確定是否問題一樣,查到這位博主的時候眼前一亮,一毛一樣啊!並且博 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...