☰
操作系统期末试卷高频考点解析:进程线程、虚拟内存与死锁
2026/11/6 7:08:35 网站建设 项目流程

简介:这份西安电子科技大学《操作系统》期末试题及答案解析PDF,面向计算机相关专业本科生及备考操作系统课程的学生,用于期末复习、考点自查与考前模拟。内容覆盖进程与线程调度、分页存储管理、虚拟内存与局部性原理、UNIX i-node结构、磁盘访问时间、Linux文件权限、DMA传输以及死锁四个必要条件等高频知识点,并配有答案解析,便于按题回溯概念、定位薄弱环节。全套为1个PDF文件,体积约118KB,轻量便携,适合在电脑或平板上随时翻阅。已有207人学习,可作为熟悉西电课程题型分布、英文题干表达与答题思路的参考样卷。

1. 操作系统到底是什么:一间永远在抢座的自习室

用过电脑的人多少有过这种疑惑:只是双击了一个图标,背后却牵出进程、分页、信号量一堆词,翻开卷子还全是要算的题。把操作系统想成一间自习室的管理员就顺了:座位是内存,台灯是 CPU,进进出出的人流是磁盘读写,谁先坐、坐多久、两个人同时抢一张桌子怎么办,全归它管。

一句话定义,操作系统是夹在硬件和应用程序之间的一层软件,它干两件事——把零散的硬件包装成一套好用的接口,业内叫“扩展机器”;把 CPU、内存、磁盘这些有限资源分配出去,业内叫“资源管理器”。这份试卷考的,其实就是这两件事的具体规则。

参考文章

2. 进程、线程与调度:为什么线程不是“小一号的进程”

先看一道选择题:一个进程里所有线程共享的东西,下面四个选项里哪个不是?答案是寄存器。这个选项之所以常被选错,是因为很多人心里默认“线程就是一个轻量级进程”,既然都是进程,那资源自然一样。可只要想清楚线程存在的理由——让同一个地址空间里能同时推进多条执行流——就会发现寄存器恰恰不能共享,因为寄存器记录的是“当前执行到哪一行”,两条执行流各走各的路,共用一张路线图就乱套了。

把这一章的内容理顺,你会同时拿下试卷里的选择题第 2、3、4 题和问答题第 1、2 题。

2.1 程序还在硬盘上时,它还什么都不是

一个.exe或者一个 ELF 文件,说到底就是一堆指令加上一些数据,是一份静态的说明书。它被加载进内存、分配了运行所需的上下文之后,才真正“活”过来,成为进程。所谓上下文,包括程序计数器指到哪一条指令、各个寄存器的值、函数调用的栈、已经打开的文件、占了多久 CPU 这些信息。内核为每个进程维护一份结构体来保存这些东西,业内叫进程控制块。

进程最经典的状态模型只有三个圈:运行态、就绪态、阻塞态。运行态表示 CPU 正在执行它;就绪态表示万事俱备,只差一个 CPU;阻塞态表示它在等某件事,比如等磁盘读完一块数据、等用户敲键盘。

当前状态触发事件转移到
就绪被调度器选中运行
运行时间片用完或被更高优先级抢占就绪
运行发起 I/O 请求或等待某个事件阻塞
阻塞等待的事件完成就绪

这张表里最容易被忽略的一条是:阻塞态不能直接跳到运行态。哪怕它等的事件已经完成,也必须先回到就绪队列排队,重新被调度器选中才能跑。原因很直白,CPU 此时可能正被别的进程占着,事件完成只意味着“有资格跑了”,不代表“马上就能跑”。画进程状态图时漏掉这一条,问答题直接少一半分。

2.2 线程共享什么:一道选择题背后的账本

一个进程里开三条线程,它们像三个人共用一间办公室。办公室本身(地址空间,包括代码段、数据段、堆)是共用的,文件柜(已打开的文件)是共用的,墙上贴的考勤表(记账信息,比如 CPU 使用时间、用户 ID)也是共用的。但每个人手里那张写着“下一步做什么”的便签,也就是程序计数器,必须各写各的;每个人自己的草稿纸,也就是栈,也必须各用各的。

为什么栈不能共享?因为函数调用是嵌套的,A 线程正在执行某个函数,压了一层返回地址,B 线程如果也用同一个栈,两边的返回地址就会互相覆盖,函数一返回就跳到不知道哪里去了。寄存器同理,它保存的是瞬时状态,两条执行流交替推进,共享就必然翻车。

import threading counter = 0 # 全局变量:在进程地址空间里,所有线程都能看见 def worker(n): global counter local = n # 局部变量:放在本线程自己的栈上,别的线程看不见 for _ in range(100000): counter += 1 # 没加锁,两个线程同时读改写,结果往往小于 200000 print(local) t1 = threading.Thread(target=worker, args=(1,)) t2 = threading.Thread(target=worker, args=(2,)) t1.start(); t2.start() t1.join(); t2.join() print(counter)

这段代码里,counter是共享的那一类,local是独占的那一类。counter += 1在没有加锁时不是一步完成的原子操作,它要先读出旧值、加一、再写回去,两个线程交错执行就会丢掉一些自增。这正是“共享”带来的代价,也是后面信号量那一章要解决的问题。

参数说明:threading.Thread创建的是同一进程内的线程,args传进去的值会变成各自的局部副本。如果你把local改成全局变量再打印,看到的就不是 1 和 2,而是最后一次写入的值——这也是理解共享边界最直观的实验。

2.3 用户级线程和内核级线程:差别在内核知不知道

线程可以在用户空间实现,也可以在内核里实现,两者都能跑,但代价完全不同。

用户空间实现的线程,靠一个运行时库来调度,内核压根不知道线程的存在,在它眼里这就是一个普通的单线程进程。好处是切换极快,不用陷入内核,纯用户态几条指令就换过去了;坏处也很明显,如果其中一个线程发起阻塞式系统调用,内核会把整个进程挂起,其他线程陪着一起等;而且内核只认一个执行流,多核机器上没法真正并行。

内核里实现的线程,创建和撤销都要走系统调用,内核维护一张线程表来做调度。好处是一个线程阻塞不影响别人,多核也能真并行;坏处是每次创建、切换都得进内核,开销比用户级大不少。

还有一种混合做法:每个内核级线程上挂一组用户级线程,轮流使用。试卷第 2 题问“线程可以在哪里实现”,答案是两者都行,正是因为这三种方案都真实存在。

2.4 三级调度:长期、中期、短期各管什么

调度这个词在操作系统里出现了三次,管的事情完全不同,混着用就说不清了。

名称别名管什么发生频率
长期调度作业调度从后备作业里挑一个,创建进程让它进来最低
中期调度内存调度决定某个进程是否被换出内存、何时换回中等
短期调度CPU 调度从就绪队列里挑一个进程,把 CPU 给它最高

表格里最要紧的是最后一行。平时说的“调度算法”,比如先来先服务、时间片轮转、多级反馈队列,讲的都是短期调度。有人把短期调度答成“线程调度”或“作业调度”,就是被这三个名字绕晕了。短期调度发生在每次时间片用完、每次 I/O 完成、每次进程主动让出 CPU 的时候,一秒钟可能发生几百次,所以算法必须足够快,这也是为什么它通常只做简单的优先级比较。

2.5 系统调用:一次读文件要过几道门

应用程序不能直接操作硬件,它得请内核代劳。内核对外暴露的那组入口,就是系统调用。整个流程大致是四步:用户程序把参数放到约定好的寄存器或者栈上;执行一条陷入指令,CPU 从用户态切换到内核态;内核根据系统调用号查表,找到对应的处理函数执行;处理完把返回值放回寄存器,切回用户态继续跑。

#include <fcntl.h> #include <unistd.h> int main(void) { char buf[64]; int fd = open("data.txt", O_RDONLY); // 系统调用:内核检查路径、权限,返回文件描述符 int n = read(fd, buf, sizeof(buf)); // 系统调用:内核把磁盘数据拷进用户缓冲区 write(1, buf, n); // 系统调用:写到标准输出 close(fd); // 系统调用:释放描述符 return 0; }

这四行代码每一步都在跨越用户态和内核态的边界。open里内核要查目录、验权限、在进程的文件描述符表里占一个位置;read里内核可能要把进程挂起等磁盘,等数据到了再拷到用户空间,这里就出现了“运行态到阻塞态再回就绪态”的完整迁移。参数说明:fd是内核返回的一个小整数,本质是描述符表的下标;buf是用户空间的地址,内核往里写数据前会做一次地址合法性检查,防止用户传一个内核地址进来。

提示:问答题里写“系统调用的步骤”,只写“应用程序请求内核帮忙”是拿不到分的,至少要落到参数传递、状态切换、查表分发、返回结果这四步。

3. 内存管理:8G 内存为什么能跑 20G 的程序

这个问题几乎是每个初学操作系统的人都会问的:我电脑 8G 内存,怎么同时开着浏览器、编辑器、音乐播放器,加起来早超了?答案藏在“虚拟”两个字里。每个进程看到的地址空间都是假的、连续的、从零开始的一大片,真正落到物理内存上的是其中正在用的那一小部分,剩下的要么躺在磁盘上,要么根本还没被碰过。这一章把这条链路拆开看。

3.1 页号和偏移:那串 31 到 0 的位到底在算什么

分页的本质是把地址切成两段:高位是页号,低位是页内偏移。页内偏移的位数决定了页有多大,页号的位数决定了最多能有多少页。

试卷里那道题给出的地址结构是第 31 位到第 10 位为页号、第 9 位到第 0 位为偏移。低 10 位能表示 0 到 1023,所以页大小是 2 的 10 次方,也就是 1K 字节;页号占 22 位,能表示 2 的 22 次方,也就是 4M 个页面。答案选“页大小 1K,最多 4M 页”。

这道题的坑在于顺序。有人先看到“1K”觉得太小不可能,转头去选 4K;也有人把 22 位当成页大小的指数,算出一个离谱的数。记住一条:偏移位宽决定页大小,页号位宽决定页数量,两者是分开算的。

偏移位数页大小页号位数最多页数
101K224M
112K212M
124K201M

3.2 局部性原理:虚拟内存敢这么玩的底气

如果程序真的均匀地访问整个地址空间,虚拟内存这套把戏一天都撑不下去,因为每次访问都可能缺页,磁盘根本来不及。它能成立,全靠程序自己“讲道理”:一段时间内,程序只访问相对小的一段地址空间。

这件事分两面看。时间局部性是说,刚被访问过的指令和数据,很可能马上还会被访问,比如循环体里的计数器、反复调用的函数。空间局部性是说,一个地址被访问了,它附近的地址大概率也快被访问了,比如遍历数组、顺序执行指令。

正因为如此,操作系统才敢只把常用的那几页留在内存里,其余的放到磁盘上。等真访问到不在内存的页,再触发一次缺页,把它换进来。这个“常用页的集合”就是工作集,工作集越大,缺页越频繁,程序就越慢。写完一段死循环疯狂扫一个大数组的代码,机器突然卡成幻灯片,多半就是这个原因。

3.3 TLB:把查表结果记在小本子上

有了分页,每次访问内存其实要访问两次:先查页表,找到物理页框号,再拿物理地址去取数据。这就把访存次数翻了一倍,性能上没法接受。

TLB 就是来解决这个问题的。它是内存管理单元里的一块小型硬件缓存,表项很少,通常只有几十到几百条,每条记着虚拟页号、对应的物理页框号、这一页有没有被改过、以及读/写/执行的保护码。程序访问一个地址时,硬件先拿虚拟页号去 TLB 里比一下,命中就直接得到物理页框号,一步到位;没命中才去内存里查页表,查到之后再把结果塞回 TLB。

用一个生活化的比方:页表是图书馆的总目录,翻一次要走到大厅;TLB 是你桌上的便利贴,写着最近常借的那几本书在哪个架子上。绝大多数时候你看便利贴就够了,偶尔才需要跑一趟大厅。

3.4 一次地址转换的完整走法

拿试卷里那组数据走一遍。页大小 2K(2048 字节),内存里只有 4 个页框,进程需要 8 个虚拟页,页表里标记了每页是否有效、对应哪个页框。

PAGE_SIZE = 2048 # None 表示有效位为 0,也就是这一页不在内存里 page_table = {0: None, 1: None, 2: 1, 3: None, 4: 3, 5: None, 6: 0, 7: 2} def translate(addr): page = addr // PAGE_SIZE # 页号 = 地址整除页大小 offset = addr % PAGE_SIZE # 偏移 = 地址对页大小取余 frame = page_table[page] if frame is None: return f"{addr} 缺页(虚拟页 {page} 不在内存,需从磁盘调入)" return f"{addr} 页号 {page} 偏移 {offset} 物理地址 {frame * PAGE_SIZE + offset}" for a in (8500, 1400, 5000, 2100): print(translate(a))

这段代码做的就是硬件那几步:先除、再取余、然后查表、最后拼地址。//求页号,是因为页大小是 2 的整数次幂时,整除等价于右移;%求偏移,等价于取低位。

结果如下:

虚拟地址页号偏移页框结果
850043083物理地址 6452
140001400无缺页
500029041物理地址 2952
2100152无缺页

缺页的地址范围也就出来了:虚拟页 0、1、3、5 都无效,对应 0K 到 2K、2K 到 4K、6K 到 8K、10K 到 12K 这四段。答这类题的关键是先老老实实把每页的起止地址列成表,再对照有效位,不要凭感觉猜。

3.5 地址绑定:编译、装入、执行三个时机

指令和数据里的地址,什么时候从“相对偏移”变成“真实物理地址”,有三种可能。

编译时就确定,意味着编译那一刻就知道程序将来会装在哪块内存,生成的直接是绝对地址。这要求环境完全可预测,一旦位置变了就得重新编译,现在基本见不到。装入时才确定,是链接器生成可重定位代码,加载器把它放到内存某处时统一加上一个基址,之后就不再变。执行时才确定,是运行过程中由硬件配合完成转换,进程可以被换出再换入到不同的位置,虚拟内存靠的就是这一条。

真正让“程序以为自己独占一大片连续内存”成立的,是执行时绑定加上内存管理单元的地址翻译。这也是为什么现代系统里,一个进程被挂起再恢复,位置变了却照样能跑。

4. 并发与死锁:信号量、管程和那只“银行家的眼睛”

多个执行流同时访问同一份数据,麻烦就来了。最直观的例子是两个人同时往同一个账户里存钱:A 读出余额 100,B 也读出 100,A 写回 150,B 写回 180,账户凭空少了 70。并发控制这一整套东西,说到底就是在防这种“读改写被切开”的事。

4.1 临界区与信号量:P 和 V 到底在等什么

那段“读余额、加钱、写回”的代码,必须一次做完,中间不能被打断,这段代码就叫临界区。信号量是管这件事的工具,它本质上是一个整型变量,只允许通过两个原子操作来动它:P 操作和 V 操作。

P 操作的做法是先把值减一,如果减完小于零,说明资源已经被占满,当前进程就阻塞,把自己挂到该信号量的等待队列上。V 操作是先把值加一,如果加完小于等于零,说明队列里还有人等着,就唤醒其中一个。

用停车位打个比方:信号量初值等于车位总数。P 就是开车进场,满位了就在门口排队;V 就是开车离场,顺带喊一声“有空位了”,让门口第一辆车进来。整个过程必须原子,否则两辆车同时看到“还剩一个位”,一起冲进去就撞了。

4.2 管程:把互斥锁进一个盒子里

信号量的问题在于它靠程序员自觉。该 P 的地方忘了 P,该 V 的地方忘了 V,程序照样编译通过,跑起来就是玄学崩溃。管程换了个思路:把共享变量和操作它们的过程打包成一个模块,语言层面保证同一时刻只有一个进程能待在管程内部。

管程里还有条件变量,配合 wait 和 signal 使用。wait 会让当前进程在条件变量上等待,并且自动释放管程的互斥权,让别的进程能进来;signal 则唤醒一个在该条件变量上等待的进程。注意这个自动释放是“原子”完成的,否则就会出现“锁还没放、别人进不来、自己又在等”的死局。

管程和信号量的区别,说白了是“约定”和“强制”的区别。信号量是一套全靠自觉的协议,管程是把互斥写进了语言规则里,忘不了。

4.3 生产者-消费者:那六个空对应什么

试卷最后一道综合题给了半截代码,要填六个空。这是并发部分最经典的模型:一个生产者往缓冲区放数据,一个消费者从缓冲区取,缓冲区只能放一个数。

void *producer(void *ptr) { for (int i = 1; i <= MAX; i++) { pthread_mutex_lock(&the_mutex); // 先拿锁,才能碰缓冲区 while (buffer != 0) // 空①:缓冲区非空说明还没被取走,得等 pthread_cond_wait(&condp, &the_mutex); // wait 会原子地释放锁并挂起 buffer = i; // 放数据 pthread_cond_signal(&condc); // 空②:叫醒等着的消费者 pthread_mutex_unlock(&the_mutex); // 空③:交还锁 } pthread_exit(0); } void *consumer(void *ptr) { for (int i = 1; i <= MAX; i++) { pthread_mutex_lock(&the_mutex); while (buffer == 0) // 空④:缓冲区空,没东西可取,得等 pthread_cond_wait(&condc, &the_mutex); // 空⑤:睡在消费者自己的条件变量上 int res = buffer; buffer = 0; // 取走,标记为空 pthread_cond_signal(&condp); // 叫醒生产者 pthread_mutex_unlock(&the_mutex); // 空⑥:交还锁 printf("buffer=%d\n", res); } pthread_exit(0); }

六个空填完是:①buffer != 0、②pthread_cond_signal(&condc)、③pthread_mutex_unlock(&the_mutex)、④buffer == 0、⑤&condc、⑥pthread_mutex_unlock(&the_mutex)。

几个容易写错的地方。第一,条件判断必须用 while 而不是 if,因为被唤醒之后缓冲区状态可能又被别人改了,得重新检查一遍,这就是所谓的虚假唤醒。第二,两个 wait 用的条件变量不能搞混,生产者等的是condp,消费者等的是condc,试卷里pthread_cond_wait(&condp, ...)出现的那个位置属于生产者。第三,unlock 一定要放在 signal 之后、循环体末尾,如果放在 signal 之前,被唤醒的线程可能会在锁还没释放时又睡回去。

4.4 死锁四条件与银行家的“安全序列”

死锁是四个条件同时成立才会发生的:互斥(资源一次只能给一个人)、占有并等待(拿着一个还想要下一个)、不可抢占(拿到手的不能被强行收走)、循环等待(A 等 B、B 等 A 绕成一圈)。这四个条件缺一不可,所以防死锁的思路也很直接——破坏其中任意一条就行。实际系统里最常用的是破坏循环等待,比如给所有资源编号,规定只能按序号递增的顺序申请。

银行家算法走的是另一条路:不禁止申请,但每次分配前先算一遍,分完之后系统还能不能找到一个安全序列,让所有进程都顺利跑完。它的核心公式只有一个:某进程还需要的资源量等于它声明的最大需求量减去已经分配给它的量。

试卷里给出的那张表,第一步就是逐行算 Need。算完之后从当前可用资源出发,找一个 Need 小于等于可用的进程,假设它跑完并归还全部资源,可用量随之增加,再继续找下一个。能一路找完所有进程,就是安全状态;中途卡住,就是不安全状态。

第二问的结论是不安全,因为按可用资源只能推进 P0 和 P3,推完之后剩下的资源满足不了其余任何进程。第三问 P1 请求 (0,4,2,0),做法是先试着分配给它,再重新跑一遍安全检查。这次能走出 P0、P3、P1、P2、P4 这样一条完整序列,所以请求可以立即批准。

提示:银行家算法的题,一定要把“试分配”和“安全性检查”写成两步,中间不能跳。直接说“批准”而不写试分配后的新状态,阅卷时基本拿不到过程分。

5. 五组最容易踩的易混概念:别在这些地方翻车

概念题丢分往往不是不会,而是把两个长得像的东西记串了。下面这五组,几乎每年都有人栽。

5.1 进程和线程:到底谁共享、谁独占

易混点:觉得线程就是小一号的进程,所以资源该一样。

为什么混:两者确实都是“执行流”,都参与调度,都有状态迁移,教材上又常常并排讲。

怎么区分:记住一句话——线程共享进程的“家产”,独占自己的“随身物品”。家产包括地址空间、打开的文件、记账信息;随身物品包括寄存器组(含程序计数器)和自己的栈。判断方法很实用:凡是问“这个资源被多个线程同时改会不会乱”,会乱的就是独占的,不会乱的就是共享的。

5.2 程序控制 I/O、中断驱动 I/O、DMA:CPU 到底插不插手

易混点:三种方式都叫 I/O,都涉及 CPU 和外设,区别记不住。

为什么混:名字里都有 I/O,描述里又都说“CPU 参与”,只是参与程度不同。

怎么区分:看数据搬运的过程中,CPU 有没有被占用。

方式CPU 在数据传输中的角色数据经过 CPU 吗
程序控制 I/O全程忙等,反复查状态寄存器经过
中断驱动 I/O设备就绪时被打断一下,处理完继续干别的经过
DMA只在开始时授权、结束时收尾不经过,直接内存到设备

判据就是题目里常出现的那句“不需要 CPU 参与数据块的传送”,指的是 DMA。程序控制和中断驱动的差别在于“等待期间 CPU 干不干别的事”,而两者和 DMA 的差别在于“数据走不走 CPU 这条路”。

5.3 寻道时间、旋转延迟、传输时间:谁主导

易混点:以为硬盘读写慢是因为数据传输慢。

为什么混:三者加在一起才是一次磁盘读写的总时间,名字都带“时间”。

怎么区分:寻道时间是磁头移动到目标磁道的时间,旋转延迟是等目标扇区转到磁头下方的时间,传输时间是数据真正读出来的时间。对大多数机械硬盘来说,寻道时间占大头,因为机械臂的物理移动最慢。

这也解释了磁盘调度算法的意义。先来先服务按请求顺序走,机械臂来回乱窜,平均移动距离最长;最短寻道时间优先每次挑最近的,移动距离明显缩短,但可能导致远端请求长期得不到服务;电梯算法(SCAN)像电梯一样单向扫过去再折返,兼顾了效率和公平。

试卷那道题里,起始柱面是 20,请求序列是 10、22、20、2、40、6、38。按先来先服务,移动总距离是 146 个柱面,每柱面 6 毫秒,合计 876 毫秒;按最短寻道优先,总距离缩到 60,合计 360 毫秒;按电梯算法向上扫描,总距离 58,合计 348 毫秒。这里有个细节值得注意:从 20 出发,10 和 22 距离都是 2,先走哪边不同教材处理不一样,写答案时说明自己的方向约定就行,别因为和标准答案顺序不同就怀疑算错。

5.4 i-node、目录项、MBR:三个被塞进同一道题里的东西

易混点:把 i-node 号、文件名、引导记录当成一回事。

为什么混:它们都和“磁盘上的文件系统结构”有关,又都出现在同一批选择题里。

怎么区分:MBR 是磁盘第 0 号扇区,也就是主引导记录,负责开机时把控制权交给引导程序,跟具体文件没关系。目录项是目录文件里的一条记录,里面只有文件名和该文件的 i-node 编号,不存文件内容也不存大小。i-node 才是真正记录文件元数据的地方,包括文件大小、权限、时间戳、数据块位置。

试卷里那道“每个文件系统最多多少文件”的题,考的就是目录项的结构:i-node 号占 2 个字节,能表示 2 的 16 次方,也就是 64K 个不同编号,所以上限是 64K 个文件。文件名占 14 个字节这个信息是干扰项,它限制的是单个文件名的长度,不是文件数量。

5.5 chmod 755 和 644:权限位不是随手填的数

易混点:看到 755、644、744 一串数字,凭印象挑一个顺眼的。

为什么混:这三个数太常见,网上到处是chmod 755,容易形成“反正都这么写”的错觉。

怎么区分:三个数字依次对应文件主、同组用户、其他用户,每个数字是读(4)、写(2)、执行(1)三个权限相加的结果。

题目要求文件主可读可写可执行,其他人只能读。文件主就是 4+2+1 等于 7;同组用户只读是 4;其他用户只读也是 4,合起来是 744。755 错在同组和其他用户多了执行权限,644 错在文件主少了执行权限。

6. 什么时候该用这份资料,什么时候别用

这份资料的定位很清楚:一份带答案解析的期末试卷,四大部分、满分一百,涵盖选择、填空、问答和综合。它适合的场景有两个。

一是考前两三周的自查。这个阶段最怕的是“看着都会,一写就错”。拿它当模拟卷,闭卷限时两小时做完,然后对答案,重点不是看对错,而是把每一道错题归类:是概念没记住(比如三级调度的别名),是计算方式记反了(比如页大小和页号位数搞混),还是根本没思路(比如银行家算法的安全序列)。三类错因对应三种复习动作,混在一起就会变成“刷了很多题但没长进”。

二是查漏补缺时做定向突破。比如综合题里生产者-消费者的填空总是填不对,那就把条件变量那几行代码单独拎出来,自己默写一遍,再对照答案看是条件写反了还是 wait 的位置放错了。这种题型的考点高度固定,吃透一道就能覆盖一片。

不适合的场景也得说清楚。如果你想学的是内核源码怎么实现调度器、页表怎么组织成多级、文件系统怎么落盘,这份试卷帮不上忙,它考的是原理层面的模型,不是工程实现。如果你需要的是动手写一个多线程程序并调优,它也不行,题目里的代码是教学用的简化版本,只是为了说明同步关系,离生产可用还差得远。还有一种情况:如果你的课程教材用的符号体系和这份卷子不一样,比如信号量的 P/V 被别人写成 wait/signal、进程状态多画了创建态和终止态,直接背答案反而会让你在别的题上犯迷糊,这时候应该以自己教材的定义为准,把这份卷子当练习题而不是标准答案。

一个具体的用法是这样的:先把四道综合题当成四道独立的小测,每道限时二十分钟。做完之后不看答案,先自己口头讲一遍思路——磁盘调度为什么最短寻道优先会比先来先服务快、银行家算法为什么要先试分配。讲得磕巴的地方就是没真懂的地方,这时候再翻答案,收获比直接抄一遍大得多。至于选择题和填空题,它们适合在碎片时间过一遍,因为这些题考的就是记忆点,反复见面才记得牢。

我自己有个改不掉的习惯:每次对完答案,都会把错题的原因用一句话写在旁边,不写“粗心”,只写具体动作,比如“把偏移位数当成页大小算了”“忘了阻塞态要先回就绪态”。这个习惯是从一次很惨的翻车经历里养成的——某次考试前把整本卷子刷了两遍,感觉稳了,结果考场上遇到一道页表地址转换的题,数字换了换,我照着记忆里的答案写,最后一步加错了页框号,白白丢了十分。从那以后,我每次做计算题都强制把中间结果写出来,页号、偏移、页框、物理地址各占一行,宁可多写两行,也不让脑子里的捷径替我做决定。

希望这份梳理能帮到你。需要对照原题的人,直接取用那份 PDF 就好——先合上答案自己做一遍,再打开一起来看,效果会好很多。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询