批处理系统(Batch Processing System)是早期计算机系统的重要里程碑。它的核心思想是让管理员预先准备一组程序,计算机在执行完一个程序后自动执行下一个程序。这种设计极大地提高了计算机的使用效率,减少了人工干预的时间。
批处理系统的关键在于需要一个后台程序来管理前台程序的切换。这个后台程序负责在一个前台程序执行结束后,自动加载并执行下一个前台程序。从本质上说,这个后台程序就是操作系统的雏形。
批处理系统的主要优势在于减少了程序切换时的人工干预时间,使得计算机能够更连续地执行任务,提高了整体利用率。
在批处理系统中,操作系统和用户进程之间的切换需要一种受限的、可控的方式。这种切换不能简单地通过普通程序代码实现,因为:
为了实现这种受限的切换方式,硬件需要提供一种特殊的机制——异常响应机制。在RISC-V架构中,这通过自陷指令(如ecall)实现。执行自陷指令后,处理器会跳转到预先设置的异常入口地址,这个地址存储在mtvec寄存器中。
异常响应过程涉及三个关键状态寄存器:
当RISC-V处理器执行ecall指令时,硬件会执行以下操作序列:
异常处理完成后,处理器需要通过mret指令返回到原程序。mret指令会:
我们可以将处理器视为一个状态机,其状态S可以表示为:
S = {R, M}
其中:
在基础模型中,R = {GPR, PC},其中GPR是通用寄存器,PC是程序计数器。
为了支持异常处理,我们需要扩展状态机的寄存器部分:
R = {GPR, PC, SR}
其中SR(状态寄存器)包括:
一条指令是否会"失败"取决于如何定义"失败"。常见的失败条件包括:
值得注意的是,RISC-V架构不将除零视为异常条件,除法指令会按照手册定义正常执行,即使除数为零。
我们可以定义一个函数fex: S → {0,1}来判断当前指令是否会失败:
在操作系统中,上下文(context)指的是使程序能够从中断点恢复并继续执行的全部状态。这包括:
触发条件:
保存当前上下文:
切换地址空间(如需要):
恢复新上下文:
CTE定义了如下事件数据结构:
c复制typedef struct {
enum {
EVENT_NULL = 0,
EVENT_YIELD, EVENT_SYSCALL, EVENT_PAGEFAULT, EVENT_ERROR,
EVENT_IRQ_TIMER, EVENT_IRQ_IODEV,
} event;
uintptr_t cause, ref;
const char *msg;
} Event;
c复制bool cte_init(Context* (*handler)(Event ev, Context *ctx))
该函数接受一个事件处理回调函数,当事件发生时CTE会调用此回调。
c复制void yield()
触发EVENT_YIELD事件,不同ISA使用不同的自陷指令实现。
通过AM测试框架中的hello_intr测试触发异常:
c复制CASE('i', hello_intr, IOE, CTE(simple_trap));
测试程序主要逻辑:
c复制void hello_intr() {
printf("Hello, AM World @ " __ISA__ "\n");
printf(" t = timer, d = device, y = yield\n");
while (1) {
for (volatile int i = 0; i < 1000000; i++);
yield();
}
}
yield()通过内联汇编触发异常:
c复制void yield() {
#ifdef __riscv_e
asm volatile("li a5, -1; ecall");
#else
asm volatile("li a7, -1; ecall");
#endif
}
c复制cpu.mstatus = 0x00001800;
cpu.mepc = epc;
cpu.mcause = NO;
return cpu.mtvec;
在__am_asm_trap中:
simple_trap示例:
c复制Context *simple_trap(Event ev, Context *ctx) {
switch(ev.event) {
case EVENT_IRQ_TIMER: putch('t'); break;
case EVENT_IRQ_IODEV: putch('d'); break;
case EVENT_YIELD: putch('y'); break;
default: panic("Unhandled event"); break;
}
return ctx;
}
mepc的处理:
mstatus的设置:
寄存器保存顺序:
在实现异常处理机制时,有几个容易出错的地方值得特别注意:
上下文保存不完整:
异常号混淆:
栈指针管理:
测试策略:
在实际项目中,异常处理机制的稳定性直接影响整个系统的可靠性。建议通过以下方式提高代码质量: