操作系统这门课,我在不同阶段完整过了三遍:大二为了期末、大三为了408、后来读研又陪着学弟学妹复盘了一遍。三次下来最深的体会是,它根本不是一门"背多分"的课。真正拉开差距的,是你能不能把进程调度、地址翻译、页面置换、索引结点这几条计算链路闭着眼推出来,而不是看到"操作系统"四个字就先想到一堆名词解释。这份笔记最早是照着王道那套单科书和讲义一句句抄的,抄完发现还是不会做题,于是索性推翻重写,改成"骨架层 + 公式层 + 题眼层"的三层结构:先搭知识地图,再锁死可手算的公式,最后把每道题埋的坑单独标出来。下面这些内容适合三类人——正在准备408的、下周就要考操作系统的、以及想把这门课真正吃透而不是应付考试的人。整篇围绕进程管理、内存管理、文件系统与I/O四条主线展开,每条主线都给出可复现的手算套路和我自己踩过的坑,能直接拿去对答案的那种。
1. 先把"考什么"钉死:操作系统复习无效的三种典型姿势
1.1 为什么把书看三遍,大题还是写不出来
我见过太多人,包括曾经的我,复习操作系统的方式就是"从头读到尾,读完再读一遍"。这种读法的产物是:选择题眼熟得很快,一到大题就卡壳。原因不复杂——选择题考的是"你认不认识这个词",大题考的是"你能不能走完一条完整的推理链路"。比如"分页存储管理",选择题只问你页表存什么,大题却要你从逻辑地址出发,拆页号、查页表、拼物理地址,中间还夹一个TLB命中判断和缺页处理。你光知道"页表是页号到物理块号的映射",这条链路一步都走不动。
所以我的第一个建议很直接:复习操作系统时,凡是能用计算链路串起来的知识点,一律不许用阅读的方式过,必须动手把链路写一遍。进程调度要写时间轴表格,页面置换要画页框快照,地址翻译要写二进制拆位,文件索引要列求和式。写一次的记忆强度,顶得上读五遍。王道那本书的课后大题就是为这个过程准备的,不做题等于没学。
1.2 408和期末考对同一知识点的权重完全不同
这是很多人栽的第二个跟头:拿期末考的复习思路去应付408,或者反过来。两者重合度大概七成,但重心差得很远。我整理了一张对照表,复习前先看清自己在准备哪一场。
| 知识点 | 期末考常见权重 | 408常见权重 | 建议策略 |
|---|---|---|---|
| 进程与线程概念、状态转换 | 高 | 中 | 记判定规则即可,不必深挖 |
| 调度算法手算 | 中 | 高 | 必须能画时间轴、算带权周转 |
| PV操作与同步互斥 | 高 | 高 | 两类考试都躲不开,重点中的重点 |
| 死锁与银行家算法 | 中 | 高 | 安全性序列必须手推熟练 |
| 分页/分段地址计算 | 中 | 高 | 多级页表和TLB是高频大题 |
| 页面置换算法 | 高 | 高 | 缺页率统计要能画表 |
| 文件索引结点计算 | 低 | 高 | 期末常略过,408几乎年年考 |
| 磁盘调度算法 | 中 | 中 | 会算磁头移动总量就够 |
| I/O控制方式对比 | 高 | 中 | 适合做表格记忆 |
提示:如果你的目标是期末,把表格里"期末考权重"高而"408权重"低的行优先吃透;如果是408,把索引结点、多级页表这两块单独拎出来加练,它们是最容易被低估的失分点。
1.3 笔记的三层结构:骨架层、公式层、题眼层
抄书式笔记最大的问题是"信息密度均匀",每一页看起来都同样重要,结果考前一翻全是字,抓不住重点。我后来改成了三层:
- 骨架层:每个章节只写一页纸的框架图,用层级缩进列出"这一章要解决什么问题、分成哪几块、每块的输出是什么"。比如内存管理这一章,骨架就四行:地址翻译、内存分配、虚拟内存、页面置换。
- 公式层:把所有需要代入数字的公式集中在一处,写明每个符号的含义和单位。这一层是给你在考场上"抄作业"用的,必须背到条件反射。
- 题眼层:记录每类题型的触发词。看到"求平均周转时间"就自动切到"完成时间减到达时间";看到"主存访问时间"就自动分清是否含TLB时间;看到"最大文件长度"就自动切到"直接块加各级间接块求和"。
这三层分开记的好处是:考前三天你只需要刷题眼层和公式层,考前一天快速扫一遍骨架层,效率比从头翻书高一个数量级。
2. 进程与线程这条主线:状态、调度与"谁发起"的判定逻辑
2.1 五状态转换题的判定口诀:看谁主动
进程状态转换是选择题的常客,也是最容易靠"感觉"做错的一类。我的判定口诀只有一句:判断发起方是进程自己,还是操作系统或外部事件。
- 就绪到运行:由调度程序发起,进程被动。
- 运行到就绪:时间片用完或被更高优先级抢占,进程被动,仍具备运行条件。
- 运行到阻塞:进程自己主动发起,比如请求I/O、申请资源、等待信号量。
- 阻塞到就绪:外部事件完成(I/O结束、资源可用、信号量V操作),进程被动。
- 阻塞到运行:不存在,必须经就绪中转。
这套口诀的威力在于排除法。题目里只要出现"阻塞到运行"或者"就绪到阻塞",直接判错,不用犹豫。另一个高频陷阱是"运行到阻塞"和"运行到就绪"的区别:前者进程失去了CPU并且不再具备运行条件,后者进程失去CPU但随时可以被再次调度。这个区别在后面的调度算法和响应时间计算里会反复用到。
顺带提一句,挂起状态(就绪挂起、阻塞挂起)属于外存换入换出的范畴,判定逻辑和上面一致,只是多了"是否在内存"这一维。看到"挂起"两个字,先问自己:它在内存里吗?
2.2 调度算法的手算模板:两种周转时间必须分清楚
调度大题的核心只有两个公式:
- 周转时间 = 完成时间 − 到达时间
- 带权周转时间 = 周转时间 ÷ 服务时间
等待时间 = 周转时间 − 服务时间。这三个量算错一个,后面全崩。我一般先把时间轴画成一条横线,按顺序标出每个进程的起止时刻,再回头填表,比直接在脑子里排序稳得多。
拿一组数据练手,四个进程的到达时间和服务时间如下。
| 进程 | 到达时间 | 服务时间 |
|---|---|---|
| A | 0 | 7 |
| B | 1 | 4 |
| C | 2 | 1 |
| D | 4 | 4 |
先算FCFS(先来先服务):A在0到7,B在7到11,C在11到12,D在12到16。周转时间依次为7、10、10、12,平均9.75;带权周转为1、2.5、10、3,平均4.125。
再算非抢占式SJF(短作业优先):t=0时只有A到达,只能先跑A;t=7时B、C、D都已到达,选服务时间最短的C(1),跑7到8;接着B和D服务时间同为4,按到达先后选B,跑8到12;最后D跑12到16。周转时间依次为7、11、6、12,平均9;带权周转为1、2.75、6、3,平均3.19。
对比一下就很清楚:SJF把平均带权周转从4.125压到3.19,但代价是长作业D的等待被拖长,这就是典型的"饿死"风险的来源。考试里如果题目问"哪种算法对短作业有利",答案就是它。
HRRN(高响应比优先)的响应比公式是**(等待时间 + 服务时间)÷ 服务时间**,也就是 1 + 等待时间 ÷ 服务时间。它是非抢占式的,每次调度前重新算一遍所有就绪进程的响应比,选最大的。这个算法的妙处在于兼顾了长作业——等得越久,响应比越高,不会无限期饿死。
时间片轮转(RR)在纸上推演时最容易乱,我的做法是:画一个就绪队列,时间片用完的进程排到队尾,新到达的进程按到达时刻插入队尾,然后一格一格推。别图快,一次推错就得重来。
2.3 上下文切换、系统调用、中断:三个概念的边界在哪
这三个词在选择题里经常混在一起考,但它们的归属层级完全不同。
中断是硬件层面的机制,分为内中断(异常)和外中断。内中断由当前执行的指令引起,比如除零、缺页、系统调用;外中断来自CPU外部,比如时钟中断、I/O完成中断。注意一个反直觉的点:系统调用本质上是内中断(陷阱),它并不是"外中断"。
系统调用是用户程序请求操作系统服务的唯一入口。凡是涉及资源分配、I/O、进程控制的操作,用户程序都做不了,必须通过系统调用陷入内核态。典型的系统调用包括fork、read、write、exec、wait。
上下文切换保存的是处理机现场,包括程序计数器、寄存器、栈指针等。这里有个高频考点:进程切换一定会引起上下文切换,但上下文切换不一定意味着进程切换。同一进程内从用户态切到内核态,也需要保存和恢复现场,但它不改变当前运行的进程。
2.4 进程与线程到底共享什么
线程是调度的基本单位,进程是资源分配的基本单位,这句话谁都背过,但落到"共享什么"上就有人含糊。我列个表,比死记硬背靠谱。
| 资源 | 同进程内的线程 | 不同进程之间 |
|---|---|---|
| 地址空间 | 共享 | 独立 |
| 全局变量、堆 | 共享 | 独立 |
| 栈、寄存器、程序计数器 | 各自独立 | 独立 |
| 打开的文件、信号量 | 共享 | 独立 |
| 进程ID | 共享 | 不同 |
一句话记法:除了栈、寄存器和PC,其他基本都共享。这也是为什么多线程编程里,局部变量天然安全、全局变量必须加锁的根本原因——不是语言的规定,是内存模型决定的。
3. 同步互斥与死锁:PV操作从"看得懂"到"写得对"
3.1 信号量题型的四类模板
PV操作是整张卷子里最能体现功力的一类题。我把见过的题目归成四类模板,背模板比背题目有效得多。
第一类是纯互斥。临界区访问共享变量,标准写法是 mutex 初值1,进入前 P(mutex),退出后 V(mutex)。注意P和V必须成对,且V一定在临界区外面——V放错位置是新手最常犯的错,会导致"锁没解开"或者"提前放行"。
第二类是前后驱同步。题目描述"A完成后B才能开始",就在A末尾 V(s)、在B开头 P(s),s初值0。多条依赖链就开多个信号量,一一对应。
第三类是生产者-消费者。标准三信号量写法:mutex=1(互斥访问缓冲区),empty=n(空槽位数),full=0(已有产品数)。生产者的顺序是 P(empty) → P(mutex) → 放入 → V(mutex) → V(full);消费者的顺序是 P(full) → P(mutex) → 取出 → V(mutex) → V(empty)。这里有个铁律:资源信号量(empty/full)必须在互斥信号量(mutex)之前P,反过来写会死锁。原因很简单,如果先占住mutex再等empty,缓冲区满时生产者抱着锁睡觉,消费者永远进不来解这个锁。
第四类是读者-写者。核心是加一个计数器 count 记录当前读者数,第一个读者进来时给写者上锁,最后一个读者离开时解锁。写法是:读者侧 P(mutex) → count++ → 若count==1则P(rw) → V(mutex);读完后再 P(mutex) → count−− → 若count==0则V(rw) → V(mutex)。写者侧直接 P(rw) … V(rw)。
3.2 经典模型怎么改:变体题的加工思路
考试不会原封不动考经典模型,一定加条件。常见改法有三种。
第一种是缓冲区容量变化。原来是n个槽位,改成1个,那就退化成单缓冲,empty和full初值分别为1和0。改成无限容量,可以直接去掉empty信号量——因为永远有空位,不需要等待。
第二种是增加同步顺序约束。比如要求"必须先取再放""同一类操作不能连续超过三次"。这类题的解法是引入额外的计数信号量或者状态标记,把"次数"当成一个需要互斥修改的共享变量,改完再判断是否需要阻塞。
第三种是多类角色加配额。比如"最多允许两个读者同时读"。这时候除了mutex和rw,还要加一个初值为2的读者并发信号量,每个读者进出时P/V它一次。这类题的关键是别把"数量限制"和"互斥"混为一谈——前者用计数信号量,后者用初值1的信号量。
我自己的做法是:拿到题先把所有角色列出来,标出哪些是"资源的消耗者"、哪些是"资源的生产者"、哪些之间是互斥关系、哪些之间是先后关系,画成一张小图,再逐个映射到信号量。这比直接上手写代码快得多。
3.3 死锁判定与银行家算法的手算流程
死锁的四个必要条件是互斥、不可剥夺、请求并保持、循环等待。注意它们是"必要条件",不是充分条件,所以选项中如果出现"满足这四个条件就一定死锁",那是错的。破坏其中任意一个就能预防死锁:破坏请求并保持用资源预分配,破坏不可剥夺用强制回收,破坏循环等待用资源有序分配。
银行家算法的步骤是固定的,照着走不会错:
- 算 Need 矩阵,Need = Max − Allocation。
- 把 Available 作为初始 Work,把所有进程的 Finish 置为 false。
- 在未完成的进程里找一个 Need 小于等于 Work 的,分配给它,执行完回收资源,Work += Allocation,Finish 置 true。
- 重复第3步,直到所有进程完成(存在安全序列),或者找不到可满足的进程(处于不安全状态)。
举个简单的例子,三个进程P0、P1、P2,Available = [3, 3, 2],Need 分别是 [7,4,3]、[1,2,2]、[6,0,0]。先看P1,Need [1,2,2] ≤ [3,3,2],满足,P1执行完回收 Allocation [2,0,0],Work 变成 [5,3,2]。再看P2,Need [6,0,0] 不满足;看P0,Need [7,4,3] 也不满足。于是再找——这一轮P2和P0都过不去,说明当前Work下没有可执行进程,当前状态不安全。这就是一个典型的"第一轮能找到、第二轮卡住"的陷阱。
注意:安全性算法里找的是"Need ≤ Work"的进程,方向和大小关系千万别写反,很多人在这里丢分。另外安全序列可能不唯一,题里只要写出一个就行。
3.4 我在PV操作上踩过的三个坑
第一个坑是信号量初值给错。初值代表"一开始有多少个可用资源",互斥信号量永远是1,同步信号量要看题目描述的状态。我曾经把 empty 的初值写成0、full 写成n,结果整道大题的生产者和消费者操作全部反了,后续计算连锁崩盘。现在我养成一个习惯:写完初值先自查一遍——"系统初始时刻,缓冲区是空的、可以放n个",那 empty 就该是n。
第二个坑是P/V顺序颠倒。前面说的"资源信号量先于互斥信号量"这条铁律,我至少栽过两次。后来我在草稿纸上写的时候,会强行按"先资源、后互斥,先V互斥、后V资源"的口诀排一遍,基本不会再错。
第三个坑是忘了互斥保护计数器。读者-写者模型里的 count 是共享变量,多个读者同时进来修改它必须加锁,很多人只记得给写者上 rw 锁,忘了 count 自己的 mutex,导致"两个读者同时判断 count==1"这种经典错误。
4. 内存管理:从地址翻译到页面置换的完整计算链
4.1 地址翻译的统一公式与位运算技巧
分页存储的地址翻译,所有题目都可以用同一套流程解决:
- 页面大小为 L,逻辑地址为 A;
- 页号 P = A ÷ L(整除),页内偏移 W = A mod L;
- 查页表得到物理块号 b;
- 物理地址 = b × L + W。
关键在于,当 L 是2的整数次幂时,除法取余全部退化成位运算:页号就是地址的高位,偏移就是低 log₂L 位。页面大小4KB,即2¹²,那么偏移占低12位,页号是剩下的高位。
举个具体的例子。32位地址、页面4KB、逻辑地址 0x00002F3A。低12位是偏移,0xF3A = 3898;高20位是页号,0x2 = 2。查页表第2项,假设对应的物理块号是5,物理地址就是 5 × 4096 + 3898 = 20480 + 3898 = 24378,换成十六进制是 0x5F3A。你会发现,物理地址就是把页号部分替换成物理块号,低12位原封不动。
分段存储的逻辑不同:段号加段内偏移,段表里存的是段基址和段长。这里必须做越界检查——偏移量大于段长就产生越界中断。段页式则是先查段表得到页表首址,再查页表得到物理块号,两次查表,访问内存次数增加两次。
4.2 多级页表:页目录项的偏移怎么算
单级页表最大的问题是连续存储:32位地址空间、页面4KB,页表项4B,那页表本身就占 2²⁰ × 4B = 4MB,而且必须连续。多级页表就是为了解决这个问题。
拆位方法很机械。32位地址、页面4KB,偏移占12位,剩下20位是页号。如果用两级页表,每级10位:一级页号(页目录索引)10位,二级页号10位,页内偏移12位。一级页目录有 2¹⁰ = 1024 项,每个二级页表也有1024项,都正好占一页(1024 × 4B = 4KB),完美对齐。
多级页表的核心优势是"按需分配":一级页目录必须常驻,但二级页表只在用到时才创建。一个进程如果只用了很少的虚拟地址空间,二级页表可能只有一两张,占用远小于4MB。同时每个页表刚好一页大小,不存在"必须连续分配大块内存"的问题。
三级、四级页表的拆位方法完全相同,只是把页号位数继续均分。做题时先算总页号位数,再看题目给几级,除一下就是每级位数。这里有个小陷阱:如果页号位数除不尽,多出来的位要放在最靠近页内偏移的那一级(也就是低位的页表级)。这个细节在真题里出现过,按"从低位往高位分"的思路就不会错。
4.3 TLB与有效访问时间
TLB(快表)是页表项的高速缓存。带TLB的访问时间计算有一个基本模型:设TLB访问时间为 t,内存访问时间为 m,TLB命中率为 p。
- 命中时:访问TLB得到物理块号,再访问一次内存取数据,时间 = t + m。
- 未命中时:访问TLB(未命中)+ 访问内存中的页表 + 访问内存取数据,时间 = t + m + m = t + 2m。
平均有效访问时间 EAT = p × (t + m) + (1 − p) × (t + 2m)。
如果题目说"TLB访问时间忽略不计",那就直接套 m + (1−p) × m。这里最容易错的是"是否需要额外访问内存查页表"——只有未命中时才需要,命中时不用。我见过不少人把两种情况都算成 t + 2m,结果整题失分。
如果题目里还有缺页的情形,那就再多一层:缺页时需要从磁盘调入页面,时间要加到整个链路上,同时别忘了缺页处理完还要重新执行一次指令。这类题的公式会长一点,但思路一致:把每条路径的概率乘时间加起来。
4.4 页面置换算法与缺页率统计表
页面置换的核心是"物理块不够时淘汰谁"。三种主流算法要能手工推演:
- OPT(最佳置换):淘汰未来最长时间不再使用的页面。考试里只有它需要看"未来",用来算理论下界。
- FIFO(先进先出):淘汰在内存中驻留最久的页面。实现简单,但存在 Belady 异常——分配的物理块数增加,缺页次数反而可能上升。这是唯一会有这种异常的算法,是选择题的高频考点。
- LRU(最近最久未使用):淘汰最长时间未被访问的页面,用栈或者访存时间戳实现,性能接近OPT但开销大。
经典例题:页面访问串为 7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1,物理块数为3。用OPT缺页9次,LRU缺页12次,FIFO缺页15次。这三个数字建议直接记下来,考场上用来校验自己的推演结果——如果你算FIFO只缺了10次,那一定是某一格淘汰对象挑错了。
做这类题我有一个固定的表格模板:横着写访问序列,竖着写物理块的编号,每一格填当前块里的页面号,换页时把被淘汰的页面划掉并标一个记号。必须用笔写,不许在脑子里推,因为一旦超过十步,人脑的短期记忆一定出错。
CLOCK算法和改进型CLOCK(二次机会)的推演规则也比较常考:每个页面配一个访问位,指针顺时针扫描,遇到访问位为1就清零并跳过,遇到0就淘汰。改进型还要看修改位——优先淘汰"访问位0、修改位0"的页面,因为修改过的页面换出时需要写回磁盘,代价更高。
4.5 几个反直觉的结论
虚拟内存这块有几个结论,第一遍学的时候会觉得违反直觉,考试偏偏爱考。
第一,虚拟内存的容量不受物理内存和地址位数限制,而是受CPU寻址范围限制。32位机器的虚拟地址空间上限就是4GB,物理内存装多少都不影响这个上限。
第二,程序不需要全部装入内存就能运行,这是虚拟内存的局部性原理决定的。时间局部性指刚访问过的数据很可能马上再被访问,空间局部性指访问了某个地址,附近的地址很可能被访问。
第三,抖动(颠簸)是"多给点内存"解决不了的。抖动指的是页面频繁换入换出,CPU大量时间花在换页上。根源是多道程序度过高、每个进程分到的物理块太少。解决办法是引入工作集模型,根据程序近期的访问集合动态分配物理块,必要时降低多道程序度。
第四,页表项里同时存了有效位和访问位,有效位标记该页是否在内存中,这直接对应缺页中断的判断。缺页中断属于内中断,且它发生在指令执行过程中,处理完要重新执行该指令——不是从下一条开始。
5. 文件系统与I/O:选择题密度最高、最容易被跳过的一章
5.1 索引结点与文件最大长度的求和套路
这一块是408的高频计算题。题目一般这样给:物理块大小为4KB,每个地址项占4B,索引结点采用混合索引,包含10个直接地址项、1个一级间接、1个二级间接、1个三级间接,求文件最大长度。
第一步先算一个索引块能装多少地址项:4KB ÷ 4B = 1024 项。
然后逐层求和:
| 层级 | 计算式 | 容量 |
|---|---|---|
| 10个直接地址项 | 10 × 4KB | 40KB |
| 1个一级间接 | 1024 × 4KB | 4MB |
| 1个二级间接 | 1024 × 1024 × 4KB | 4GB |
| 1个三级间接 | 1024³ × 4KB | 4TB |
| 合计 | — | 约 40KB + 4MB + 4GB + 4TB |
这套数字几乎每年都会以某种形式出现,记住"4KB、4B、1024、40KB、4MB、4GB、4TB"这条链,考场上现推也就一分钟。
还有一个变体问法:访问文件某个位置的数据,需要读几次磁盘。原则是看这个位置落在哪个区间:落在直接地址范围内,读一次索引结点(如果已在内存则不需要)+ 读一次数据块;落在一级间接范围,需要先读一级索引块,再读数据块。这类题的坑在于"索引结点是否已在内存",题目一般会说明,没说明就按最坏情况算。
5.2 磁盘访问时间与调度算法
磁盘访问时间由三部分组成:
- 寻道时间:磁头移动到目标磁道所需时间,是最主要的部分。
- 旋转延迟:目标扇区转到磁头下方所需时间,平均值为半个旋转周期。
- 传输时间:读写数据本身的时间,通常可以忽略。
转速换算要熟练。7200转/分,一圈用时 60 ÷ 7200 = 8.33ms,平均旋转延迟 = 8.33 ÷ 2 ≈ 4.17ms。如果题目给的是15000转/分,一圈就是4ms,平均旋转延迟2ms。这个换算常年出现在选择题里。
磁盘调度算法和它们的磁头移动总量,用经典例题过一遍最有效。设磁头初始在100号磁道,请求序列为 55, 58, 39, 18, 90, 160, 150, 38, 184。
- FCFS:按请求先后顺序走,移动量 = 45+3+19+21+72+70+10+112+146 = 498。
- SSTF(最短寻道优先):100→90→58→55→39→38→18→150→160→184,移动量 = 10+32+3+16+1+20+132+10+24 = 248。
- SCAN(电梯算法,先向大):100→150→160→184,然后掉头到90→58→55→39→38→18,移动量 = 50+10+24+94+32+3+16+1+20 = 250。
- CSCAN(循环扫描):100→184 后直接回到最小请求18再往大扫,移动量 = 50+10+24+166+20+1+16+3+32 = 322。
SSTF的移动量最小,但它有"饥饿"问题,热点区域的请求会一直插队。SCAN和CSCAN都避免了饥饿,SCAN的移动量略大于SSTF,CSCAN因为多了"空扫回程"所以更大。做题时一定要先看清磁头当前移动方向,方向反了,整题答案就全错。
5.3 缓冲与I/O控制方式:用一张表解决
I/O控制方式从低级到高级依次是:程序直接控制、中断驱动、DMA、通道。它们解决的核心问题都是"如何减少CPU在I/O上的开销"。
| 方式 | 数据单位 | CPU干预频率 | 特点 |
|---|---|---|---|
| 程序直接控制 | 字(字节) | 极高,全程轮询 | CPU与设备串行工作 |
| 中断驱动 | 字(字节) | 高,每字一次中断 | CPU与设备可并行,但中断频繁 |
| DMA | 块 | 低,每块一次中断 | 数据直接进内存,不经CPU |
| 通道 | 一组块 | 极低 | 通道是独立处理机,执行通道程序 |
其中DMA和中断驱动的区别是高频考点:DMA以数据块为单位,中断驱动以字节为单位;DMA在传输过程中不需要CPU干预,只在开始和结束时需要。另外DMA请求的是总线使用权,不经过CPU的地址空间转换,所以不需要保存现场。
缓冲区的计算也是常客。设从磁盘读入一个块到缓冲区的时间为T,从缓冲区送到用户区的时间为M,CPU处理一块数据的时间为C。
- 单缓冲:每处理一块的平均时间 = max(T, C) + M。因为缓冲区只有一块,输入和传送会互相等待。
- 双缓冲:每处理一块的平均时间 = max(T, C + M)。两块缓冲区交替使用,读入下一块的同时处理当前块。
判断用哪个公式的窍门是看谁先被卡住:单缓冲时输入和CPU处理争同一块缓冲区,所以先取二者较大者再加传送时间;双缓冲时输入可以和处理并行,所以比的是输入时间和"处理加传送"。
5.4 位示图与空闲空间管理
空闲空间管理有四种方式:空闲表、空闲链表、位示图、成组链接。其中位示图的计算最容易出题。
位示图的规则:每一位对应一个磁盘块,0表示空闲,1表示已分配(具体0/1的含义题目会说明,看反了整题就废了)。给定块号和字长,求它在第几个字的第几位:
- 字序号(从0开始)= 块号 ÷ 字长,取整数部分;
- 位序号(从0开始)= 块号 mod 字长。
如果题目要求"字号从1开始编号",那字序号要再加1,位号一般仍从0开始。这个"从0还是从1"的细节每年都有人错,读题时务必圈出来。
反过来也一样常考:已知位示图里第3个字(从1开始编号)的第4位(从0开始编号)被占用,求它对应的块号。这时块号 = (3−1) × 字长 + 4。两个方向都要练熟。
6. 笔记怎么做、错题怎么回填:我的复盘节奏
6.1 概念表和计算表必须分开
我最早的笔记是流水账,概念和公式混在一起,结果复习时要么全看要么全跳。后来我拆成两份:一份是概念表,只放"是什么、为什么、怎么区分"这类文字型知识点,比如"分页和分段的区别""进程和线程的区别""内部碎片和外部碎片的区别",全部整理成三列表格;另一份是计算表,只放公式、符号含义、单位、典型例题的解题步骤。
概念表适合碎片时间翻,吃饭排队的时候看两眼就能记住一组对比;计算表适合坐下来动手推,必须配草稿纸。两者混着看,效率反而低——看文字的时候脑子会自动跳过公式,看公式的时候又不想读定义,最后两边都没吃透。
6.2 错题回填的时机比错题本身更重要
我的错误做法是"当场抄一遍错题",抄完就再也没看过。正确的做法是分两次回填:第一次在当天,只写"错在哪一步",不抄完整题干,目的是趁热定位问题;第二次在一周后,重做一遍这道题,如果还是错,再补一句"为什么会重复错"。第二次回填的那句话往往才是真正的收获。
比如我有一道银行家算法的题错了三次,第三次我才发现问题不在算法本身,而是我在计算 Available 时漏掉了某个进程释放的资源。那之后我给自己加了一条自检规则:每完成一个进程,必须立刻把它的 Allocation 加回 Work,并且把这个动作写在算式旁边。加上这条规则之后,同类题再没错过。
6.3 考前一周只看三样东西
距离考试还有一周的时候,我把复习范围压缩成三样:
第一是所有计算公式的清单。地址翻译、EAT、带权周转、索引结点容量、磁盘移动量、单双缓冲时间、位示图换算,大概十来条,一张A4纸写得下,每天默写一遍。
第二是PV操作的四类模板。默写生产者-消费者和读者-写者的完整代码,检查信号量初值、P/V顺序、计数器加锁这三处。
第三是错题回填的第二句话。那些我自己总结的自检规则,比如"资源信号量先P""索引结点先看是否在内存""磁头方向先确认",一共二十来条,考前一晚扫一遍。
这三样东西加起来不到五页纸,但它们覆盖了我所有丢过分的地方。相比之下,重新翻一遍王道那本厚书反而会制造焦虑——很多内容是选择题的边角料,考前突击性价比极低。
最后分享一个我自己用着很顺手的小习惯:给每道大题标一个"链路名"。比如做完一道地址翻译题,就在旁边写"逻辑地址→拆位→查表→拼物理地址";做完一道页面置换题,就写"访问串→逐格推演→统计缺页"。标完之后再回来复习,看到链路名就能瞬间回忆起整道题的走法,比看题干快得多。操作系统这门课真正难的地方,从来不是知识点多,而是链路长——把每一条链路的名字记住,就等于把整本书压缩成了十几行字。