RISC-V架构的Linux启动过程从汇编代码开始,经过多个阶段最终进入C语言环境。以下是详细分析:
RISC-V Linux内核的汇编启动代码通常位于arch/riscv/kernel/head.S
文件中。启动入口点为_start
符号。
.section ".text.head"
ENTRY(_start)
/* 设置初始堆栈指针 */
la sp, init_thread_union + THREAD_SIZE
/* 清除BSS段 */
call clear_bss
/* 跳转到主启动函数 */
tail start_kernel
在head.S
中会进行以下关键初始化:
_start:
/* 禁用中断 */
csrw sie, zero
/* 设置异常处理基地址 */
la t0, trap_vector
csrw stvec, t0
/* 初始化页表 */
call setup_vm
call relocate
/* 启用MMU */
call enable_mmu
RISC-V使用Sv39或Sv48分页模式,早期页表初始化在汇编中完成:
setup_vm:
/* 加载页表基地址 */
la t0, early_pg_dir
/* 设置虚拟地址到物理地址的恒等映射 */
/* 每个页表项包含PPN(物理页号)和标志位 */
li t1, (PTE_V | PTE_R | PTE_W | PTE_X | PTE_A | PTE_D)
slli t2, t1, 10 /* 构建页表项 */
/* 填充页表 */
sw t2, 0(t0)
/* 设置satp寄存器 */
li t3, (SATP_MODE & SATP_MODE_SV39)
srli t0, t0, PAGE_SHIFT
or t0, t0, t3
csrw satp, t0
ret
汇编启动的最后阶段是准备C语言环境并跳转到start_kernel
:
ENTRY(start_kernel)
/* 设置最终堆栈指针 */
la sp, init_thread_union + THREAD_SIZE
/* 清零帧指针 */
mv fp, zero
/* 传递设备树地址 */
mv a0, s1 /* s1保存了设备树物理地址 */
/* 跳转到C语言的主函数 */
tail start_kernel
在启动过程中会初始化以下关键CSR寄存器:
stvec
: 设置异常处理入口satp
: 控制地址转换和页表sie
: 控制中断使能sstatus
: 控制CPU状态对于多核处理器,从核的启动流程如下:
ENTRY(smpboot)
/* 从核会从这里开始执行 */
call setup_trap_vector
call enable_mmu
/* 获取CPU ID */
csrr a0, mhartid
/* 跳转到C语言的从核启动函数 */
tail smp_secondary_start
早期异常处理设置:
trap_vector:
/* 保存寄存器 */
SAVE_ALL
/* 调用C语言异常处理 */
csrr a0, scause
csrr a1, sepc
mv a2, sp
call handle_exception
/* 恢复寄存器 */
RESTORE_ALL
sret
启动过程中会解析设备树获取硬件信息:
/* 设备树物理地址通常由bootloader传入 */
mv a0, s1 /* 设备树地址 */
call early_init_dt_scan
_start
开始,首先设置最小运行环境启动过程最终会跳转到Linux内核的C语言入口start_kernel()
,完成后续初始化工作。