插件窝 干货文章 RISC-V Linux汇编启动过程分析

RISC-V Linux汇编启动过程分析

启动 初始化 设置 寄存器 92    来源:    2025-04-13

RISC-V Linux汇编启动过程分析

RISC-V架构的Linux启动过程从汇编代码开始,经过多个阶段最终进入C语言环境。以下是详细分析:

1. 启动入口点

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

2. 早期CPU初始化

head.S中会进行以下关键初始化:

_start:
    /* 禁用中断 */
    csrw sie, zero

    /* 设置异常处理基地址 */
    la t0, trap_vector
    csrw stvec, t0

    /* 初始化页表 */
    call setup_vm
    call relocate

    /* 启用MMU */
    call enable_mmu

3. 页表初始化

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

4. 跳转到C代码

汇编启动的最后阶段是准备C语言环境并跳转到start_kernel

ENTRY(start_kernel)
    /* 设置最终堆栈指针 */
    la sp, init_thread_union + THREAD_SIZE

    /* 清零帧指针 */
    mv fp, zero

    /* 传递设备树地址 */
    mv a0, s1  /* s1保存了设备树物理地址 */

    /* 跳转到C语言的主函数 */
    tail start_kernel

5. 关键寄存器初始化

在启动过程中会初始化以下关键CSR寄存器:

  • stvec: 设置异常处理入口
  • satp: 控制地址转换和页表
  • sie: 控制中断使能
  • sstatus: 控制CPU状态

6. SMP启动流程

对于多核处理器,从核的启动流程如下:

ENTRY(smpboot)
    /* 从核会从这里开始执行 */
    call setup_trap_vector
    call enable_mmu

    /* 获取CPU ID */
    csrr a0, mhartid

    /* 跳转到C语言的从核启动函数 */
    tail smp_secondary_start

7. 异常处理初始化

早期异常处理设置:

trap_vector:
    /* 保存寄存器 */
    SAVE_ALL

    /* 调用C语言异常处理 */
    csrr a0, scause
    csrr a1, sepc
    mv a2, sp
    call handle_exception

    /* 恢复寄存器 */
    RESTORE_ALL
    sret

8. 与设备树的交互

启动过程中会解析设备树获取硬件信息:

    /* 设备树物理地址通常由bootloader传入 */
    mv a0, s1  /* 设备树地址 */
    call early_init_dt_scan

关键点总结

  1. RISC-V启动从_start开始,首先设置最小运行环境
  2. 早期使用恒等映射的页表,后期会重新初始化
  3. 异常处理在汇编中设置,但主要逻辑在C语言中实现
  4. 多核启动有专门的处理流程
  5. 设备树信息在早期就被解析和使用

启动过程最终会跳转到Linux内核的C语言入口start_kernel(),完成后续初始化工作。