本文基于你的
riscv32 + NEMU + AM代码。
环境路径:NEMU:~/ysyx-workbench/nemu AM:~/ysyx-workbench/abstract-machine
一、多道程序要解决什么问题
批处理系统是:
程序 A 结束 -> 加载程序 B多道程序更进一步:
内存里同时放多个程序 CPU 在它们之间来回切换这样:
一个程序在等待 IO 时 CPU 可以去执行另一个程序多道程序的核心就是三个动作:
1. 保存当前程序现场 2. 选择下一个程序 3. 恢复下一个程序现场二、你当前代码总览
ysyx-workbench ├── nemu NEMU 模拟器 ├── abstract-machine AM 抽象层 ├── am-kernels AM 内核/测试 ├── fceux-am ├── npc └── 没有 nanos-lite所以:
底层上下文切换机制:已有 操作系统 nanos-lite:还没有下面逐个分析“已有”和“缺失”。
三、上下文 Context 的定义
文件:
abstract-machine/am/include/arch/riscv.hstruct Context { uintptr_t mepc, mcause, gpr[NR_REGS], mstatus; void *pdir; };字段含义:
| 字段 | 作用 |
|---|---|
mepc | 恢复后从哪里继续执行 |
mcause | 异常/中断原因 |
gpr[] | 通用寄存器现场 |
mstatus | 机器状态 |
pdir | 页目录,用于虚拟内存切换 |
上下文就是“一个进程暂停时,把 CPU 所有状态抄下来”。
四、上下文保存与恢复:trap.S
文件:
abstract-machine/am/src/riscv/nemu/trap.S__am_asm_trap: addi sp, sp, -CONTEXT_SIZE MAP(REGS, PUSH) csrr t0, mcause csrr t1, mstatus csrr t2, mepc STORE t2, OFFSET_EPC(sp) STORE t0, OFFSET_CAUSE(sp) STORE t1, OFFSET_STATUS(sp) mv a0, sp call __am_irq_handle mv sp, a0 LOAD t1, OFFSET_STATUS(sp) LOAD t2, OFFSET_EPC(sp) csrw mstatus, t1 csrw mepc, t2 MAP(REGS, POP) addi sp, sp, CONTEXT_SIZE mret关键点
mv sp, a0如果__am_irq_handle返回的是另一个进程的 Context,那么下面恢复的就是另一个进程的现场。
mret最后跳回新进程的mepc。
这就是进程切换的底层基础。
五、异常处理入口:cte.c
文件:
abstract-machine/am/src/riscv/nemu/cte.c1. 注册异常入口
bool cte_init(Context*(*handler)(Event, Context*)) { asm volatile("csrw mtvec, %0" : : "r"(__am_asm_trap)); user_handler = handler; return true; }2. 异常分发
Context* __am_irq_handle(Context *c) { if (user_handler) { Event ev = {0}; switch (c->mcause) { case 11: if (c->gpr[17] == -1) { ev.event = EVENT_YIELD; } else { ev.event = EVENT_SYSCALL; } c->mepc += 4; break; case 0x80000007: ev.event = EVENT_IRQ_TIMER; break; default: ev.event = EVENT_ERROR; break; } c = user_handler(ev, c); assert(c != NULL); } return c; }3. 创建内核上下文
Context *kcontext(Area kstack, void (*entry)(void *), void *arg) { Context *c = (Context *)((uint8_t *)kstack.end - sizeof(Context)); memset(c, 0, sizeof(Context)); c->mepc = (uintptr_t)entry; c->mstatus = 0x1800; c->gpr[10] = (uintptr_t)arg; return c; }4. 主动让出 CPU
void yield() { asm volatile("li a7, -1; ecall"); }六、你现在的事件处理函数
文件:
abstract-machine/am/src/platform/nemu/trm.cstatic Context *event_handler(Event ev, Context *ctx) { switch (ev.event) { case EVENT_YIELD: break; case EVENT_SYSCALL: if (ctx->GPRx == 0) { halt(ctx->GPR1); } break; case EVENT_IRQ_TIMER: break; default: panic("Unhandled event"); break; } return ctx; }问题
case EVENT_YIELD: break;它只是空处理,最后:
return ctx;返回的还是原来的上下文。
所以现在:
能进入异常 但没有真正切换进程七、虚拟内存 VME 的现状
文件:
abstract-machine/am/src/riscv/nemu/vme.c已有部分
bool vme_init(...) { ... set_satp(kas.ptr); vme_enable = 1; return true; }void protect(AddrSpace *as) { PTE *updir = (PTE*)(pgalloc_usr(PGSIZE)); as->ptr = updir; as->area = USER_SPACE; as->pgsize = PGSIZE; memcpy(updir, kas.ptr, PGSIZE); }缺失部分
void map(AddrSpace *as, void *va, void *pa, int prot) { } Context *ucontext(AddrSpace *as, Area kstack, void *entry) { return NULL; }所以:
用户地址空间映射:未实现 用户上下文创建:未实现八、多处理器 MPE 现状
文件:
abstract-machine/am/src/platform/nemu/mpe.cint cpu_count() { return 1; } int cpu_current() { return 0; }当前是:
单核PA4 的多道程序先只做单核即可。
九、定时器中断已经打通
文件:
nemu/src/device/intr.cvoid dev_raise_intr() { cpu.pc = isa_raise_intr(0x80000007, cpu.pc); }链路:
定时器 -> timer_intr -> dev_raise_intr -> isa_raise_intr(0x80000007) -> pc = mtvec这就是抢占式调度的硬件基础。
十、多道程序切换的完整流程
进程 A 运行中 ↓ 定时器中断 / yield ↓ 进入 trap ↓ 保存进程 A 的 Context ↓ __am_irq_handle 产生 Event ↓ 操作系统调度器选择进程 B ↓ 返回进程 B 的 Context ↓ 恢复进程 B 的现场 ↓ mret 跳到进程 B 继续运行对应代码:
| 步骤 | 代码 |
|---|---|
| 进入异常 | isa_raise_intr |
| 保存现场 | trap.S |
| 异常分发 | __am_irq_handle |
| 选择下一个进程 | 缺:nanos-lite 调度器 |
| 返回新 Context | 缺:nanos-lite |
| 恢复并返回 | trap.S / mret |
十一、代码完成度总结表
| 功能 | 现状 |
|---|---|
Context结构体 | ✅ 已有 |
cte_init | ✅ 已有 |
yield | ✅ 已有 |
kcontext | ✅ 已有 |
__am_irq_handle | ✅ 已有 |
trap.S保存/恢复 | ✅ 已有 |
| 定时器中断 | ✅ 已接通 |
ucontext | ❌ 未实现 |
map | ❌ 未实现 |
| 调度器 | ❌ 没有 nanos-lite |
| PCB 进程控制块 | ❌ 未实现 |
| 用户程序加载器 | ❌ 未实现 |
十二、PA4 你需要补的代码
1. 初始化 nanos-lite 工程 2. 定义 PCB(进程控制块) 3. 实现用户程序加载器 4. 实现 AM 的 ucontext() 5. 实现 AM 的 map() 6. 实现 nanos-lite 的调度器 7. 调度器调用 kcontext / ucontext 8. 定时器中断触发抢占调度 9. 系统调用:yield、exit、write十三、总结
多道程序 =:
保存现场 + 选择下一个进程 + 恢复现场你现在的状态是:
保存现场:✅ 已有 恢复现场:✅ 已有 选择下一个进程:❌ 还没有所以 PA4 的核心任务,不是重写底层,而是:
在 nanos-lite 中实现调度器和进程管理底层上下文切换的“积木”已经准备好了