RISC-V Linux的匯編啟動部分比較簡單,不算復雜。有兩個部分比較核心:頁表創建和重定向。頁表創建是用C語言寫的,今天先分析匯編部分,先帶大家分析整體匯編啟動流程,然后分析重定向。 注意:本文基于linux5.10.111內核 先從整體分析匯編做的事情,有個大體框架。 路徑: 從 登錄后復制 登錄后復制 登錄后復制 登錄后復制 登錄后復制 登錄后復制 登錄后復制 登錄后復制 登錄后復制 登錄后復制 登錄后復制 完整_start_kernel匯編代碼: 登錄后復制 匯編中非常重要的一個部分就是頁表的創建,關乎著后面的程序能不能繼續往下跑。setup_vm創建頁表后就會開始執行relocate重定向,這個重定向主要開啟mmu,下面分析relocate的匯編。 relocate重定向,就是在開啟mmu。開啟mmu的操作就是將一級頁表的地址以及權限寫到 登錄后復制 relocate有兩次開啟mmu的操作,第一次開啟mmu使用的是 如果 計算返回地址 返回地址就是 登錄后復制 將異常入口 因為一旦開啟MMU,地址都變成了虛擬地址,原來訪問的都是物理地址,開啟MMU時,地址發生了改變, 登錄后復制 再進入relocate之前,就已經把early_pg_dir賦值給a0了,所以a0是early_pg_dir。srl是邏輯右移,mmu使用的是sv39,虛擬地址39位,物理地址56位: 低12位是偏移量,所以 登錄后復制 登錄后復制 進入異常 完整relocate匯編代碼: 登錄后復制 以上就是RISC-V Linux的匯編啟動流程,雖說RISC-V的指令不復雜,但要理解這個匯編啟動的部分,還是需要一點基礎和時間。另外,大多數人工作中基本用不上匯編,只有真正用上了理解才會比較深。希望本文能夠幫助到有需要的人。
匯編啟動流程
arch/riscv/kernel/head.S
,入口是ENTRY(_start_kernel)
ENTRY(_start_kernel)
開始進行啟動前的一些初始化,建立頁表前的主要工作:
/* 關閉所有中斷 */
csrw CSR_IE, zero
csrw CSR_IP, zero
/* 加載全局指針gp */
.option push
.option norelax
la gp, __global_pointer$
.option pop
/* 禁用 FPU 以檢測內核空間中浮點的非法使用*/
li t0, SR_FS
csrc CSR_STATUS, t0
/* 選擇一個核啟動 */
la a3, hart_lottery
li a2, 1
amoadd.w a3, a2, (a3)
bnez a3, .Lsecondary_start
/* 清除bss */
la a3, __bss_start
la a4, __bss_stop
ble a4, a3, clear_bss_done
/* 保存hatr id和dtb地址,hart id保存到a0,dtb地址保存到a1 */
mv s0, a0
mv s1, a1
la a2, boot_cpu_hartid
la sp, init_thread_union + THREAD_SIZE
mv a0, s1
call setup_vm // 跳轉到C函數setup_vm,setup_vm會創建臨時頁表
#ifdef CONFIG_MMU
la a0, early_pg_dir
call relocate //重定向,實際就是開啟MMU
#endif
call setup_trap_vector
/* 重載C環境 */
la tp, init_task
sw zero, TASK_TI_CPU(tp)
la sp, init_thread_union + THREAD_SIZE
tail start_kernel
ENTRY(_start_kernel)
/* 關閉所有中斷 */
csrw CSR_IE, zero
csrw CSR_IP, zero
/* 在源碼中,這里有一個M模式處理的宏,這里沒有用到,直接跳過*/
/* 加載全局指針gp */
.option push
.option norelax
la gp, __global_pointer$
.option pop
/* 禁用 FPU 以檢測內核空間中浮點的非法使用*/
li t0, SR_FS
csrc CSR_STATUS, t0
#ifdef CONFIG_SMP
li t0, CONFIG_NR_CPUS
blt a0, t0, .Lgood_cores
tail .Lsecondary_park
.Lgood_cores:
#endif
/* 選擇一個核啟動 */
la a3, hart_lottery
li a2, 1
amoadd.w a3, a2, (a3)
bnez a3, .Lsecondary_start
/* 清除bss */
la a3, __bss_start
la a4, __bss_stop
ble a4, a3, clear_bss_done
clear_bss:
REG_S zero, (a3)
add a3, a3, RISCV_SZPTR
blt a3, a4, clear_bss
clear_bss_done:
/* 保存hatr id和dtb地址,hart id保存到a0,dtb地址保存到a1 */
mv s0, a0
mv s1, a1
la a2, boot_cpu_hartid
REG_S a0, (a2)
/* 初始化頁表,然后重定向到虛擬地址 */
la sp, init_thread_union + THREAD_SIZE
mv a0, s1
call setup_vm // 跳轉到C函數setup_vm,setup_vm會創建臨時頁表
#ifdef CONFIG_MMU
la a0, early_pg_dir
call relocate //重定向,實際就是開啟MMU
#endif /* CONFIG_MMU */
call setup_trap_vector
/* 重載C環境 */
la tp, init_task
sw zero, TASK_TI_CPU(tp)
la sp, init_thread_union + THREAD_SIZE
#ifdef CONFIG_KASAN
call kasan_early_init
#endif
/* Start the kernel */
call soc_early_init
tail start_kernel //跳轉到C函數start_kernel,開始C語言部分初始化
relocate
satp
寄存器中,這就算開啟mmu了。#ifdef CONFIG_MMU
la a0, early_pg_dir //跳轉到relocate前,先把第一級頁表early_pg_dir的地址存入a0
call relocate //跳轉到relocate,開啟MMU
#endif
setup_vm()
建立的trampoline_gd_dir
頁表,這頁表保存的是kernel
的前2M
內存。第二次開啟MMU使用的是early_pg_dir
頁表,這個頁表映射了整個kernel內存以及dtb
的4M空間。trampoline_pg_dir
或者early_pg_dir
這兩個頁表的映射沒弄好的話,開啟MMU的時候就會失敗,所以頁表的建立十分關鍵。頁表創建后續再深究,下面分析relocate匯編代碼。
ra
加上虛擬地址和物理地址之間的偏移量,這個是固定偏移量。PAGE_OFFSET
是kernel
入口地址對應的虛擬地址,_start
就是kernel
入口地址的虛擬地址,PAGE_OFFSET
– _start
就得到它們之間的偏移,然后再和ra相加,就是返回地址。/* Relocate return address */
li a1, PAGE_OFFSET
la a2, _start
sub a1, a1, a2
add ra, ra, a1
1f
的虛擬地址寫入stvec
寄存器VA != PA
,從而進入異常,所以要先設置異常入口地址,此時的異常入口為1f
。/* Point stvec to virtual address of intruction after satp write */
la a2, 1f
add a2, a2, a1
csrw CSR_TVEC, a2
early_pg_dir
頁表要寫入satp
的值PAGE_SHIFT
等于12,將early_pg_dir
地址右移12位存到a2
。根據satp寄存器定義:MODE
等于0x8
代表使用sv39 mmu
,0x0
代表不進行地址翻譯,即不開啟MMU
。這里STAP_MODE
為sv39
,即0x8
。將early_pg_dir
地址和SATP_MODE
進行或運算后,即可得到寫入satp
寄存器的值,最后保存到a2
。/* Compute satp for kernel page tables, but don't load it yet */
srl a2, a0, PAGE_SHIFT
li a1, SATP_MODE //sv39 mmu
or a2, a2, a1
satp
值的計算和上述是一樣的。開啟MMU
之前,通過sfence.vma
命令先刷新TLB
。此時開啟MMU
,就會進入下面的標號為1
的匯編段 la a0, trampoline_pg_dir
srl a0, a0, PAGE_SHIFT
or a0, a0, a1
sfence.vma
csrw CSR_SATP, a0
1f
段,重新設置異常入口為.Lsecondary_park
,然后切換到early_pg_dir
頁表,相當于第二次開啟MMU。此時,如果之前建立的early_pg_dir
頁表不對,則會就進入.Lsecondary_park
。.Lsecondary_park
里面是個wfi
指令,是個死循環。relocate:
/* Relocate return address */
li a1, PAGE_OFFSET
la a2, _start
sub a1, a1, a2
add ra, ra, a1
/* Point stvec to virtual address of intruction after satp write */
la a2, 1f
add a2, a2, a1
csrw CSR_TVEC, a2
/* Compute satp for kernel page tables, but don't load it yet */
srl a2, a0, PAGE_SHIFT
li a1, SATP_MODE
or a2, a2, a1
/*
* Load trampoline page directory, which will cause us to trap to
* stvec if VA != PA, or simply fall through if VA == PA. We need a
* full fence here because setup_vm() just wrote these PTEs and we need
* to ensure the new translations are in use.
*/
la a0, trampoline_pg_dir
srl a0, a0, PAGE_SHIFT
or a0, a0, a1
sfence.vma
csrw CSR_SATP, a0
.align 2
1:
/* Set trap vector to spin forever to help debug */
la a0, .Lsecondary_park
csrw CSR_TVEC, a0
/* Reload the global pointer */
.option push
.option norelax
la gp, __global_pointer$
.option pop
/*
* Switch to kernel page tables. A full fence is necessary in order to
* avoid using the trampoline translations, which are only correct for
* the first superpage. Fetching the fence is guarnteed to work
* because that first superpage is translated the same way.
*/
csrw CSR_SATP, a2
sfence.vma
ret
總結
以上就是RISC-V Linux匯編啟動過程分析的詳細內容,更多請關注www.92cms.cn其它相關文章!