Lec 19操作系统
Lec 19 操作系统
TL;DR
- 特权分级与资源抽象:操作系统基于硬件双模式(User/Supervisor)与特权 CSR 寄存器,为上层进程封装独立的虚拟机运行时与受保护的 ABI 契约。
- 精确异常与中断响应:区分同步异常(指令内生)与异步中断(外设请求),硬件负责原子冻结指令流、保存现场并跳转至受信任的内核处理入口。
- 抢占调度与系统调用:利用定时器中断驱动非协作式抢占调度,通过专用
ecall指令陷入内核实现安全的外设访问与环境模拟。
操作系统核心目标与特权分级
到目前为止我们见到的都是单程序机器:硬件运行一个程序,独占处理器、内存、磁盘、网卡、显示器、键盘等全部资源,软硬件之间唯一的契约就是 ISA(如 RISC-V),程序直接写到 ISA 上。但只有洗碗机控制芯片这类简单系统才这样工作。现实中我们同时运行多个程序,它们不直接碰硬件,而是用“文件”等更高层抽象。
本讲引入操作系统(OS):它介于多个程序与硬件之间,管理它们如何共享资源。这需要硬件提供专门支持(模式、异常、虚拟内存),是“让计算机好用”的关键一环。
本讲导览
- 为什么需要操作系统
- 程序 vs 进程;ISA vs ABI;虚拟机
- 支持 OS 的四项 ISA 扩展
- 异常与中断
- 异常处理机制
- 三个用例:调度 / 指令仿真 / 系统调用
- 进程生命周期与 CSR
为什么需要操作系统
OS 内核(kernel)是一个有特权的进程,存在三大理由:
- 保护与隔离(protection & privacy):不让进程访问/读取彼此的数据,即使某进程崩溃或有 bug。最好让每个进程以为自己独占一台机器。
- 抽象(abstraction):隐藏底层硬件细节,进程通过文件系统等抽象访问设备,而不必(也不该)直接操作硬盘等(直接访问还很危险)。
- 资源管理(resource management):把算力分配给重要进程,防止某进程独占 CPU 或内存。
于是出现两个接口:ISA 管硬件与 OS 之间,ABI(Application Binary Interface) 管 OS 与普通程序之间。
程序 vs 进程;虚拟机
- 程序(program):一组指令(你写的代码)。
- 进程(process):程序的一次运行实例——既含代码,也含寄存器、内存等运行时状态。(类比“类 vs 对象”“函数 vs 函数的一次运行”。)
OS 为每个进程提供三样虚拟化:私有地址空间(虚拟内存)、CPU 时间片(调度)、外设访问接口(系统调用)。这三者合起来,让每个进程都以为自己运行在一台独占的机器上——这就是**虚拟机(VM)**的抽象。
这个抽象层层嵌套:Lab 的 RISC-V 程序 → 跑在
sim.py(RISC-V 模拟器,一个 Python 程序)→ 跑在 CPython(Python VM)→ 跑在 Linux 内核(x86 VM)→ 可能又跑在 VMware(模拟整台 x86)→ ……可达五层不同类型的 VM。纯软件模拟(如 Python 解释器)很慢(比原生慢 10–100 倍),所以我们给处理器加硬件支持,让应用大多数时候直接用硬件,只在必要时让 OS 安全介入。
支持 OS 的四项 ISA 扩展
- 两种执行模式:用户模式(user)与管理模式(supervisor)。OS 内核跑在 supervisor,其它一切跑在 user。
- 特权状态与指令:某些寄存器/指令是特权的,只有 supervisor 模式能用。
- 异常与中断(exceptions & interrupts):当程序出错或有事件发生时,安全地从 user 切到 supervisor,让内核介入。
- 虚拟内存:提供私有地址空间、抽象存储资源(见 Lec 18)。
软硬件约定必须严格对齐,这套机制才成立。
异常与中断
异常(exception):应用自己处理不了、需要 OS 内核处理的事件(通常罕见或意外)。可把它看成一次隐式的“调用进 OS”:处理器跳到内核里的异常处理程序(handler),处理完通常把控制权交还给进程(在原指令 处继续);若是非法操作(如段错误)则终止该进程。
| 异常(狭义/同步) | 中断(异步) | |
|---|---|---|
| 来源 | 程序指令本身产生 | I/O 设备“按门铃” |
| 时机 | 同步(可预测到是哪条指令) | 异步(随时可能来) |
| 例子 | 非法指令、系统调用、除 0 | 键盘按键、定时器到期、磁盘传输完成 |
两词常混用;本课统称“异常”,把同步的叫“同步异常”。
异常处理机制
异常发生在指令 时,处理器需要:
- 完成 及之前的所有指令,但不完成 ;(单周期下很容易;到了流水线,同时有多条指令在飞,这会带来麻烦——见 Lec 16。)
- 保存现场:把当前 PC、异常原因等存入只有内核可访问的特权寄存器;
- 进入 supervisor 模式,跳转到一个预先指定的 handler PC(地址固定,程序不能跳到任意位置);
- 处理完后(若非致命)回到 继续——异常对进程是透明的。
这很像每条指令都额外具备“跳转到别处”的能力的 branch。
三个用例
用例 1:CPU 调度(靠定时器中断)
要限制每个进程的 CPU 时间,用一个可编程定时器(如经 MMIO)。内核把控制交给进程 1 前,设定时器在 ~20ms 后触发中断、载入进程 1 的状态、进入 user 模式;定时器中断到来时,CPU 跳到 handler,保存进程 1 状态、调度进程 2、重设定时器、载入进程 2 状态,如此往复。这种“不靠程序主动让出、而由 OS 强行切换”叫抢占式多任务(preemptive multitasking)。通过控制时间片长短,可按优先级调控各进程的 CPU 速度。
用例 2:指令仿真(靠非法指令异常)
可以“伪造”硬件里不存在的指令、用软件实现其功能。例如 mul(RISC-V M 扩展)在 RV32I 上是非法指令——一执行就抛非法指令异常,内核 handler 发现可以用软件(反复相加)模拟乘法,替进程算完,然后返回到该指令之后(避免再次触发)。于是 RV32I 机器“假装”支持 RV32IM。代价是慢得多(来回切换内核、存恢复状态)。
用例 3:系统调用(靠 ecall)
进程访问文件、调用其它系统服务要用系统调用——类似函数调用,但进入 OS。为防程序乱跳,用专门的、会触发异常的指令:RISC-V 用 ecall(environment call)。系统调用可用于开关/读写文件、网络(socket)、内存管理、查询系统信息、休眠/让出、创建/中断其它进程等。
程序通常不直接发系统调用,而是经低层库间接调用:如 C 的
printf内部调sys_write。有些系统调用会阻塞进程(如休眠、等文件读完),服务完成后再唤醒。
进程生命周期与 CSR
进程生命周期:创建 → 就绪(ready) → 被调度进/出执行(executing);系统调用可能阻塞它进入等待(waiting),唤醒后回到 ready;完成时用系统调用通知并被终止。OS 始终维护一张“所有进程及其状态(ready/executing/waiting)”的列表。
RISC-V 用一组特权的**控制状态寄存器(CSR, Control and Status Registers)**记录这些信息,例如:
mepc:异常 PC(出异常时的指令地址);mcause:异常原因;- 用
csrr/csrw读写 CSR,并有专门指令把控制权交回用户态。
系统调用的约定与函数调用类似(见 Lec 9 调用约定)。
下一步——“共享内存”的硬件支持,即虚拟内存,见 Lec 18。
我的理解
::: insight
精确异常(Precise Exception)契约与特权态切换的硬件屏障
异常与中断机制是现代多任务操作系统的基石,但其背后隐藏着极度严格的硬件微架构契约:
- 精确异常的不可侵犯性:在概念上,异常要求“异常指令之前的全部指令已完整提交其架构状态(如写回寄存器或内存),而异常指令本身及其后续的所有指令绝不可产生任何持久化的副作用”。在单周期处理器中这自然满足,但在乱序多发射或深流水线中,后续指令可能已经超前计算完毕。此时硬件必须维护重排序缓存(ROB)或临时的投机状态,在异常触发时强行撤回(Squash)所有乱序推测的指令,恢复出严格干净的串行架构现场。
- 特权切换与原子现场保存:当执行
ecall或发生外部中断时,硬件在单拍内执行一系列原子切换:将当前 PC 锁存至mepc、将触发原因编码入mcause、在mstatus中记录先前的特权级与中断使能位,并将当前特权模式升格为 Supervisor/Machine 态,最后强制将 PC 重定向至中断向量表基址(mtvec)。这一过程必须完全由硬件连线保证原子性,绝不能依赖任何软件代码,从而杜绝在模式提升的空隙中被恶意利用。 - 软硬件边界的正交解耦:RISC-V 的 CSR 体系证明了极简主义的设计美学——硬件仅负责提供原子捕捉、状态冻结与恢复(
mret)的底层原语,而诸如时间片轮转策略、缺页处理算法、进程控制块(PCB)的内存组织则完全交给内核软件调度。这种正交性为操作系统的高效演进提供了极其坚固的物理基础。 :::