1. 建立最小特权级环境
任务目标
本案例在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
运行与验证
- 构建:执行
make生成mode_switch.elf。 - 运行:执行
make run启动QEMU。 - 验证:在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命令查看该地址内容。