1. 搭建RISC-V特权级模拟环境

搭建RISC-V特权级模拟环境 学习卡片

任务目标

本案例在QEMU模拟的RISC-V 64位环境中,配置处理器支持M-Mode与U-Mode,实现从M-Mode到U-Mode的首次切换,并验证切换后CPU正确运行在U-Mode下。这是后续实现系统调用、页表隔离等特权级功能的基础框架。

环境依赖

  • QEMU(支持RISC-V 64位,如 qemu-system-riscv64)
  • RISC-V GNU工具链(riscv64-unknown-elf-gcc)
  • Make(可选,用于构建)

核心实现

以下代码包含完整的最小可运行实现:启动时CPU处于M-Mode,代码设置U-Mode的入口地址与初始栈,然后执行mret指令切换到U-Mode。U-Mode下通过一个自旋循环保持运行,便于在QEMU监控台中观察当前特权级。

/* mode_switch.S - 最小特权级切换实现 */
.section .text.init
.globl _start

_start:
    /* 设置M-Mode异常向量(简单处理:直接跳转到自身) */
    la t0, _start
    csrw mtvec, t0

    /* 设置U-Mode入口地址为 u_mode_entry */
    la t0, u_mode_entry
    csrw mepc, t0

    /* 设置U-Mode初始栈指针(使用独立栈区域) */
    la sp, u_stack_top

    /* 设置mstatus:清除MPP(设为U-Mode),保留MPIE=0 */
    li t0, 0x00001800   /* MPP字段位[12:11],清零表示U-Mode */
    csrc mstatus, t0

    /* 切换到U-Mode */
    mret

/* U-Mode入口:自旋循环,便于观察 */
u_mode_entry:
    j u_mode_entry

/* U-Mode栈空间 */
.section .bss
.align 4
.space 4096
u_stack_top:
/* linker.ld - 链接脚本 */
OUTPUT_ARCH(riscv)
ENTRY(_start)

SECTIONS
{
    . = 0x80000000;
    .text : { *(.text.init) *(.text) }
    .bss : { *(.bss) }
}
# Makefile - 构建与运行
CROSS = riscv64-unknown-elf-
CFLAGS = -march=rv64gc -mabi=lp64 -nostdlib -fno-builtin

all: mode_switch.elf

mode_switch.elf: mode_switch.S linker.ld
	$(CROSS)gcc $(CFLAGS) -T linker.ld -o $@ mode_switch.S

run: mode_switch.elf
	qemu-system-riscv64 -machine virt -cpu rv64 -nographic -bios none -kernel mode_switch.elf

clean:
	rm -f mode_switch.elf

运行与验证

  1. 构建:执行 make 生成 mode_switch.elf。
  2. 运行:执行 make run 启动QEMU。
  3. 验证:在QEMU监控台中按 Ctrl+A 然后按 C 进入monitor,执行 info registers 查看 mstatus 寄存器。

通过条件:观察到 mstatus 的 MPP 字段(位[12:11])为 0,且 pc 指向 u_mode_entry 地址(即 0x80000000 附近的代码段内)。由于程序在U-Mode自旋,CPU不会产生异常或退出,说明切换成功且U-Mode代码正常执行。

关键说明

  • QEMU的 -bios none 参数确保CPU直接从 0x80000000 开始执行,此时处于M-Mode。
  • mret 指令将 mepc 值载入 pc,并将特权级设置为 mstatus.MPP 指定的模式(此处为U-Mode)。
  • U-Mode下无法访问M-Mode的CSR,因此自旋循环不涉及任何特权操作,保证不会触发非法指令异常。
  • 若需观察更明确的切换证据,可在 u_mode_entry 中写入一个已知值到内存地址(如 0x80001000),然后在QEMU monitor中用 xp 命令查看该地址内容。