4. 实现U模式系统调用触发S模式异常
任务目标
在RISC-V M-Mode下完成中断向量表(mtvec)的初始化,实现一个中断服务程序(ISR),使其能够处理来自U-Mode触发的软件中断(MSIP),并在处理完成后正确返回到U-Mode继续执行。
必要环境与依赖
- RISC-V 64位(RV64)或32位(RV32)工具链(如riscv64-unknown-elf-gcc)
- QEMU模拟器(支持RISC-V virt机器)或等效硬件平台
- 无需外部库,直接操作CSR寄存器
核心实现
以下代码在M-Mode下完成中断向量表设置,并通过ecall指令从U-Mode触发软件中断。中断服务程序读取mcause确认中断来源,清除软件中断挂起位,然后通过mret返回U-Mode。
#include <stdint.h>
// CSR寄存器地址定义
#define CSR_MTVEC 0x305
#define CSR_MSTATUS 0x300
#define CSR_MCAUSE 0x342
#define CSR_MIP 0x344
#define CSR_MIE 0x304
// 软件中断位(MSIP对应MIP的第3位)
#define MIP_MSIP (1 << 3)
// 中断使能函数
static inline void csr_write(uint64_t csr, uint64_t value) {
__asm__ volatile ("csrw %0, %1" : : "i"(csr), "r"(value));
}
static inline uint64_t csr_read(uint64_t csr) {
uint64_t value;
__asm__ volatile ("csrr %0, %1" : "=r"(value) : "i"(csr));
return value;
}
// 中断服务程序(M-Mode)
__attribute__((interrupt))
void m_trap_handler(void) {
uint64_t mcause = csr_read(CSR_MCAUSE);
// 检查是否为软件中断(mcause[63] = 1 表示中断,低6位为3表示软件中断)
if ((mcause & 0x8000000000000000ULL) && ((mcause & 0x3F) == 3)) {
// 清除软件中断挂起位(写入MIP寄存器)
uint64_t mip = csr_read(CSR_MIP);
mip &= ~MIP_MSIP;
csr_write(CSR_MIP, mip);
// 设置返回地址,跳过ecall指令继续执行
uint64_t mepc = 0;
__asm__ volatile ("csrr %0, 0x341" : "=r"(mepc));
mepc += 4; // 跳过ecall指令(4字节)
__asm__ volatile ("csrw 0x341, %0" : : "r"(mepc));
}
// 其他中断类型可在此扩展
}
// U-Mode软件中断触发函数
void trigger_software_interrupt(void) {
__asm__ volatile ("ecall");
}
// 主函数
int main(void) {
// 1. 设置中断向量表地址(M-Mode)
csr_write(CSR_MTVEC, (uint64_t)&m_trap_handler);
// 2. 使能M-Mode全局中断和软件中断
uint64_t mstatus = csr_read(CSR_MSTATUS);
mstatus |= (1 << 3); // MIE位置1
csr_write(CSR_MSTATUS, mstatus);
uint64_t mie = csr_read(CSR_MIE);
mie |= MIP_MSIP; // 使能软件中断
csr_write(CSR_MIE, mie);
// 3. 切换到U-Mode并触发软件中断
// 设置返回地址为中断触发函数
uint64_t mepc = (uint64_t)&trigger_software_interrupt;
__asm__ volatile ("csrw 0x341, %0" : : "r"(mepc));
// 设置MPP为U-Mode(00),然后执行mret切换到U-Mode
mstatus = csr_read(CSR_MSTATUS);
mstatus &= ~(3 << 11); // 清除MPP位
mstatus |= (0 << 11); // MPP = 00 (U-Mode)
csr_write(CSR_MSTATUS, mstatus);
__asm__ volatile ("mret");
// 中断处理完成后会返回到这里(U-Mode)
// 验证:如果到达此处,说明中断处理成功并正确返回
volatile int success = 1;
// 死循环,防止程序退出
while(1) {
// 空循环
}
return 0;
}
编译与运行
# 编译(RV64)
riscv64-unknown-elf-gcc -march=rv64gc -mabi=lp64d -nostdlib -nostartfiles -Ttext=0x80000000 -o interrupt_test.elf interrupt_test.c
# 使用QEMU运行
qemu-system-riscv64 -machine virt -kernel interrupt_test.elf -nographic
验证方法与判定标准
- 编译验证:上述编译命令应无错误通过,生成
interrupt_test.elf文件。 - 运行验证:在QEMU中运行后,程序应进入
while(1)死循环且不崩溃。若在调试器中设置断点于success = 1处,应能命中该断点,证明中断处理完成并正确返回U-Mode。 - 中断路径验证:在
m_trap_handler入口处设置断点,运行后应能观察到程序进入该处理函数,且mcause值为0x8000000000000003(表示M-Mode软件中断)。 - 返回验证:单步执行
mret后,mstatus.MPP应恢复为U-Mode(00),程序计数器应指向trigger_software_interrupt之后的指令(即ecall的下一条指令)。
关键说明
- 中断向量表采用直接跳转模式(
mtvec.MODE = 0),即mtvec直接存放处理函数地址。 - U-Mode通过
ecall触发环境调用,在M-Mode下表现为软件中断(mcause = 3)。 - 处理软件中断时需清除
mip.MSIP位,否则中断会持续触发。 - 返回U-Mode前需调整
mepc跳过ecall指令(RV64下为4字节),否则会陷入无限中断循环。 - 本实现使用
__attribute__((interrupt))确保编译器生成正确的保存/恢复上下文代码,无需手动保存寄存器。