本篇關鍵詞:核心重定位、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 它提供了系統的最高階別控制,高到了玉皇大帝級別,程式碼中將 0
、2
、12
位寫 0
。對應關閉 MMU 、資料快取 、指令快取 功能。
第三步: 對浮點運算FPU
的設定,在安全模式下使用FPU
,須定義NSACR
、CPACR
、FPEXC
三個暫存器
第四步: 計算虛擬地址和實體地址的偏移量,為何要計算它呢 ? 主要目的是為了建立虛擬地址和實體地址的對映關係,因為在 MMU啟動之後,執行地址(PC暫存器指向的地址)將變成虛擬地址,使用虛擬地址就離不開對映表,所以兩個地址的對映關係需要在MMU啟動前就建立好,而有了偏移量就可以建立對映表。但需先搞清楚 連結地址 和 執行地址 兩個概念。
兩個地址往往不一樣,而核心設計者希望它們是一樣的,那有沒有辦法檢測二者是否一樣呢? 答案是 : 當然有的 ,通過一個變數在連結時將其連結地址變成變數的內容 ,無論中間怎麼載入變數的內容是不會變的,而獲取執行地址是很容易獲取的,其實就是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
代表文章序號和修改的次數,精雕細琢,言簡意賅,力求打造精品內容。按功能模組:
百萬漢字註解核心目的是要看清楚其毛細血管,細胞結構,等於在拿放大鏡看核心。核心並不神祕,帶著問題去原始碼中找答案是很容易上癮的,你會發現很多文章對一些問題的解讀是錯誤的,或者說不深刻難以自圓其說,你會慢慢形成自己新的解讀,而新的解讀又會碰到新的問題,如此層層遞進,滾滾向前,拿著放大鏡根本不願意放手。
< gitee | github | coding | gitcode > 四大碼倉推播 | 同步官方原始碼,鴻蒙研究站 | weharmonyos 中回覆 百萬 可方便閱讀。
據說喜歡點贊分享的,後來都成了大神。