news 2026/10/14 17:27:40

【一生一芯 / PA4】多道程序:上下文切换的代码基础与实现缺口(完整分析)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【一生一芯 / PA4】多道程序:上下文切换的代码基础与实现缺口(完整分析)

本文基于你的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.h
struct 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.c

1. 注册异常入口

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.c
static 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.c
int cpu_count() { return 1; } int cpu_current() { return 0; }

当前是:

单核

PA4 的多道程序先只做单核即可。


九、定时器中断已经打通

文件:

nemu/src/device/intr.c
void 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 中实现调度器和进程管理

底层上下文切换的“积木”已经准备好了

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 17:27:12

高数值孔径(NA)如何决定显微镜成像分辨率与荧光信号质量

1. 高数值孔径不是玄学——它定义了显微镜的天花板1.1 数值孔径到底在描述什么搞过显微镜的人都知道,做成像实验就像撮合“光学”“样品”“探测器”三方的默契。很多时候换了高级样品,用了最新款相机,图像依然不理想,最后查下来&…

作者头像 李华
网站建设 2026/10/14 17:25:19

企业培训系统如何接入业务流程?拆解EduSoho的AI应用与集成思路

企业上线培训系统,通常希望解决三件事:让员工快速找到岗位知识,让培训任务能够持续执行,让管理者知道接下来应该辅导谁、改进什么。从系统建设角度看,这涉及内容管理、组织人员、学习任务、业务练习和数据分析。只把课…

作者头像 李华
网站建设 2026/10/14 17:24:22

RAG文档解析与切片:决定检索上限的地基工程

文档解析与切片,在 RAG 项目里经常被一句话带过:“就是个预处理嘛。”但这两步才是真正决定检索上限的地方。这篇是这个系列的第二篇,不聊大框架,专门聚焦解析和切片,聊聊为什么说它们是地基,以及怎么才能把…

作者头像 李华
网站建设 2026/10/14 17:24:05

铌酸锂薄膜在高速光通信与量子光学中的突破应用

1. 项目背景与核心价值铌酸锂(LiNbO3)薄膜作为集成光子学领域的关键材料,近年来在高速光通信和量子光学系统中展现出独特优势。传统体材料铌酸锂虽然具有优异的电光系数(r33≈30 pm/V),但其与硅基工艺的兼容性差、器件尺寸大等问题…

作者头像 李华
网站建设 2026/10/14 17:23:43

Qt 5不再自带FTP库?QFtp模块编译与集成全攻略

1. 编译前必须搞懂的背景:Qt 5 里为什么不带 FTP 库了 我在接手某跨平台桌面项目时,需要处理上位机与远程设备之间的文件传输。第一反应是直接用 Qt 自带的网络模块,翻遍 Qt 5 的文档目录后才发现:QFtp 类从 Qt 5.0 开始就被彻底移…

作者头像 李华