v87.01 鴻蒙內核源碼分析 (內核啟動篇) | 從彙編到 main () | 百篇部落格分析 OpenHarmony 源碼
本篇關鍵詞:內核重定位、MMU、SVC棧、熱啟動、內核映射表
內核彙編相關篇為:
- v74.01 鴻蒙內核源碼分析(編碼方式) | 機器指令是如何編碼的
- v75.03 鴻蒙內核源碼分析(彙編基礎) | CPU上班也要打卡
- v76.04 鴻蒙內核源碼分析(彙編傳參) | 如何傳遞複雜的參數
- v77.01 鴻蒙內核源碼分析(鏈接腳本) | 正在製作中 …
- v78.01 鴻蒙內核源碼分析(內核啟動) | 從彙編到main()
- v79.01 鴻蒙內核源碼分析(進程切換) | 正在製作中 …
- v80.03 鴻蒙內核源碼分析(任務切換) | 看彙編如何切換任務
- v81.05 鴻蒙內核源碼分析(中斷切換) | 系統因中斷活力四射
- v82.06 鴻蒙內核源碼分析(異常接管) | 社會很單純 複雜的是人
- v83.01 鴻蒙內核源碼分析(缺頁中斷) | 正在製作中 …
這應該是系列篇最難寫的一篇,全是彙編程式碼,需大量的底層知識,涉及協處理器,內核鏡像重定位,創建內核映射表,初始化 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 它提供了系統的最高級別控制,高到了玉皇大帝級別,程式碼中將
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 是系統定義的記憶體地址, 可由系統集成商指定配置, 他們希望內核從這裡運行。 下圖為內核鏡像布局
具體程式碼如下:/* 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 中回復 百文 可方便閱讀。
按功能模組:
百萬注源碼 | 處處扣細節
-
百萬漢字註解內核目的是要看清楚其毛細血管,細胞結構,等於在拿放大鏡看內核。內核並不神秘,帶著問題去源碼中找答案是很容易上癮的,你會發現很多文章對一些問題的解讀是錯誤的,或者說不深刻難以自圓其說,你會慢慢形成自己新的解讀,而新的解讀又會碰到新的問題,如此層層遞進,滾滾向前,拿著放大鏡根本不願意放手。
-
< gitee | github | coding | gitcode > 四大碼倉推送 | 同步官方源碼,鴻蒙研究站 | weharmonyos 中回復 百萬 可方便閱讀。
據說喜歡點贊分享的,後來都成了大神。😃