计组与操作系统融合精讲
写给没系统学过计算机的高三毕业生:从“0 和 1 是什么”讲起,到能独立做 2009—2026 年的 408 真题。
计组和操作系统在 408 里一共 80 分,占整张卷子一半以上。两门课其实在讲同一台计算机:计组讲硬件“怎么做到”,操作系统讲软件“怎么安排”。很多真题(尤其是综合大题)专门考两者交界的地方,比如“一次访存要经过 TLB、页表、Cache、主存,缺页了操作系统怎么接手”。所以本讲义不按两本教材各讲各的,而是把它们拼成一条主线。
- 按顺序读。第 0 篇是零基础补课,后面每篇都假设你读过前面的篇。
- 每遇到 真题卡 先自己做,再点「展开解析」。解析会写出每一步为什么这样想,而不只给答案。
- 每个小节标题下面有一条“真题标签”:★★★ 高频 表示这个知识点在 2009—2026 年真题里出现得多(★★ 常考、★ 考过、“基础铺垫”是没直接考但后面要用的);后面的
09#26这样的小按钮就是“2009 年第 26 题”,点一下在页面里弹出题目,直接作答、判对错、看讲解。正文里出现的“2019-14”这类题号也都能点。读完一个小节就把它下面的题做掉——这就是“看完能做真题”的检验。 - 每篇末尾有“本篇真题闯关”,把这一篇涉及的真题按小节排好;第 12 篇 12.8 可以按年份刷完 2009—2026 年全部 451 道计组、操作系统真题(选择 379 道、综合 72 道,每道都有讲解,综合题是分步完整解答,其中 22 道附原卷参考答案的评分标注)。做对做错会记在本机,题号随之变绿/变红。
- 每个考过选择题的小节(共 72 个),末尾都有「选择题变式」(虚线框,共 205 道,考得越多的小节题越多;点选项立即判对错):每道都由一道真题改动一两个条件而来,解析后面的“变化点”说明改了什么、答案为什么跟着变。先做真题,再做变式,用来检验是不是真正懂了,而不是记住了答案。
- 标 融合 的地方是两门课的交界,是综合大题的高发区,读两遍。
- 页面里带 ⚙ 的是可以动手的小工具(进制转换、Cache 模拟、页面置换、调度甘特图、磁盘调度、银行家算法……),改数字就能看到结果,用来验证你手算的答案。
- 第 11 篇「大题满分训练营」把 72 道计组、操作系统综合题分成 13 类,每类讲“这类题长什么样 → 必须会的几件事 → 解题模板 → 完整示范 → 自编模拟大题 → 举一反三变式表 → 丢分点”,最后 11.15 有两套按真实考试格式出的整卷模拟(附评分细则),每道大题都有“答题卡这样写”的满分解答。学完对应的知识篇就去练对应的题型。
- 每篇末尾有「自测」和「本篇学完」勾选框;第 12 篇是考前速查:公式 + 易错清单 + 真题索引 + 按年份刷题。
- 第 13 篇「满分冲刺」给已经学完一遍的人用:先看两张“年份 × 章节”热力图和 2027 预测,再做冷考点专项、概念辨析(Ⅰ/Ⅱ/Ⅲ 组合题)、计算题专练、融合综合题,最后用三套80 分全真模拟卷(21 道选择 + 4 道综合,限时 100 分钟,可只做计组或只做操作系统,交卷自动分科计分)检验;13.8 的知识点细目表可以逐条勾选,查漏补缺。
一张图看懂:两门课为什么要合在一起学
| 篇 | 内容 | 两门课的交界 |
|---|---|---|
| 第 0 篇 | 零基础装备:二进制、十六进制、2 的幂、单位、地址、位运算 | 所有计算题的地基 |
| 第 1 篇 | 一台计算机的全景:五大部件、程序怎么执行、操作系统是什么、性能指标 | 硬件与 OS 的分工 |
| 第 2 篇 | 数据怎么表示和运算:补码、C 语言类型转换、标志位、IEEE 754、大小端 | 计组独有 |
| 第 3 篇 | 指令系统与“程序的一生”:寻址方式、汇编代码、编译链接装入、进程地址空间 | 融合 编译链接装入 |
| 第 4 篇 | CPU 内部:数据通路、控制信号、控制器、流水线、多核 | 计组为主 |
| 第 5 篇 | 中断、异常、系统调用、用户态与内核态 | 融合 核心交界 |
| 第 6 篇 | 进程、线程、CPU 调度、上下文切换 | OS 为主 |
| 第 7 篇 | 同步互斥(PV 大题)、死锁、银行家算法 | 融合 硬件原子指令 |
| 第 8 篇 | 存储系统全景:主存芯片、Cache、分页分段、TLB、虚拟内存、页面置换 | 融合 最大交界 |
| 第 9 篇 | 总线与 I/O:查询/中断/DMA、驱动、缓冲、一次 scanf 的全过程 | 融合 |
| 第 10 篇 | 磁盘与文件系统:访问时间、调度算法、inode、目录、链接 | 融合 |
| 第 11 篇 | 大题满分训练营:13 类综合题,每类有模板、示范和满分答案 | 综合题专项 |
| 第 12 篇 | 考场作战手册:题型速查、公式速查、易错清单、真题索引、按年份刷题 | 考前冲刺 |
| 第 13 篇 | 满分冲刺:考情热力图与 2027 预测、冷考点专项、概念辨析与计算专练、融合综合题、三套 80 分全真模拟卷、知识点细目表 | 查漏补缺、限时实战 |
小节标题下的星级按“这一小节挂了多少道真题”自动算(选择题算 1、综合题算 3:≥9 为三星,≥4 为两星)。每道题挂在哪个小节、讲解怎么写,都是对照题目逐道确定的;题干和答案来自本机题库(2009—2026 年计组 235 题、操作系统 216 题),缺图的题干按原卷重绘。考过多次说明重要,但不代表明年一定考;没挂题的知识点也在大纲里,同样要会。
出发前的装备:0 和 1 的世界
后面所有计算题——地址有几位、Cache 分几组、页表多大、磁盘多少秒——用到的数学只有四样:二进制、十六进制、2 的幂、单位换算。这一篇把它们一次补齐,花 1~2 小时,后面会省很多时间。
0.1计算机只认识“开”和“关”
计算机里最小的零件是晶体管,你可以把它想成一个电灯开关:只有“通电”和“不通电”两种状态。我们把“通电”记作 1,“不通电”记作 0。
- 一个开关能记住 1 个 0 或 1,这叫 1 位(bit,简写 b)。
- 8 个开关排成一排,叫 1 字节(Byte,简写 B)。8 个开关一共有 2×2×…×2 = 28 = 256 种开关组合,所以 1 字节能区分 256 种东西。
- n 位能表示 2n 种不同的组合——这是全书用得最多的一句话。
- A. 仅Ⅰ、Ⅱ
- B. 仅Ⅰ、Ⅲ
- C. 仅Ⅱ、Ⅲ
- D. Ⅰ、Ⅱ和Ⅲ
展开解析
D三条都是计算机采用二进制的理由,分别从“运算”“器件”“电路实现”三个角度说的。
- Ⅰ 对二进制加法只有 0+0、0+1、1+0、1+1 四种情况,乘法也只有 0、1 相乘,运算规则最简单。
- Ⅱ 对开 / 关、高 / 低电平这种只有两个稳定状态的器件最容易造,也最不容易出错。
- Ⅲ 对0 / 1 正好对应逻辑上的假 / 真,用与门、或门、异或门就能搭出加法器,算术运算可以用逻辑电路实现。
干扰项从哪来:A 是漏了Ⅲ,以为“用逻辑门实现运算”只是结果而不是原因;B 是漏了Ⅱ,忽视了物理器件容易制造这一最根本的原因;C 是漏了Ⅰ,没注意到二进制运算规则简单本身就是优点
采用二进制的原因:器件容易造(两个稳态)、运算规则简单、能用逻辑电路实现运算,可靠性高。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 二进制只有 0 和 1 两个数字,用有两个稳定状态的器件就能表示
- B. 二进制的算术运算可以用逻辑门电路实现
- C. 任何十进制小数都能用有限位二进制数精确表示
- D. 十六进制只是二进制的简写,计算机内部存的仍是二进制
展开解析
C0.1、0.3 这类十进制小数化成二进制是无限循环小数,有限位只能近似表示。其余三项都是采用二进制的理由或事实。
变化点:2018-12 问采用二进制的原因(器件容易做、运算规则简单、便于用逻辑门实现)。这里反过来找一个错误说法。
0.2十进制、二进制、十六进制
我们平时用的十进制,本质是“逢十进一”,每一位的权重是 1、10、100……。二进制就是“逢二进一”,权重是 1、2、4、8……。这几种进制之间怎么互相换算,就是教材里的进制转换。
二进制太长,人看着眼花,于是有了十六进制:逢十六进一,用 0~9 和 A(10)、B(11)、C(12)、D(13)、E(14)、F(15) 共 16 个符号。408 里十六进制数常在末尾写 H(如 2000H),C 语言里写 0x 前缀(如 0x2000),两种写法意思一样。
最重要的技巧:1 个十六进制位 = 4 个二进制位
因为 16 = 24,所以十六进制和二进制可以四位四位直接对照,不用经过十进制。把下面这张表背熟(其实只要会数数):
| 十六进制 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 二进制 | 0000 | 0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 | 1000 | 1001 | 1010 | 1011 | 1100 | 1101 | 1110 | 1111 |
| 十进制 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
例:2F3AH → 2=0010,F=1111,3=0011,A=1010 → 0010 1111 0011 1010。反过来,二进制从右往左每 4 位一组(最左边不够 4 位就在前面补 0),每组换成一个十六进制位。
地址切分题里,字段位数往往不是 4 的倍数。比如 12 位页内偏移正好是 3 个十六进制位(很舒服),但 5 位块内地址、3 位组号就会“劈开”一个十六进制位。稳妥做法:先把十六进制全部展开成二进制,再按位数切。不要试图直接在十六进制上切。
八进制,以及“任意进制 → 十进制”
八进制逢八进一,只用 0~7 八个符号。因为 8 = 23,1 个八进制位 = 3 个二进制位,道理和十六进制完全一样,只是 3 位一组。C 语言里以 0 开头的整数是八进制(017 是十进制的 15,不是 17),教材里常在末尾写 O 或 Q(17O、17Q)。408 里八进制很少出现,会换就行。
任意 r 进制 → 十进制:按权展开。小数点左边第 0、1、2 位的权是 r0、r1、r2……,小数点右边第 1、2、3 位的权是 r−1、r−2、r−3……每一位“数码 × 权”再加起来:
带小数点的二进制换成十六进制(八进制):从小数点出发,往两边分组。整数部分往左每 4 位(3 位)一组,最左边不够在左边补 0;小数部分往右每 4 位(3 位)一组,最右边不够在右边补 0。小数部分如果把 0 补在左边,就把 0.1 变成了 0.0001,值全变了。
| 二进制 1 0110 1101.101 | 分组(补的 0 用红色) | 结果 |
|---|---|---|
| 4 位一组 → 十六进制 | 0001 0110 1101 . 1010 | 16D.AH |
| 3 位一组 → 八进制 | 101 101 101 . 101 | 555.5₈ |
| 验算(按权展开) | 16D.AH = 256 + 96 + 13 + 10/16 = 365.625;555.5₈ = 320 + 40 + 5 + 5/8 = 365.625 ✓ | |
十进制 → 二进制
- 整数部分:除 2 取余,倒着写。例:13 → 13÷2=6 余 1,6÷2=3 余 0,3÷2=1 余 1,1÷2=0 余 1 → 倒读 1101。更快的办法是“凑 2 的幂”:13 = 8+4+1 = 1101。
- 小数部分:乘 2 取整,正着写。例:0.25 → ×2=0.5 取 0,×2=1.0 取 1 → 0.01₂。
- 有的小数乘不尽:0.1 → 0.2(0)→0.4(0)→0.8(0)→1.6(1)→1.2(1)→0.4(0)… 无限循环 0.000110011…₂。所以0.1、0.2、1.2 这样的小数计算机存不精确;而 0.5、0.25、0.125、1.25 这类“分母是 2 的幂”的小数可以精确存。
- 换成八进制、十六进制也是同一套办法:整数部分“除 8(16)取余、倒着写”,小数部分“乘 8(16)取整、正着写”。但考场上更快、更不容易错的永远是先变成二进制,再 3 位(4 位)一组。
除 2 取余时,第一次得到的余数是“这个数是奇数还是偶数”,也就是最低位(20 位),所以先得到的写在最右边;乘 2 取整时,第一次乘 2 溢出到整数部分的是“这个小数有没有 1/2”,也就是小数点后第 1 位(2−1 位),所以先得到的写在最左边。两个方向都是“离小数点最近的位先出来”。
- A. 1.2
- B. 1.25
- C. 2.0
- D. 2.5
展开解析
A一个小数能被 IEEE 754 精确表示的前提是:它的二进制小数部分是有限位(能写成若干个 1/2、1/4、1/8 …之和),且有效位不超过 24(float)。
- A 对1.2 = 1.0011 0011…₂,0.2 乘 2 取整永远循环(0.2 → 0.4 → 0.8 → 1.6 → 1.2 → 0.4…),存不下,只能舍入——不能精确表示,本题选它。
- B 错1.25 = 1.01₂,能精确表示。
- C 错2.0 = 1.0 × 21,能精确表示。
- D 错2.5 = 10.1₂ = 1.01 × 21,能精确表示。
十进制的 0.1、0.2 在二进制里是无限循环小数,float、double 都不能精确表示。
0.32 的幂与单位:408 计算题的“九九乘法表”
| 2n | 20=1 | 21=2 | 22=4 | 23=8 | 24=16 | 25=32 | 26=64 | 27=128 | 28=256 | 29=512 |
|---|---|---|---|---|---|---|---|---|---|---|
| 名字 | 210 = 1024 = 1K | 220 = 1M | 230 = 1G | 240 = 1T | 250=1P | |||||
于是任何“2 的幂”都能口算:4KB = 22×210 B = 212 B;64MB = 26×220 = 226 B;4GB = 232 B。反过来,“2n 个东西需要 n 位编号”,记作 n = log2(个数)。
“有多少个东西” ⇄ “需要几位编号”:个数 = 2位数,位数 = log2个数。后面 Cache 的组号位数、页号位数、寄存器编号位数、磁盘扇区号位数……全是这一句。如果个数不是 2 的幂(比如 500 个扇区),位数要向上取整:⌈log2500⌉ = 9 位(29=512 ≥ 500)。
为什么是 2n?每多一位,就是在原来所有编号前面分别写一个 0 和一个 1,编号的个数翻一倍:
两种“K”:存储用 210,速度用 103
容量(存储器、文件、页面)
K = 210,M = 220,G = 230例:4KB 页 = 4096 B
速率与频率(主频、带宽、传输率)
k = 103,M = 106,G = 109例:主频 1GHz = 每秒 109 个时钟周期
真题里偶尔两者混在一个式子里(比如 “0.5MB/s 的设备,每次传 4B”)。408 的评分答案一般直接按题目数字算(0.5MB/4B = 125000 次/秒,把 M 当 106),除非题目明确给了 220。遇到页面、块这种“容量”,一律按 2 的幂。
b 和 B、时间单位
- 小写 b = bit(位),大写 B = Byte(字节),1B = 8b。网络、总线的“Mbps”是兆位每秒;“MB/s”是兆字节每秒,差 8 倍。
- 时间:1 s = 103 ms(毫秒)= 106 μs(微秒)= 109 ns(纳秒)= 1012 ps(皮秒)。
- 频率和周期互为倒数:主频 500MHz → 时钟周期 = 1/(500×106) s = 2 ns。
0.4地址:内存就是一排带门牌号的格子
主存(内存)可以想成一条很长的街,每个格子是一个存储单元,门牌号就是地址。CPU 要读写内存,必须先说出门牌号。
按字节编址 vs 按字编址
按字节编址:每个地址对应 1 个字节(现在几乎都是这样,408 大题也基本是)。按字编址:每个地址对应 1 个“字”(字长 16 位就是 2 字节一个地址)。同样 16 位地址,按字节编址能访问 216 B = 64KB,按 16 位字编址能访问 216×2B = 128KB。
- A. 22 位
- B. 23 位
- C. 25 位
- D. 26 位
展开解析
DMAR 要能给出整个主存地址空间中的任何一个地址(以后可以插满内存),所以看地址空间 64MB,而不是现在装了多少。64MB = 226B,按字节编址 → 26 位。
- A 错22 位对应 4M,是单个芯片的单元数。
- B 错23 位只能编址 223B = 8MB,连实际装的 32MB 都覆盖不了,更不用说 64MB 的地址空间。
- C 错25 位对应实际装的 32MB,这是干扰条件。
- D 对26 位。
MAR 位数由地址空间决定;实际容量只影响用几片芯片。
练习:20 位地址、按字节编址 → 地址空间 220 B = 1MB,地址范围 00000H ~ FFFFFH(20 位 = 5 个十六进制位,全 1 就是 FFFFF)。一块 8KB 的区域从 0800H 开始,最后一个地址是 0800H + 2000H − 1 = 27FFH(8K = 213 = 2000H;十六进制加法 0800H + 2000H = 2800H,再减 1)。
0.5逻辑运算与位运算
| A | B | 与 AND(A·B) 都为 1 才 1 | 或 OR(A+B) 有 1 就 1 | 异或 XOR(A⊕B) 不同才 1 | 非 NOT(Ā) |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | Ā:0→1 |
| 0 | 1 | 0 | 1 | 1 | |
| 1 | 0 | 0 | 1 | 1 | 1→0 |
| 1 | 1 | 1 | 1 | 0 |
对一串位逐位做这些运算,就是“位运算”。它在 408 里主要有三个用途:
- 取出某几位(掩码):想要 x 的低 8 位,就算 x AND 0xFF(0xFF = 1111 1111,与 1 保留、与 0 清零)。
- 移位 = 乘除 2 的幂:左移 1 位相当于 ×2,右移 1 位相当于 ÷2(取整)。所以“地址右移 12 位”就是“去掉低 12 位的页内偏移,只剩页号”。
- 组合起来切字段:2013-46 让写二级页表的页目录号:
(LA >> 22) & 0x3FF——先右移 22 位把低位扔掉,再和 10 个 1 做与运算只留 10 位。
十进制里“把 3847 右移 2 位”就是 38(去掉最后两位 = 除以 100 取整),“3847 mod 100”就是 47(留下最后两位)。二进制里完全一样:右移 k 位 = 除以 2k 取整 = 去掉低 k 位;mod 2k = 只留低 k 位。所以“块号 mod 组数”在组数是 2 的幂时,就是“取块号的低几位”。这一点第 8 篇会反复用。
下面这张图把“切字段”完整做一遍。它就是第 8 篇分页、Cache 题的第一步,先在这里练熟:
试试:输入 −9 选 16 位看它的补码 FFF7H;输入 134 选 8 位看它当有符号数是多少;输入 FFFFFFDFH 选 32 位。第 2 篇会解释这些结果为什么是这样。
0.6本讲义的记号约定
| 写法 | 意思 | 例 |
|---|---|---|
2000H、0x2000 | 十六进制数 | 2000H = 8192 |
1011B、1011₂ | 二进制数 | 1011B = 11 |
(R1) 或 R[r1] | 寄存器 R1 里面的内容 | (R1) = 1234H |
((R1)) 或 M[R[r1]] | 以 R1 的内容为地址,去内存里取出的内容 | 先得 1234H,再取内存 1234H 单元 |
A → B 或 B ← A | 把 A 送到 B 里 | (PC) → MAR |
[x]补 | x 的补码(机器里存的那串位) | [−9]补 = FFF7H(16 位) |
⌈x⌉、⌊x⌋ | 向上取整、向下取整 | ⌈log2500⌉ = 9 |
| 计组 操作系统 融合 | 知识点所属;“融合”是两门课交界 |
- 把
3A7H写成二进制,再写成十进制。答案
0011 1010 0111B;3×256 + 10×16 + 7 = 768+160+7 = 935。 - 某 Cache 有 512 组,组号要几位?一个磁道有 500 个扇区,扇区号至少几位?
答案
512 = 29 → 9 位;⌈log2500⌉ = 9 位。 - 页面大小 4KB、按字节编址,页内偏移几位?32 位虚拟地址有多少个页?
答案
4KB = 212B → 12 位;页数 = 232/212 = 220 = 1M 个。 - 主频 2.5GHz 的时钟周期是多少 ns?
答案
1/(2.5×109) s = 0.4 ns。 - 32 位地址
0040 1025H右移 12 位是多少?它的低 12 位是多少?答案
右移 12 位 = 去掉最后 3 个十六进制位 → 00401H;低 12 位 = 最后 3 个十六进制位 → 025H。(这就是 2019-46 的“页号”和“页内地址”。)
0.✎本篇真题闯关(1 题)
本篇各小节挂的真题全部在这里,按小节排好。可以展开后一道道做,也可以用弹窗模式连续做。做题记录(对/错)只保存在你这台电脑的浏览器里,小节标题下的题号会随之变绿或变红。
一台计算机的全景:硬件、操作系统与性能
先建立一张“整机地图”:计算机由哪几块组成、一条指令怎么被执行、操作系统夹在硬件和程序之间干什么、怎样衡量一台机器快不快。这一篇的题基本都是选择题,但它是后面所有篇章的坐标系。
1.1冯·诺依曼结构:五大部件 + “存储程序”
1945 年冯·诺依曼提出的设计思想,今天所有电脑、手机都还在用。核心就两句话:
① 存储程序:程序(一条条指令)和数据先一起存进存储器,然后机器自己一条接一条地取出来执行,不需要人干预。
② 五大部件:运算器、控制器、存储器、输入设备、输出设备。指令和数据都用二进制,在存储器里长得一模一样,没有标签说明“我是指令”。
- A. 指令操作码的译码结果
- B. 指令和数据的寻址方式
- C. 指令周期的不同阶段
- D. 指令和数据所在的存储单元
展开解析
C指令和数据在存储器里长得一模一样,都是二进制位串;CPU 只能靠“什么时候、按什么地址取出来”来区分。
- 取指阶段:按 PC 的值访存,取出来的一定是指令,送进 IR。
- 执行阶段:按指令地址码给出的有效地址访存,取出来的是数据,送进运算器(或寄存器)。
- A 错因果倒了:只有先知道“这是指令”才会把它送进 IR 去译码;数据根本不会被送去译码,所以不可能靠译码结果来区分。
- B 错寻址方式只规定“操作数的地址怎么算”,它本身写在指令里;取指令时用的是 PC,不经过寻址方式,所以它区分不了指令和数据。
- C 对取指周期取到的是指令,执行周期取到的是数据——这就是冯·诺依曼机的区分办法。
- D 错同一个存储单元既可以放指令也可以放数据(自修改程序就是例子),存储器没有划分“指令区 / 数据区”的硬件标记。
冯·诺依曼机靠“指令周期的阶段”区分指令和数据;哈佛结构才把指令和数据放进两个存储器。
冯·诺依曼机的特点,选择题常逐条考(2019-12):① 由运算器、控制器、存储器、输入、输出五大部件组成;② 指令和数据以同等地位存放在存储器中,按地址访问;③ 指令和数据都用二进制表示,形式上没有区别;④ 指令由操作码(做什么)和地址码(对谁做)组成;⑤ 指令在存储器中按顺序存放,通常顺序执行,遇到转移指令才改变顺序;⑥ 早期以运算器为中心。注意:数据一般放在存储器里,指令里给的是它的地址,只有立即数才直接写在指令里。
- A. 程序的功能都通过中央处理器执行指令实现
- B. 指令和数据都用二进制表示,形式上无差别
- C. 指令按地址访问,数据都在指令中直接给出
- D. 程序执行前,指令和数据需预先存放在存储器中
展开解析
C冯·诺依曼机“按地址访问”:指令和数据都存放在存储器中,指令里一般给出的是数据的地址。
- A 对程序的功能都由 CPU 执行指令来完成。叙述正确。
- B 对指令和数据都用二进制表示,形式上没有区别(靠取出的阶段区分,见 2009-11)。叙述正确。
- C 错“数据都在指令中直接给出”错:只有立即寻址才把数据写在指令里,一般数据放在存储器中。本题选它。
- D 对存储程序:执行前指令和数据要预先存入存储器。叙述正确。
冯·诺依曼机的核心是“存储程序 + 按地址访问 + 二进制 + 五大部件”。
五大部件各管什么:主机和外设怎么分
五大部件是“功能上”的划分。真正拿起一台电脑,教材还有一种“物理上”的划分:主机和外部设备。这两种划分放在一张图里看:
| 部件 | 干什么 | 由什么组成 | 选择题常考 |
|---|---|---|---|
| 运算器 | 做算术运算(加减乘除)和逻辑运算(与或非、移位) | 核心是 ALU;外加 ACC、MQ、X、通用寄存器、程序状态字 PSW | ALU 宽度 = 机器字长(1.2) |
| 控制器 | 取指令、分析指令、按节拍发出控制信号,指挥所有部件 | CU(控制单元)、IR、PC | “计算机的指挥中心”;第 4 篇 |
| 主存 | 存放正在运行的程序和数据 | 存储体 + MAR + MDR(1.2) | CPU 能直接访问的只有主存(和寄存器、Cache) |
| 辅存 | 长期保存程序和数据,断电不丢 | 磁盘、固态硬盘、光盘、U 盘 | 属于外设;程序要先装入主存才能执行 |
| 输入 / 输出设备 | 把外界信息送进来 / 把结果送出去 | 键盘、鼠标 / 显示器、打印机 | 经 I/O 接口挂在总线上(第 9 篇) |
运算器里的三个专用寄存器。教材的模型机运算器里除了 ALU,还有三个寄存器:ACC(累加器)、MQ(乘商寄存器)、X(操作数寄存器)。做不同运算时它们分工不同,选择题偶尔会问“做乘法时乘数放在哪”:
| 运算 | ACC(累加器) | MQ(乘商寄存器) | X(操作数寄存器) |
|---|---|---|---|
| 加法 | 被加数 → 算完变成和 | 不用 | 加数 |
| 减法 | 被减数 → 算完变成差 | 不用 | 减数 |
| 乘法 | 乘积的高位 | 乘数 → 算完变成乘积的低位 | 被乘数 |
| 除法 | 被除数 → 算完变成余数 | 商 | 除数 |
为什么乘法的积要占 ACC 和 MQ 两个寄存器?n 位 × n 位的积最长有 2n 位,一个寄存器装不下。2.5 节的乘法器电路里,“部分积 P”就是 ACC,“乘数 Y”就是 MQ,“被乘数 X”就是 X——到那里再对照着看会很清楚。
计算机的发展与分类(大纲写了“发展与分类”,了解即可)
| 代 | 大致年代 | 逻辑元件 | 这一代的标志 |
|---|---|---|---|
| 第一代 | 1946—1957 | 电子管 | 1946 年的 ENIAC 是第一台电子数字计算机,但它还不是存储程序式的,改程序要重新插线;1945 年冯·诺依曼在 EDVAC 方案里提出“存储程序”。只能用机器语言、汇编语言编程 |
| 第二代 | 1958—1964 | 晶体管 | 出现 FORTRAN 等高级语言和编译程序,出现批处理的监督程序(操作系统的雏形) |
| 第三代 | 1965—1971 | 中小规模集成电路 | 高级语言迅速发展;操作系统走向成熟,出现多道程序、分时系统 |
| 第四代 | 1972—现在 | 大规模、超大规模集成电路 | 出现微处理器和微型计算机;并行、流水线、Cache、虚拟存储器等技术普遍使用 |
各本教材划分的年份略有出入,记“元件”就够了。摩尔定律:集成电路上能容纳的晶体管数目,大约每 18~24 个月翻一番(性能也大致随之翻倍)。微处理器按字长经历了 4 位、8 位、16 位、32 位到 64 位。
| 按什么分 | 分成 |
|---|---|
| 处理的信息形式 | 电子数字计算机(处理离散的 0/1,今天的计算机都是);电子模拟计算机(处理连续变化的电压等物理量) |
| 用途 | 通用计算机;专用计算机(为某类问题专门设计,如工业控制) |
| 规模、速度、价格 | 巨型机(超级计算机)、大型机、中型机、小型机、微型机、单片机 |
| 指令流和数据流 | SISD、SIMD、MISD、MIMD(Flynn 分类,第 4 篇 4.5) |
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 指令和数据分别存放在两个独立的存储器中
- B. CPU 在取指阶段取出的是指令,在执行阶段按地址取出的是数据
- C. 指令中不能包含操作数
- D. 程序执行时不必预先存放在存储器中
1.2走进 CPU 和主存:几个必须认识的寄存器
寄存器是 CPU 里的“小抽屉”,速度最快、数量很少。下面这张模型机图里的每个名字,后面都会反复出现,第一次见先认个脸:
| 名字 | 干什么 | 要记住的性质 |
|---|---|---|
| PC 程序计数器 | 存下一条要执行的指令的地址;取完一条指令会自动加“1”(一条指令的长度) | 程序员可见(跳转指令能改它) |
| IR 指令寄存器 | 存当前正在执行的指令 | 程序员不可见;位数 = 指令字长 |
| CU 控制单元 | 分析 IR 里的指令,发出控制信号 | 第 4 篇详讲 |
| ALU 算术逻辑单元 | 做加减、与或非、移位 | 宽度 = 机器字长 |
| 通用寄存器 | 暂存操作数和结果(R0、R1…;x86 里叫 eax、ebx…) | 程序员可见;宽度 = 机器字长 |
| PSW 程序状态字/标志寄存器 | 记录运算结果的特征(有无进位、溢出、是否为零、正负),以及 CPU 状态(第 5 篇的“用户态/内核态”、中断允许位) | 程序员可见 |
| MAR 存储器地址寄存器 | 存要访问的内存地址 | 位数决定能寻址多少单元;不可见 |
| MDR 存储器数据寄存器 | 存从内存读出的/要写入内存的数据 | 位数一般 = 存储字长;不可见 |
打开主存看一看:存储体、存储单元、MAR 和 MDR
第 0 篇把主存比作“一排带门牌号的格子”。教材对这排格子有一套专门的名词,选择题和大题的第一问经常用到,下面这张图把它们一次对上号:
| 名词 | 意思 | 和谁有关 |
|---|---|---|
| 存储元 | 存 1 位二进制的物理器件(一个小电容或一个触发器),存储器里最小的零件 | —— |
| 存储单元 | 若干个存储元组成,共用一个地址,一次一起读写 | 个数 = 2MAR 位数 |
| 存储字 / 存储字长 | 存储字 = 一个存储单元里存的那串二进制代码;存储字长 = 一个存储单元有多少位(8、16、32 位……) | MDR 位数一般 = 存储字长 |
| 存储体 | 全部存储单元的集合 | 容量 = 单元个数 × 存储字长 |
字节永远是 8 位;“字”的长度随机器而定——说“字长 16 位的机器”,1 字就是 16 位;说“字长 32 位”,1 字就是 32 位。题目写“按字编址、字长 32 位”,意思是每个地址对应 4 个字节。(x86 汇编里的 word 专指 16 位、dword 指 32 位,是历史遗留叫法,看到时按题目语境理解。)
PC 和 MAR 的位数:一般都等于地址位数;但如果指令都按字对齐,PC 的最低几位永远是 0,可以省掉不存(下面 2016-18)。
机器字长:CPU 一次整数运算能处理的位数 = ALU 宽度 = 通用寄存器宽度(“32 位机”说的就是它)。指令字长:一条指令有多少位(决定 IR 位数)。存储字长:一个存储单元有多少位(决定 MDR 位数)。三者可以不相等。
“汇编程序员可见”的寄存器:PC、通用寄存器、PSW/标志寄存器、基址/变址寄存器;不可见:IR、MAR、MDR、微指令寄存器(2010-18、2015-43、2021-17 都考过)。
- A. Ⅰ、Ⅱ
- B. Ⅰ、Ⅲ
- C. Ⅱ、Ⅲ
- D. Ⅱ、Ⅲ、Ⅳ
展开解析
考点1.2 三种字长
B机器字长 = CPU 一次能处理的整数的位数 = ALU 宽度 = 通用寄存器宽度。
- Ⅰ 对ALU 的宽度就是机器字长的定义之一。
- Ⅱ 错指令寄存器的位数 = 指令字长,可以比机器字长短或长(如变长指令)。
- Ⅲ 对通用寄存器存放整数操作数,宽度 = 机器字长。
- Ⅳ 错浮点寄存器的位数由浮点格式决定(如 64 位 double),不一定等于机器字长。
三种字长:机器字长(ALU、通用寄存器)、指令字长(IR)、存储字长(MDR),可以各不相同。
- A. 30、30
- B. 30、32
- C. 32、30
- D. 32、32
展开解析
BIR 要装下一整条指令;PC 要能表示所有可能的指令地址。
- IR:指令 32 位定长 → IR 至少 32 位。
- PC:主存 4GB 按字节编址,地址本来 32 位;但指令 4 字节且按字边界对齐,每条指令的地址最低 2 位都是 00,不必存——能放指令的位置只有 4GB ÷ 4B = 230 个 → PC 至少 30 位。
- A 错IR 只有 30 位装不下 32 位的指令;按字对齐能省的是 PC 的低 2 位,IR 要存整条指令,一位都不能省。
- B 对PC 30 位、IR 32 位。
- C 错PC 32 位、IR 30 位正好把两者弄反:IR 要装整条 32 位指令,PC 才能因按字对齐省掉最低 2 位。
- D 错PC 32 位够用但不是“至少”:指令按字边界对齐,地址最低 2 位恒为 00,PC 只需 30 位。
“至少多少位”题:看有没有对齐——对齐到 2k 字节,地址最低 k 位恒为 0,可以省去。
MAR、MDR 装在哪?哪些寄存器程序员“看得见”
问题引入:图 1-3 把 MAR、MDR 画在 CPU 里,上面“打开主存看一看”的图却说主存由存储体、MAR、MDR 组成——它们到底归谁?再看 2010-18:四个选项都是 CPU 里的寄存器,为什么只有 PC 算“可见”?这两个问题其实在问同一件事:从哪个角度看。
MAR、MDR 像银行柜台的“取号单”和“递物托盘”:按业务算,它们属于“柜台窗口”(主存系统);按摆放位置,现代银行把托盘装在了客户这一侧(CPU 芯片里),离客户更近、递得更快。不像的地方:托盘客户摸得到,MAR、MDR 程序员却摸不到——没有哪条指令能点名读写它们,它们只在每次访存时被硬件自动使用。
教材口径:冯·诺依曼机的教材模型里,主存储器由存储体、MAR、MDR 组成(另有地址译码器和读写控制电路);现代计算机中,MAR 和 MDR 通常集成在 CPU 芯片内,而地址译码器在存储器(内存芯片)一侧。题目问“主存的组成”时按教材模型答;问“现代计算机中 MAR、MDR 在哪个部件里”时答 CPU。
“透明”与“可见”:说某个部件对某人透明,意思是“他看不见、也不用管”(和日常用语相反,1.4)。判断一个寄存器对汇编程序员是否可见,只问一句:有没有机器指令能读它、写它,或者把它的值当作执行的依据?有就可见。
| 寄存器 | 存什么 | 可见? | 理由(能被哪类指令用到) | 真题 |
|---|---|---|---|---|
| PC | 下一条指令的地址 | 可见 | 转移、调用、返回指令就是在改写 PC;相对寻址以 PC 为基准 | 2010-18 |
| 通用寄存器(R0…、eax…) | 操作数、结果 | 可见 | 指令里直接写出它的编号或名字 | 2015-43 |
| PSW / 标志寄存器 | ZF、CF、OF、SF 和状态位 | 可见 | 比较、算术指令设置标志,条件转移指令读标志 | 2021-17 Ⅳ |
| 基址、变址寄存器 | 基地址、下标 | 可见 | 基址 / 变址寻址要在指令里指定用哪一个 | 2021-17 Ⅲ |
| 栈指针 SP | 栈顶地址 | 可见 | push、pop、call、ret 都隐含地使用它,也能用 mov 改它 | 第 3 篇 |
| IR | 当前正在执行的指令 | 不可见 | 取指时由硬件自动装入,没有指令能读写它 | 2021-17 Ⅰ |
| MAR、MDR | 访存地址 / 访存数据 | 不可见 | 每次访存由硬件自动使用 | 2010-18 |
| 暂存器(ALU 输入端 Y、输出端 Z 等) | 中间结果 | 不可见 | 数据通路内部用的,第 4 篇单总线大题里常见 | 2015-43 |
| 微指令寄存器 μIR、微地址寄存器 | 当前微指令及其地址 | 不可见 | 在控制器内部,比机器指令还低一层(图 1-8 的 M0) | 2021-17 Ⅱ |
| Cache、TLB | 主存 / 页表内容的副本 | 不可见 | 由硬件自动管理(第 8 篇) | — |
真题怎么考:2010-18 四选一(只有 PC 可见);2021-17 组合题(基址寄存器、标志寄存器可见,IR、微指令寄存器不可见);2015-43(1)在单总线结构图里问“哪些寄存器程序员可见”,答通用寄存器 R0~R3 和 PC,暂存器 T、MAR、MDR、IR 都不可见。秒杀法:IR、MAR、MDR、各种暂存器、带“微”字的都不可见;指令里能写出名字的(Rx、SP、基址、变址)和被转移类指令读写的(PC、标志)都可见。口诀失效的情况:题目换了“对谁透明”的对象。对高级语言程序员,连通用寄存器、PC 也是透明的(变量放哪个寄存器由编译器决定);对操作系统内核,PSW 里的特权位(用户态 / 内核态、中断允许位)可以用特权指令改写(第 5 篇)。
① PC 可见,但不一定能直接赋值:x86 没有 mov eip, … 这样的指令,PC“可见”是因为 jmp、call、ret 等指令就是在改它。② MAR、MDR 在 CPU 里,不等于它们可见:位置和可见性是两回事。③ 地址译码器在存储器里,不在 CPU 里。
- 某 CPU 有 R0~R7、SP、PC、IR、MAR、MDR、暂存器 Y 和 Z、PSW、微地址寄存器。哪些对汇编程序员可见?
答案
R0~R7、SP、PC、PSW。其余(IR、MAR、MDR、Y、Z、微地址寄存器)都由硬件自动使用,不可见。 - 判断:“地址译码器在 CPU 中”“现代计算机的 MAR 在 CPU 中”。
答案
前者错,译码器在存储器一侧;后者对。 - 对 C 语言程序员来说,通用寄存器是透明的吗?
答案
是透明的:C 程序员写的是变量,变量放在哪个寄存器、什么时候放回内存,由编译器决定。
四种“宽度”:机器字长、指令字长、存储字长、数据总线宽度
问题引入:“32 位机”到底什么是 32 位?地址?指令?一次读内存的位数?其实这几个数在同一台机器上可以各不相同,真题就爱在这里设陷阱:2016-18 问 PC 和 IR 的位数,2020-12 问哪个部件的位数“一定”等于机器字长。
大白话:把 CPU 想成一个厨师。机器字长是他一刀能切多宽(ALU 一次算几位);指令字长是菜谱上一句话有多长;存储字长是仓库里一个格子有多大;数据总线宽度是传菜窗口一次能递出多宽的托盘。四样东西由不同的设计决定,所以不必相等。(不像的地方:厨师可以多切几刀;机器字长说的是一条整数运算指令一次处理的位数,更宽的数要拆成几条指令去算。)
| 名称 | 教材定义 | 决定谁的位数 | 补充 |
|---|---|---|---|
| 机器字长 | CPU 一次整数运算所能处理的二进制位数 | ALU、通用寄存器 | “n 位机”说的就是它(2020-12) |
| 指令字长 | 一条指令包含的二进制位数 | IR(至少等于最长指令) | 可以定长或变长;指令字长 = 1 个机器字长叫单字长指令,2 个叫双字长指令(第 3 篇) |
| 存储字长 | 一个存储单元存放的二进制位数 | MDR | 按字编址时,一个地址对应一个存储字 |
| 数据总线宽度 | 数据总线的根数,即一次能并行传送的位数 | 一次总线传送的数据量 | 也叫数据通路带宽(1.8);可以是存储字长的整数倍(第 8 篇交叉存储一次读多个字) |
| 附:地址位数 | 主存地址的二进制位数 | MAR、地址总线根数,PC 一般也等于它 | 可寻址单元数 = 2地址位数 |
跟我做一遍(数字均已用 verify/08/p01_calc.py 验算):
展开解析
- 单元数:“512K × 32 位”的前半是单元个数 512K = 29 × 210 = 219 → MAR 19 位(依据:MAR 为 n 位时单元数 = 2n)。
- MDR:后半 32 位是每个单元的位数,即存储字长 → MDR 32 位。
- 容量:219 × 4B = 221 B = 2MB。
- 改按字节编址:每个字节都要一个地址,单元数变成 221 → MAR 21 位;容量还是 2MB,每次访存仍读写 32 位,MDR 仍是 32 位。
展开解析
- 通用寄存器 = 机器字长 = 32 位。
- IR 要装下最长的指令 → 32 位(只看最长的,不看最短的)。
- MAR = 地址线根数 = 30 位;按字节编址,最大容量 230 B = 1GB。
- MDR = 一次访存读写的位数 = 64 位(比机器字长还宽,这是允许的)。
- 存储器一次读出的是“起始地址为 8 的倍数”的 8 个字节。1000H 是 8 的倍数 → 1 次;从 1004H 开始的 8 字节是 1004H~100BH,跨了 1000H~1007H 和 1008H~100FH 两个存储字 → 2 次。这就是第 2 篇要求“边界对齐”的原因。
展开解析
- “16 位计算机” → 机器字长 16 位(ALU、通用寄存器 16 位)。
- “按字节编址” → 编址单位 1B;“存取单位为 16 位” → 存储字长 16 位,MDR 16 位。
- “16 位定长指令字” → 指令字长 16 位,IR 16 位。
- 一条指令占 2 字节,按字节编址 → 取指后 PC + 2(不是加 1)。
真题怎么考:2016-18(PC 跟地址走,指令按 4 字节对齐时可去掉最低 2 位,所以 30 位;IR 跟指令字长走,32 位);2020-12(只有 ALU、通用寄存器“一定”等于机器字长);2011-15(MAR 位数看地址空间 64MB,不看实际装了 32MB)。方法失效的情况:“IR 位数 = 指令字长”对变长指令要取最长的;“PC 位数 = 地址位数”只是一般情况,题目问“至少”且指令对齐时,PC 可以去掉恒为 0 的低位。
- 某机按字节编址,主存 256MB,存储字长 32 位。MAR、MDR 各几位?若改为按字(32 位)编址,MAR 几位?
答案
256MB = 228 B → 按字节编址 MAR 28 位;MDR 32 位。按字编址:228 ÷ 4 = 226 个字 → MAR 26 位。 - “64 位 CPU 的地址一定是 64 位”对吗?
答案
不对。机器字长和地址位数是两回事。例如 x86-64 处理器机器字长 64 位,但目前的虚拟地址一般只用 48 位,物理地址线也远少于 64 根。 - 机器字长 32 位、数据总线 16 位的机器,读一个 32 位整数至少要几次总线传送?
答案
2 次:每次只能并行传 16 位。早期的 Intel 8088 就是这种设计(内部 16 位、外部数据总线 8 位)。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 29、16
- B. 30、16
- C. 29、32
- D. 30、32
展开解析
A1GB = 230B,地址 30 位。指令按 2 字节对齐,指令地址的最低位总是 0,不用存,PC 至少 29 位。IR 要放下一条完整的指令,16 位。
变化点:2016-18 是 4GB、32 位指令、按 4 字节对齐(PC 30 位、IR 32 位)。PC 位数 = 地址位数 − log2(对齐字节数),IR 位数 = 指令字长。
Ⅰ. 程序计数器 PC Ⅱ. 存储器地址寄存器 MAR Ⅲ. 通用寄存器 Ⅳ. ALU 输入端的暂存器 Ⅴ. 程序状态字寄存器 PSW
- A. 仅Ⅱ
- B. 仅Ⅱ、Ⅳ
- C. 仅Ⅰ、Ⅱ、Ⅳ
- D. 仅Ⅱ、Ⅳ、Ⅴ
展开解析
BMAR、MDR、IR、暂存器是 CPU 内部自己用的,程序员看不见。PC(转移指令会改它)、通用寄存器、PSW(条件转移要用标志位)都是可见的。
变化点:两道真题问“可见的”,这里问“不可见的”,还加入了暂存器。判断标准:写机器语言程序时能不能直接用到或影响它。
1.3一条指令是怎么被执行的
CPU 一辈子只做一件事,周而复始:
用一个例子走一遍:C 语言 c = a + b; 编译后大约是三条指令:
0000H: LOAD R1, [2000H] ; 把内存 2000H 单元的数(a)读进 R1 0004H: ADD R1, [2004H] ; R1 加上内存 2004H 单元的数(b) 0008H: STORE R1, [2008H] ; 把 R1 写回内存 2008H 单元(c)
开始时 PC = 0000H。取指:按 0000H 取出 LOAD 指令放进 IR,PC 变成 0004H(这台机器一条指令 4 字节、按字节编址,所以“加 1 条”就是加 4);译码发现是“读内存”;按 2000H 读出 a 放进 R1。然后按 PC=0004H 取下一条……一直这样下去。
把每一小步里各个寄存器装的是什么列成一张表(假设内存里 a = 5、b = 3)。第一次学一定要跟着表一行一行看,后面第 4 篇的“数据通路”大题就是把这张表写成控制信号:
| 阶段 | 这一步做什么 | PC | MAR | MDR | IR | R1 |
|---|---|---|---|---|---|---|
| 取指 1 | PC 的值送 MAR | 0000H | 0000H | — | — | ? |
| 按 MAR 读内存,读出的指令进 MDR;PC + 4 | 0004H | 0000H | LOAD 指令 | — | ? | |
| MDR 送 IR,交给 CU 译码 | 0004H | 0000H | LOAD 指令 | LOAD R1,[2000H] | ? | |
| 执行 1 | 指令里的地址 2000H 送 MAR,读内存,数据 5 进 MDR | 0004H | 2000H | 5 | LOAD… | ? |
| MDR 送 R1 | 0004H | 2000H | 5 | LOAD… | 5 | |
| 取指 2 | 同上:MAR←0004H,读出 ADD 指令进 MDR 再进 IR,PC + 4 | 0008H | 0004H | ADD 指令 | ADD R1,[2004H] | 5 |
| 执行 2 | MAR←2004H,读出 3 进 MDR;ALU 算 R1 + MDR = 8,结果写回 R1 | 0008H | 2004H | 3 | ADD… | 8 |
| 取指 3 | MAR←0008H,取出 STORE 指令,PC + 4 | 000CH | 0008H | STORE 指令 | STORE R1,[2008H] | 8 |
| 执行 3 | MAR←2008H,R1 的 8 送 MDR,发“写”命令,8 写进内存 2008H | 000CH | 2008H | 8 | STORE… | 8 |
从这张表可以直接读出几个考点:
- MDR 在取指阶段装的是指令,在执行阶段装的是数据——同一个寄存器、同一种 0/1 串,身份只由“当时在哪个阶段”决定(这就是 2009-11 的答案 C)。
- PC 在取指一结束就已经指向下一条,所以执行阶段看到的 PC 是“下一条指令的地址”。以后算相对转移(第 3 篇)、中断断点(第 5 篇)都要用这一点。
- 每条指令至少访存一次(取指令本身);LOAD/ADD/STORE 这种带内存操作数的还要再访存一次,所以它们的指令周期比只用寄存器的指令长(2011-19)。
用教材的记号再写一遍(大题就这样作答)
上面的表是“看状态”,教材和真题答案更常用一种“看动作”的写法:每一步写成“谁 → 送到哪”。先认记号,一共就这几个:
| 记号 | 读法 |
|---|---|
| (PC) → MAR | 把 PC 的内容送进 MAR(圆括号 = “里面的内容”,第 0 篇 0.6) |
| M(MAR) → MDR | 按 MAR 里的地址读主存,读出的内容送进 MDR(M = 主存) |
| (MDR) → M(MAR) | 把 MDR 的内容写进 MAR 所指的主存单元(这是“写”) |
| (MDR) → IR | 取出的指令送进指令寄存器 |
| OP(IR) → CU | IR 里的操作码字段送控制单元译码 |
| Ad(IR) → MAR | IR 里的地址码字段(形式地址)送 MAR |
| (PC) + 1 → PC | PC 指向下一条指令(“+1”是加一条指令的长度,按字节编址、指令 4 字节时就是 +4) |
教材的模型机是单累加器结构:指令里只写一个内存地址 X,另一个操作数默认在 ACC 里。三条最基本的指令从头到尾是这样执行的:
取数 LOAD X(ACC ← [X])
取指:(PC)→MAR;M(MAR)→MDR;(MDR)→IR;(PC)+1→PC分析:OP(IR)→CU
执行:Ad(IR)→MAR;M(MAR)→MDR;(MDR)→ACC
存数 STORE X([X] ← ACC)
取指、分析:同左执行:Ad(IR)→MAR;(ACC)→MDR;(MDR)→M(MAR)(发“写”命令)
加法 ADD X(ACC ← ACC + [X])
取指、分析:同左执行:Ad(IR)→MAR;M(MAR)→MDR;(MDR)→X;(ACC)+(X)→ACC
c = a + b 在这台机器上就是 LOAD a;ADD b;STORE c——和前面表格里的三条指令一一对应(那里用的是通用寄存器 R1,这里用 ACC,道理一样)。
① 取指阶段所有指令都一样,一定是“PC → MAR → 读 → MDR → IR”这四个动作,PC 自增可以和“读主存”同时做。② 读主存一定是“地址进 MAR、结果出现在 MDR”;写主存一定是“地址进 MAR、数据先进 MDR、再发写命令”——MAR、MDR 是 CPU 和主存之间唯一的“窗口”。③ 指令的操作码去 CU(决定做什么),地址码去 MAR(决定对谁做)。第 4 篇写控制信号的大题,就是把这里的每一个箭头换成对应的 Xout、Yin 信号。
计算机的工作过程(教材的三句话):① 用编译、链接等工具把源程序变成可执行文件;② 操作系统把程序和数据装入主存;③ CPU 从程序的首地址开始,周而复始地“取指令 → 分析 → 执行”,直到程序结束。①②在第 3 篇 3.5 细讲,③就是本节。
- A. 每个指令周期中 CPU 都至少访问内存一次
- B. 每个指令周期一定大于或等于一个 CPU 时钟周期
- C. 空操作指令的指令周期中任何寄存器的内容都不会被改变
- D. 当前程序在每条指令执行结束时都可能被外部中断打断
展开解析
C哪怕是“什么都不做”的空操作指令,也要经过取指:取指后 PC 自动加上指令长度指向下一条——PC 是寄存器,它的内容变了。
- A 对没有 Cache 和指令预取,每条指令至少要访存一次把指令本身取出来。叙述正确。
- B 对一个指令周期由若干个时钟周期组成,至少一个。叙述正确。
- C 错空操作指令执行后 PC 改变了(还有 IR 也装入了这条指令),所以“任何寄存器都不变”错误——本题选它。
- D 对开中断时,CPU 在每条指令执行结束时检查中断请求,所以每条指令结束时都可能被打断。叙述正确。
取指一定会改变 PC 和 IR。“选错误的是”这类题,绿色底的那一项就是要选的错误叙述。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 所有指令的指令周期都相同
- B. 每个指令周期的取指阶段都要访问主存
- C. 每条指令都有间址周期
- D. 每条指令执行完后一定会进入中断周期
展开解析
B取指令要按 PC 访问主存,所以每个指令周期至少访存一次。不同指令的指令周期长短不同;只有间接寻址的指令才有间址周期;只有检测到中断请求(且允许响应)时才进入中断周期。
变化点:2011-19 找错误叙述(空操作指令也会改变 PC)。这里把指令周期的几个组成部分放进选项,问正确的一项。
1.4从 C 程序到电路:计算机系统的层次
- 编译程序:把高级语言一次性翻译成汇编/机器语言,生成目标文件;解释程序:翻一句执行一句,不生成目标文件;汇编程序:把汇编语言翻译成机器语言。
- C 程序变成可执行文件要经过 预处理 → 编译 → 汇编 → 链接 四步(2022-20),第 3 篇详讲。
- “透明”在计算机里的意思是看不见(与日常用法相反):对高级语言程序员,指令格式是透明的。
- 软件和硬件在逻辑功能上等价:同一个功能既可以用硬件做,也可以用软件做。比如乘法,可以造一个乘法器(硬件,快、贵),也可以写一段“移位 + 加法”的程序(软件,慢、便宜,2020-43 就考这个)。把常用的软件固化在 ROM 里,叫固件(如 BIOS)。
教材的“五级”层次模型:实际机器与虚拟机器
图 1-8 是按“谁在用”画的。教材还有一种按“机器”画的五级模型:把每一层都看成一台能执行本层语言的“机器”,上层机器的程序由下层机器翻译或解释后执行。
软件的分类、三种语言和三种“翻译程序”
计算机系统 = 硬件 + 软件。软件按用途分两类:
系统软件
保证计算机系统高效、正确运行的基础软件,一般由厂商提供:操作系统、数据库管理系统(DBMS)、语言处理程序(编译程序、汇编程序、解释程序)、链接程序、分布式软件系统、网络软件系统、标准程序库、服务性程序(诊断、调试程序)。应用软件
为解决某个应用领域的问题而编写的程序:办公软件、浏览器、科学计算程序、游戏、你自己写的程序……易错:DBMS 是系统软件;而“学生成绩管理系统”这种用 DBMS 做出来的程序是应用软件。
| 语言 | 长什么样 | 机器能直接执行吗 | 可移植性 |
|---|---|---|---|
| 机器语言 | 二进制代码 | 能,唯一能直接执行的(2015-12) | 和具体机器绑定 |
| 汇编语言 | 用助记符代替操作码(add、mov),用符号代替地址 | 不能,要汇编 | 和具体机器绑定(与机器语言一一对应) |
| 高级语言 | C、Java、Python,接近自然语言和数学公式 | 不能,要编译或解释 | 与机器无关,可移植 |
| 程序 | 把什么变成什么 | 工作方式 | 特点 |
|---|---|---|---|
| 汇编程序(汇编器) | 汇编语言源程序 → 机器语言目标程序 | 整体翻译,生成目标程序 | 与机器语言一一对应,翻译简单 |
| 编译程序(编译器) | 高级语言源程序 → 汇编语言或机器语言目标程序(2016-12) | 一次全部翻译完,生成目标程序,之后直接运行目标程序 | 翻译一次、执行多次,执行快 |
| 解释程序(解释器) | 高级语言源程序 → 边翻译边执行 | 翻译一句、执行一句,不生成目标程序 | 每次运行都要重新翻译,执行慢 |
三者都是“语言处理程序”(王道把编译、解释都叫“翻译程序”,汇编程序也是一种翻译程序),区别在于是否生成目标程序:编译、汇编生成,解释不生成。硬件描述语言(Verilog 等)描述的是电路设计,不是交给 CPU 执行的程序(2015-12 Ⅲ)。
计算机体系结构 vs 计算机组成:ISA 管“有什么”,组成管“怎么做”
| 计算机体系结构(ISA) | 计算机组成(微架构) | |
|---|---|---|
| 定义 | 机器语言程序员(和编译器)所看到的计算机的属性:概念性结构与功能特性 | 如何实现体系结构所规定的属性,对程序员透明 |
| 包括 | 指令系统(有哪些指令、指令格式、是否定长)、数据类型与格式、寻址方式、通用寄存器的个数和位数、存储器编址方式、I/O 指令、中断机制(如是否用向量中断)、虚拟存储管理方式 | 数据通路结构(单总线还是专用通路)、控制器用微程序还是硬布线、流水线级数(是否超流水线)、Cache 大小、乘法器是阵列乘法器还是用加法器移位实现、加法器的进位方式、时钟周期 |
| 经典例子 | “这台机器有没有乘法指令” | “乘法指令是用专用乘法器实现,还是用加法器多次相加实现” |
判别口诀(下面 2022-18 解析里也有):写汇编程序、做编译器时需要知道 → ISA;不需要知道 → 组成。真题:2022-18(指令格式、通用寄存器个数属于 ISA)、2025-16(是否定长指令字属于 ISA;阵列乘法器、微程序控制器、单总线数据通路都属于组成)、2026-16(I/O 指令、向量中断、虚拟存储管理方式由 ISA 规定,“是否用超流水线”不是)、2026-12(微架构是 ISA 的逻辑实现,说它是“物理实现层”是错的;再往下的芯片工艺才是物理实现)。
- A. 仅Ⅰ、Ⅱ
- B. 仅Ⅰ、Ⅲ
- C. 仅Ⅱ、Ⅳ
- D. 仅Ⅰ、Ⅲ、Ⅳ
展开解析
B判断方法:写汇编程序或编译器时需不需要知道它?需要 → 属于 ISA;只影响“怎么实现、实现得多快” → 属于计算机组成(微架构)。
- Ⅰ 对指令字格式和指令类型:写汇编必须知道 → ISA。
- Ⅱ 错CPU 的时钟周期:同一 ISA 的不同芯片主频可以不同 → 实现层面。
- Ⅲ 对通用寄存器的个数和位数:程序要用寄存器 → ISA。
- Ⅳ 错加法器的进位方式(串行/先行进位):程序员看不见 → 实现层面。
ISA = 程序员看得见的机器:指令、格式、寄存器、寻址方式、数据类型、I/O 与中断机制、虚存管理方式。
- A. 仅Ⅰ和Ⅳ
- B. 仅Ⅱ和Ⅲ
- C. 仅Ⅲ和Ⅳ
- D. 仅Ⅰ、Ⅲ和Ⅳ
同一个 ISA,多种实现:系列机与兼容
问题引入:同一个 Windows 安装包,装在 Intel 的 CPU 上能跑,装在 AMD 的 CPU 上也能跑;可苹果电脑从 Intel 芯片换成自家的 M 系列芯片后,老软件却要经过一个叫 Rosetta 2 的程序“翻译”才能运行。区别就在于:前者 ISA 相同,后者 ISA 变了。
ISA 像餐馆的菜单,微架构像后厨。两家连锁店用同一份菜单,后厨可以完全不同(一家用燃气灶、一家用电磁炉,一家 5 个厨师、一家 10 个),顾客照着菜单点菜都能吃到同样的菜;换了一份菜单,老顾客点的菜就没有了,得有人帮他“翻译”成新菜单上的菜。不像的地方:菜做得快慢顾客能直接感觉到;而 CPU 的微架构差异只会体现为“同一个程序跑得快慢不同”,运行结果必须完全一样。
教材说法:系列机是同一厂家生产的、具有相同的体系结构(ISA)、但组成和实现不同的一系列机器(经典例子是 IBM System/360 系列)。新机型要能运行旧机型上的软件,叫软件兼容(其中“新机器能运行老机器上的软件”叫向后兼容)。兼容的前提就是 ISA 相同或在原 ISA 上只增不改。
| 发生了什么变化 | 例子 | 旧程序还能直接运行吗 |
|---|---|---|
| 只换微架构,ISA 不变 | 新一代 CPU 加深流水线、加大 Cache、改用更快的乘法器 | 能,而且通常更快——这正是“组成对程序员透明”的好处 |
| ISA 扩展(只增不改) | x86 先后加入 MMX、SSE、AVX 等新指令 | 旧程序能在新 CPU 上运行;用了新指令的新程序在旧 CPU 上不能 |
| 换 ISA | x86 → ARM | 不能直接运行,要重新编译,或用模拟器 / 二进制翻译(性能有损失) |
真题怎么考:本节的 2022-18、2025-16、2026-16、2026-12 都在考“某项属于 ISA 还是属于组成(微架构)”。判别法仍是“写机器语言程序时需不需要知道”。容易判反的一对:“有没有乘法指令”是 ISA;“乘法指令用阵列乘法器还是用加法器加移位实现”是组成。
- 同一厂家先后推出的 A、B 两款 CPU 属于同一系列机。下列哪项一定相同:主频、Cache 容量、指令系统、流水线段数?
答案
指令系统(ISA)。系列机的定义就是体系结构相同、组成与实现不同;主频、Cache 容量、流水线段数都属于组成或实现。 - 为什么在 x86 电脑上编译出的 .exe 文件,不能直接拷到 ARM 手机上运行?
答案
.exe 里是 x86 的机器指令,ARM 处理器的 ISA 不同,看不懂这些指令的编码。要在 ARM 上运行,必须重新编译,或者用模拟器 / 二进制翻译软件把 x86 指令转换成 ARM 指令。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
Ⅰ. 程序员可以使用的通用寄存器个数 Ⅱ. 主存按字节编址还是按字编址
Ⅲ. Cache 采用几路组相联 Ⅳ. 是否采用 5 段指令流水线
- A. 仅Ⅰ、Ⅱ
- B. 仅Ⅰ、Ⅲ
- C. 仅Ⅱ、Ⅳ
- D. 仅Ⅰ、Ⅱ、Ⅲ
展开解析
AISA 规定的是机器语言程序员看得见的东西:指令格式和种类、寄存器、寻址方式、编址方式、数据类型、I/O 与中断机制等,所以Ⅰ、Ⅱ属于 ISA。Cache 的组织方式和流水线的段数属于计算机组成(微架构),换一种实现,程序照样能运行。
变化点:换了考法:真题问“指令格式、时钟周期、加法器进位方式”,这里问“编址方式、Cache、流水线”。判断标准不变:程序员写机器语言程序时是否必须知道。
- A. 编译程序把高级语言源程序翻译成目标程序
- B. 汇编程序把汇编语言程序翻译成机器语言程序
- C. 解释程序逐条翻译并执行源程序,不生成目标程序
- D. 汇编语言程序可以被 CPU 直接执行
展开解析
DCPU 能直接执行的只有机器指令(微程序控制的机器还要执行微指令)。汇编语言程序要先由汇编程序翻译成机器语言,所以 D 错。
变化点:真题问“哪些能被直接执行”,这里反过来问翻译程序的分工:编译、汇编生成目标程序,解释程序不生成目标程序。
1.5为什么需要操作系统:管家、接口、魔术师
先想象一台没有操作系统的电脑。你写了一个程序,想把 “hello” 存进磁盘里的一个文件,你得自己搞清楚:这块硬盘的控制器有哪些寄存器、往哪个寄存器写什么命令能让磁头移到第几号磁道、哪些扇区是空的、写完了怎么知道写好了……每个程序都要把这些重写一遍。更糟的是,如果同时跑两个程序,它们都认为“整块内存、整个磁盘都是我的”,互相覆盖对方的数据,谁都跑不对。
操作系统(OS)就是为了解决这两件事而装在硬件之上的一层管理软件:把难用的硬件包装成好用的服务,让多个程序安全地共用一台机器。从三个角度看它:
① 资源的管理者
管 CPU(进程调度)、内存(分配回收)、文件、设备。对应第 6~10 篇。② 用户和硬件之间的接口
命令接口(敲命令、点图标)和程序接口——即系统调用,程序通过它请 OS 办事。③ 扩充机器(虚拟机器)
把难用的裸机包装成好用的“虚拟机器”:程序以为自己独占 CPU、独占大内存。操作系统是指控制和管理整个计算机系统的硬件与软件资源,合理地组织、调度计算机的工作与资源的分配,进而为用户和其他软件提供方便的接口与环境的程序集合。它是计算机系统中最基本的系统软件。
没有任何软件支持的计算机叫裸机;装上操作系统后,用户看到的是一台功能更强、更好用的“扩充机器”(虚拟机器)。
作为资源管理者:四大功能分别管什么
| 功能 | 具体要做的事 | 本讲义 |
|---|---|---|
| 处理机管理 | 多道程序环境下,处理机的分配和运行都以进程(或线程)为基本单位,所以处理机管理也叫进程管理:进程控制(创建、撤销、状态转换)、进程同步、进程通信、死锁处理、处理机调度 | 第 6、7 篇 |
| 存储器管理 | 内存分配与回收、地址映射(逻辑地址 → 物理地址)、内存保护与共享、内存扩充(覆盖、交换、虚拟内存) | 第 8 篇 |
| 文件管理 | 文件存储空间管理(空闲块)、目录管理(实现“按名存取”)、文件读写管理和保护、文件共享 | 第 10 篇 |
| 设备管理 | 缓冲管理、设备分配、设备处理(驱动程序)、虚拟设备(SPOOLing)、设备独立性(程序用逻辑设备名,不依赖具体的物理设备);目标是完成用户的 I/O 请求、提高设备利用率 | 第 9 篇 |
操作系统的目标
方便性
提供命令接口、程序接口,用户不必了解硬件细节就能用计算机。有效性
提高 CPU、内存、I/O 设备等资源的利用率,提高系统吞吐量。可扩充性
方便增加新功能、支持新硬件:这是 OS 结构从无结构 → 模块化 → 分层 → 微内核演变的动力(1.7)。开放性
遵循国际标准,不同厂家的硬件和软件能彼此兼容、互相连接。其中方便性和有效性是设计操作系统时最重要的两个目标。
- A. 系统调用
- B. 中断
- C. 库函数
- D. 原语
展开解析
A操作系统给程序用的接口就是系统调用(程序接口);给人用的是命令接口和图形界面。
- A 对系统调用:程序通过陷入指令请操作系统办事,例如读文件、创建进程。
- B 错中断:是 CPU 和外设之间的一种硬件机制,不是提供给应用程序调用的接口。
- C 错库函数:属于用户态的程序代码(如 printf),它内部再去调用系统调用;库函数由编程语言的库提供,不是操作系统的接口。
- D 错原语:操作系统内部执行时不可被打断的小程序,应用程序不能直接调用。
OS 的接口 = 命令接口(给人)+ 程序接口(系统调用,给程序)。
四种接口放在一起对比(选择题常问“哪个是给程序用的”):
| 接口 | 怎么用 | 例子 |
|---|---|---|
| 联机命令接口 交互式命令接口 | 用户在终端上敲一条命令,命令解释程序执行完返回结果,再等下一条——“说一句,做一句” | Linux shell 里的 ls、cd;Windows 的 cmd |
| 脱机命令接口 批处理命令接口 | 用户事先用作业控制语言写好一份“作业说明书”,和作业一起交给系统;系统按说明书逐条执行,用户中途不能干预 | 早期批处理的作业控制语言;今天的 .bat、shell 脚本 |
| 程序接口 = 系统调用(广义指令) | 只能在程序里使用:程序执行陷入指令进入内核,请求操作系统服务(第 5 篇 5.6) | read、write、fork、open |
| 图形用户界面 GUI | 用窗口、图标、菜单、鼠标操作;最终也是通过调用系统调用来实现功能 | Windows 桌面 |
操作系统的四个特征
先把最容易混的一对词分清楚:并发是“一段时间里都在推进”,并行是“同一时刻真的都在做”。
| 特征 | 意思 | 要注意(常考) |
|---|---|---|
| 并发 | 两个或多个事件在同一时间间隔内发生。单 CPU 上宏观同时、微观交替 | OS 的并发是靠进程实现的——引入进程的目的就是让程序能并发执行(第 6 篇)。对比并行:同一时刻同时发生,需要多套硬件 |
| 共享 | 系统中的资源可供内存中多个并发执行的进程共同使用 | 互斥共享:一段时间内只允许一个进程访问,如打印机、磁带机、某些共享变量——这类资源叫临界资源(第 7 篇);同时共享:允许一段时间内多个进程“同时”访问,如磁盘上的可重入代码、文件(微观上仍可能交替) |
| 虚拟 | 把一个物理上的实体变成若干个逻辑上的对应物 | 时分复用:虚拟处理器(多道 + 分时,一个 CPU 当 n 个用)、虚拟设备(SPOOLing,一台打印机当多台用,第 9 篇);空分复用:虚拟存储器(第 8 篇)。若一台物理 CPU 被时分复用成 n 台虚拟处理器,每台虚拟处理器的平均速度 ≤ 物理 CPU 的 1/n(虚拟设备同理) |
| 异步 | 多道程序环境下,进程以不可预知的速度向前推进,走走停停 | 但只要运行环境相同,同一个作业多次运行的结果相同(由同步互斥机制保证,第 7 篇) |
四个特征的关系:并发和共享是最基本的两个特征,而且互为存在条件——没有并发,就只有一道程序在用资源,谈不上共享;反过来,若不能对共享资源进行有效管理,并发的程序就会互相干扰,并发也无法实现。虚拟以并发和共享为前提,异步是并发的必然结果。(有的教材只说“并发是最基本的特征”,单选题问“最基本的特征”时选并发。)
共享的两种方式、虚拟的两种技术:配例子看懂
问题引入:上表说共享分“互斥共享”和“同时访问”,虚拟靠“时分复用”和“空分复用”。同样是很多进程一起用,为什么打印机只能互斥共享,磁盘却可以“同时”访问?虚拟内存和虚拟打印机又为什么是两种不同的技术?
互斥共享像复印店的一台复印机:一个人的一整份材料印完,下一个人才能接着印,中途插队两份材料就混在一起了。同时访问像食堂打菜窗口:大家排队一勺一勺地打,一段时间里每个人都打到了菜,感觉是“一起在用”。时分复用像一位老师在三个班轮流上课(按时间切开);空分复用像把一块地划成三块分给三户人家(按空间切开)。不像的地方:食堂窗口每次服务的“一勺”很小,磁盘每次服务的是“一次读写请求”,也很小——同时访问能成立,恰恰因为交替的粒度小、交替了也不会出错。
教材定义:互斥共享方式——一段时间内只允许一个进程访问该资源(这样的资源叫临界资源,如打印机、磁带机,第 7 篇);同时访问方式——允许在一段时间内由多个进程“同时”访问(宏观上同时,微观上可能交替),如磁盘、用可重入代码编写的程序。时分复用技术——利用资源在时间上的空闲为多个用户服务,如虚拟处理器、虚拟设备(SPOOLing);空分复用技术——利用存储器的空闲空间分区域存放多道程序,如虚拟存储器、虚拟磁盘。
| 说法 | 意思 | 例子 | 详见 |
|---|---|---|---|
| 互斥共享 | 一段时间内只允许一个进程用,用完才轮到下一个 | 打印机、磁带机、多个进程都要修改的共享变量(临界资源) | 第 7 篇 |
| 同时访问 | 一段时间内允许多个进程交替访问,宏观上同时 | 磁盘、可重入代码(多个进程共用一份只读的程序代码) | 第 9、10 篇 |
| 时分复用 | 同一个资源按时间轮流给多个用户用 | 虚拟处理器(分时)、虚拟设备(SPOOLing) | 第 6、9 篇 |
| 空分复用 | 同一个资源按空间分成几份分别给多个用户用 | 内存划分给多个进程、虚拟存储器、虚拟磁盘 | 第 8 篇 |
真题怎么考:408 还没有单独考过这四个词,但“操作系统特征”一类组合题(2009-23、2018-23)的选项随时可能换成它们。判别法:能不能中途插进别的进程?不能 → 互斥共享;能 → 同时访问。分的是时间还是空间?时间 → 时分复用;空间 → 空分复用。失效的情况:同一个资源换一种用法,方式就会变——一个文件被多个进程只读时是同时访问,有进程要写它时就要互斥。
① “同时访问”不是物理上的同一瞬间:磁头一次只能读一个地方,只是请求交替得很快。② 互斥共享也是共享:多个进程都能用,只是要轮流、而且每人一次用完一整段。③ 时分复用得到的 n 台虚拟设备,每台都不比原来的物理设备快。
- 打印机、磁盘、多个进程共用的只读库代码、多个进程都要加 1 的全局计数器,各属于哪种共享方式?
答案
打印机:互斥共享;磁盘:同时访问;只读库代码:同时访问;要被修改的全局计数器:互斥共享(第 7 篇的临界资源)。 - 虚拟存储器、SPOOLing、分时系统的“每人一台虚拟机”各用了哪种复用技术?
答案
虚拟存储器:空分复用;SPOOLing:时分复用;分时系统的虚拟处理器:时分复用。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
Ⅰ. 两个进程的计算 Ⅱ. CPU 计算与打印机打印 Ⅲ. 磁盘的 DMA 传送与 CPU 计算 Ⅳ. 两个进程分别使用两台不同的设备做 I/O
- A. 仅Ⅱ、Ⅲ
- B. 仅Ⅱ、Ⅲ、Ⅳ
- C. 仅Ⅰ、Ⅱ、Ⅲ
- D. Ⅰ、Ⅱ、Ⅲ、Ⅳ
展开解析
B只有一个 CPU,两个进程的计算只能交替进行(并发),不能并行。CPU 与设备、设备与设备可以同时工作。
变化点:2009-23 的选项是抽象的“进程与进程、处理机与设备……”,这里换成具体场景。核心仍是:单 CPU 上进程只能并发,硬件部件之间可以并行。
- A. 库函数是操作系统提供给应用程序的接口
- B. 命令接口和系统调用都属于操作系统提供的用户接口
- C. 单 CPU 的多任务系统中,多个进程可以并行执行
- D. 原语是应用程序可以直接调用的接口
- A. 磁盘只能以互斥共享方式被多个进程使用
- B. 虚拟存储器采用的是时分复用技术
- C. SPOOLing 技术利用时分复用,把一台独占设备变成多台逻辑设备
- D. 采用时分复用后,每台虚拟处理器的速度都高于物理处理器
展开解析
CSPOOLing 让多个进程轮流使用同一台打印机(借助磁盘上的输出井),每个进程都以为自己有一台打印机,这是按时间轮流的时分复用。A 错:磁盘允许多个进程的请求交替访问,是同时访问方式。B 错:虚拟存储器借用磁盘空间、把内存按空间分给多个进程,是空分复用。D 错:n 台虚拟处理器分一台物理 CPU,每台平均速度不超过物理 CPU 的 1/n。
考点:共享的两种方式、虚拟的两种技术。判别时问两句:能不能中途插进别的进程?分的是时间还是空间?
1.6操作系统怎么一步步发展出来
整个发展史就是一个问题:CPU 太快、I/O 太慢,怎么别让 CPU 闲着?
| 阶段 | 怎么工作 | 特点(选择题原词) | 解决了什么 / 留下什么问题 |
|---|---|---|---|
| 手工操作 | 程序员把穿孔纸带装上机器,亲自操作 | 用户独占全机;CPU 等待人工操作 | 人机矛盾:人慢机器快,CPU 大量时间在等人 |
| 单道批处理 | 一批作业先存到磁带上,监督程序把它们一个接一个自动调入内存运行 | 自动性、顺序性、单道性(内存中始终只有一道作业) | 缓解了人机矛盾;但作业做 I/O 时 CPU 仍在空等 |
| 多道批处理 | 内存同时放多道作业,一道去做 I/O 就让 CPU 运行另一道(靠中断技术) | 多道;宏观上并行,微观上串行 | 资源利用率高、系统吞吐量大;但用户响应时间长、没有人机交互(作业提交后无法干预) |
| 分时系统 | CPU 时间切成很短的时间片,轮流为多个终端用户服务 | 同时性(多路性)、交互性、独立性、及时性 | 解决了人机交互;但不能优先处理紧急任务 |
| 实时系统 | 在严格的时间限制(截止时间)内完成对外部事件的响应和处理 | 及时性、可靠性 | 硬实时必须绝对按时(导弹控制、工业控制);软实时偶尔超时可以接受(视频播放、网上订票) |
| 网络操作系统 | 把网络中各台计算机有机结合起来,提供统一、经济而有效的使用方式 | 网络中资源的共享和各种通信 | 各台计算机仍各自完成自己的任务,只是能共享资源、互相通信 |
| 分布式操作系统 | 多台计算机组成一个整体系统,一个任务可以分布到多台机器上协同完成 | 分布性和并行性;各台计算机地位平等、无主从 | 与网络 OS 的本质区别:多台计算机协同完成同一个任务,对用户呈现为“一台机器” |
| 个人计算机 OS | Windows、Linux、macOS | 单用户多任务,界面友好 |
分时和实时都“响应快”,区别在于:分时追求让每个用户都及时得到响应(秒级),交互性强;实时追求在截止时间内一定完成(毫秒甚至微秒级),可靠性要求更高、交互性弱。
多道批处理让 CPU 忙起来了,但坐在终端前的人很难受:提交一个作业,要等它排队、跑完才看到结果,中间想改一个参数都不行。分时系统换了个思路:不等程序自己去做 I/O 才换人,而是按时间强行轮换——
多道程序能成立要靠两样硬件:中断(I/O 做完了设备主动通知 CPU,CPU 不用守着等)和通道/DMA(数据搬运不用 CPU 一个字一个字搬)。这两样都在第 9 篇。
多道到底省了多少:动手算一次
图 1-14 只是示意。考研题会给出具体的时间让你算“总共用多久、CPU 利用率多少”,方法就是画时间轴(甘特图)。先看一个例子:
展开解析
- 单道:A 用 10+40+10 = 60ms,B 用 30+20+20 = 70ms,总共 130ms;CPU 真正在算的时间 = 10+10+30+20 = 70ms,利用率 = 70/130 ≈ 53.8%。
- 多道:0~10 A 算;10 时 A 去 I/O(到 50),CPU 运行 B 到 40;40 时 B 去 I/O(到 60),CPU 没有就绪的程序,空闲到 50;50 时 A 回来算到 60,A 结束;60 时 B 的 I/O 完成,算到 80。总共 80ms,CPU 利用率 = 70/80 = 87.5%。
- 结论:CPU 要做的工作量(70ms)一点没少,省下的是“空等”的时间,所以多道提高的是资源利用率和系统吞吐量。对单个作业来说,它自己要用的 CPU 时间、I/O 时间一点没变,还可能因为要等别人让出 CPU 而完成得更晚(比如 A 的 I/O 在 50ms 结束时,如果 B 还占着 CPU,A 就得排队)——这就是多道批处理“平均周转时间长”的原因。
若两道程序用同一台 I/O 设备,I/O 也要排队(设备同一时刻只能服务一个),画图时给设备也单独画一行,按先来先服务排。真题 2012-29 就是这种题(第 6 篇 6.7 有完整甘特图,答案 260ms)。
- A. 仅Ⅱ、Ⅲ
- B. 仅Ⅱ
- C. 仅Ⅰ、Ⅱ
- D. 仅Ⅰ、Ⅲ
展开解析
A批处理:用户把作业交上去,系统成批自动运行,用户中途不能干预。
- Ⅰ 错批处理最大的缺点就是缺少交互性;允许多个用户与计算机直接交互的是分时系统。
- Ⅱ 对批处理系统分为单道批处理和多道批处理。
- Ⅲ 对有了中断,I/O 完成时设备主动通知 CPU,CPU 不必守着等,才能在 I/O 期间去运行其他程序——多道批处理中 CPU 与 I/O 设备并行工作。
批处理 → 提高吞吐量、无交互;分时 → 交互、及时响应;实时 → 截止时间内完成。
分时与实时、网络 OS 与分布式 OS:两组最容易混的分类
问题引入:分时和实时都说“响应快”;网络操作系统和分布式操作系统都是“很多台计算机连在一起”。选择题最爱把这两组的说法互换,考你分不分得清。
分时像餐馆服务员在几桌客人之间来回照应,让每桌都觉得“马上有人理我”;实时像急救中心,电话一来必须在规定时间内出车,晚到就是事故。网络 OS 像几户邻居,各过各的日子,但能互相借东西、打电话;分布式 OS 像几户人家合成一个大家庭,一件大事分给几个人一起干,外人看来就是“一户”。不像的地方:实时系统不一定比分时系统“快”,它的本质是保证在截止时间前完成——截止时间是 1 秒,它就保证 1 秒内完成。
| 分时系统 | 实时系统 | |
|---|---|---|
| 追求什么 | 每个用户都能及时得到响应、能交互 | 在截止时间之前一定完成对外部事件的处理 |
| 响应时间由谁定 | 人能接受的等待时间(通常秒级) | 被控制的对象(常常是毫秒甚至微秒级) |
| 交互性 | 强,用户随时可以输入命令 | 弱,主要响应外部事件,只提供有限的交互 |
| 可靠性要求 | 一般 | 很高,常采用冗余、容错 |
| 典型调度 | 时间片轮转 | 按优先级抢占、按截止时间调度(第 6 篇) |
| 例子 | 多个终端共享的主机 | 硬实时:导弹控制、汽车安全气囊,错过截止时间就是失败;软实时:视频播放、网上订票,偶尔超时可以接受 |
| 网络操作系统 | 分布式操作系统 | |
|---|---|---|
| 各台计算机 | 各自独立,有自己的操作系统 | 地位平等、无主从,由一个统一的分布式 OS 管理 |
| 一个任务 | 在一台机器上完成 | 可以分到多台机器上并行完成 |
| 用户看到 | 知道资源在哪台机器上,要指明去哪取 | 像使用一台计算机,资源位置透明 |
| 主要特征 | 资源共享、网络通信 | 分布性、并行性、透明性 |
真题怎么考:这两组概念近年选择题没有单独出过,但都在考纲里,常作为“操作系统发展与分类”组合题的选项(同 2016-23、2022-23 的考法)。判别法:看到“截止时间、可靠性”想实时,看到“交互、时间片、多终端”想分时;看到“协同完成同一任务、像一台计算机、无主从”想分布式,看到“各自独立、共享资源与通信”想网络 OS。失效的情况:“实时系统响应时间一定比分时短”不能当判据,软实时系统的截止时间可以是秒级,关键在“保证”二字。
- 汽车的防抱死刹车控制系统、网上购票系统各属于硬实时还是软实时?
答案
防抱死刹车:硬实时(错过截止时间可能车毁人亡);网上购票:软实时(偶尔慢一点只是体验差)。 - “各台计算机地位平等,一个大任务被拆开分给多台计算机并行处理,用户看起来像在用一台机器”描述的是哪种操作系统?
答案
分布式操作系统。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 用户可以通过终端与计算机交互
- B. 分时系统的首要目标是提高系统吞吐量
- C. 分时系统必须采用抢占式优先级调度
- D. 时间片越长,用户的响应越快
展开解析
A分时系统把 CPU 时间分成时间片轮流分给各用户,每个用户都像独占计算机,可以交互。B 是批处理系统的目标。C 错:分时系统一般用时间片轮转。D 错:时间片越长,轮一圈越慢,响应越慢。
变化点:2016-23 考批处理系统(不能交互)。这里换成分时系统,两者的目标正好不同:批处理要吞吐量,分时要响应时间。
- A. 20%
- B. 48.8%
- C. 60%
- D. 80%
展开解析
BCPU 空闲当且仅当 3 个进程都在等 I/O,概率 = 0.83 = 0.512。CPU 利用率 = 1 − 0.512 = 48.8%。
变化点:2017-28 只问多道程序的优点(CPU 利用率高、吞吐量大)。这里定量算一下:单道时只有 20%,3 道时提高到 48.8%。C 是把 3 个 20% 直接相加。
- A. 硬实时系统必须保证在截止时间之前完成任务
- B. 实时系统对可靠性的要求通常比分时系统高
- C. 分布式系统可以把一个任务分到多台计算机上协同完成
- D. 网络操作系统中各台计算机地位平等,对用户呈现为一台计算机
展开解析
D“地位平等、无主从、对用户像一台计算机”是分布式操作系统的特征;网络操作系统中各台计算机各自独立,用户知道资源在哪台机器上,D 把两者说反了。A、B 是实时系统的定义与特点;C 是分布式系统的本质(协同完成同一任务)。
1.7操作系统的结构、开机引导与虚拟机
内核的几种组织方式
内核怎么“盖楼”,教材总结了五种做法。先记住一个比方:操作系统像一家公司,宏内核是所有部门挤在一间大办公室里,喊一声就能配合(快),但一个人把咖啡洒了全办公室遭殃;微内核是只有总经理办公室在大楼核心区,其他部门都搬到外面的小楼,靠发邮件沟通(慢一点),但哪个小楼着火都不影响核心。
| 结构 | 怎么组织 | 优点 | 缺点 |
|---|---|---|---|
| 分层 | 分成若干层,每层只能调用紧挨着的下一层(最底层是硬件) | 便于自底向上逐层调试和验证;易扩充和维护 | 层次难以合理划分;一个请求要穿过很多层,效率低 |
| 模块化 | 按功能划分成模块,模块之间通过接口调用 | 多个模块可以并行开发,设计效率高 | 模块之间牵连多,正确性难保证。衡量标准:高内聚、低耦合 |
| 宏内核(大内核) | 进程、内存、文件、设备管理等主要功能都放在内核态 | 各功能直接互相调用,性能高(Linux、传统 UNIX) | 内核庞大,一处出错可能导致整个系统崩溃,维护难 |
| 微内核 | 内核只保留最基本的功能:与硬件紧密相关的处理(中断、陷入、时钟)、进程(线程)管理、低级存储管理、进程通信;文件系统、设备驱动等作为用户态的服务进程 | 可靠性、安全性、可扩展性、可移植性好,适合分布式系统;“机制”在内核、“策略”在用户态 | 服务之间靠消息通信,频繁在用户态和内核态之间切换,性能差 |
| 外核拓展 | 内核只负责为应用分配和保护物理资源(如直接分给某个应用一批磁盘块),资源怎么用由应用自己的“库操作系统”决定 | 应用可按需定制,减少虚拟化层次,效率高 | 应用的负担重,一致性难保证 |
分层 / 模块化回答“内核内部的代码怎么组织”;宏内核 / 微内核回答“哪些功能放在内核态”。所以它们可以组合:Linux 是宏内核,但内部按模块组织,设备驱动还能做成“可加载内核模块”在运行时装入——装入后仍在内核态运行,所以 Linux 仍是宏内核。判断宏 / 微只看一件事:文件系统、设备驱动这些服务在内核态还是用户态。
- “分层结构的操作系统中,第 3 层可以直接调用第 1 层的功能”对吗?
答案
不对。严格的分层结构里每层只能调用紧挨着的下一层,第 3 层要经过第 2 层。这也是分层结构效率低的原因。 - “Linux 支持动态加载驱动模块,所以它是微内核”对吗?
答案
不对。可加载模块装入后在内核态运行,Linux 仍是宏内核;“能动态加载”说明它模块化,与宏 / 微无关。
内核里到底放了什么
内核是计算机上配置的最底层软件,是操作系统最基本、最核心的部分,运行在内核态(第 5 篇)。不管宏内核还是微内核,下面前三项“与硬件关联较紧密”的功能都一定留在内核里:
| 内核功能 | 做什么 | 联系 |
|---|---|---|
| 时钟管理 | 计时,向用户提供系统时间;通过时钟中断管理时间片,实现进程切换;实时系统里按截止时间控制运行 | 分时系统的基础(1.6);第 6 篇时间片轮转 |
| 中断机制 | 现代操作系统靠中断驱动:键盘输入、进程管理和调度、系统调用、设备驱动、文件访问……都依赖中断。内核里只放其中与硬件关系最紧的一小部分——保护和恢复中断现场、把控制权转到相应的处理程序,以缩短中断处理时间 | 第 5 篇 |
| 原语 | 处在操作系统最底层、最接近硬件的小程序;运行具有原子性(一气呵成,不可被打断,常用关中断实现);运行时间短、调用频繁。设备驱动、CPU 切换、进程通信里的部分操作都可以定义为原语 | 第 6 篇进程控制原语、第 7 篇 P/V 原语 |
| 系统控制的数据结构及处理 | 进程管理:进程状态管理、进程调度和分派、创建与撤销 PCB;存储器管理:空间分配和回收、内存信息保护、代码对换;设备管理:缓冲区管理、设备分配和回收 | 这部分在微内核里多半移到了用户态服务进程 |
开机时发生了什么:操作系统引导
为什么要这么多级“接力”?ROM 里的程序断电不丢,但容量小、不方便更新,只能放一个很小的引导程序;操作系统又大又要经常升级,只能放在磁盘上。于是开机后先跑 ROM 里的小程序,由它去磁盘上读一段稍大的引导程序,再由那段程序去装入操作系统。逐步展开如下:
| 步 | 做什么 | 具体过程 |
|---|---|---|
| ① | 激活 CPU | 加电后 CPU 从一个固定地址开始执行,这个地址落在 ROM 里——执行的第一条指令就是 BIOS(ROM 中的引导程序)的第一条指令 |
| ② | 硬件自检 | BIOS 检查内存、显卡、键盘等硬件是否正常,出错就报警停机 |
| ③ | 找到启动盘 | BIOS 按设置好的启动顺序(硬盘、U 盘、光盘……)找到能启动的那块盘,把它的第一个扇区读进内存,把控制权交给它 |
| ④ | 执行主引导记录 MBR | MBR 在磁盘的第一个扇区(0 柱面 0 磁头 1 扇区),包含磁盘引导程序和分区表。它的作用是告诉 CPU 去哪个分区找操作系统 |
| ⑤ | 执行分区引导记录 PBR | 磁盘引导程序扫描分区表,找到活动分区(装了操作系统、被标记为可引导的分区),读入该分区的第一个扇区——分区引导记录 PBR,由它找到分区里的启动管理器 |
| ⑥ | 加载操作系统 | 启动管理器把操作系统内核和初始化程序装入 RAM 并执行,操作系统完成初始化,开始工作 |
2021-46(大题)考的就是这条链:“ROM 中的引导程序 → 磁盘引导程序 → 分区引导程序 → 操作系统的初始化程序”的执行顺序,以及把硬盘做成启动盘的准备顺序“物理格式化(划分扇区)→ 分区 → 逻辑格式化(建立文件系统和根目录)→ 安装操作系统”(第 10 篇 10.3)。
BIOS、CMOS、POST:开机第一秒的三个缩写
问题引入:开机时屏幕上闪过“按 Del 进入设置”,有人叫它“进 BIOS”,有人叫它“进 CMOS”;主板上的纽扣电池没电后,每次开机日期都回到很多年前……这三个缩写各是什么,彼此什么关系?
把开机想成一台洗衣机启动:BIOS 是出厂时烧在芯片里的控制程序;CMOS 是记住你上次选的“水温、转速”的那块小存储器,要靠电池维持;POST 是洗衣机通电后先自检“门关好没有、有没有水”。不像的地方:洗衣机的设置断电就丢,而 CMOS 有纽扣电池,主机拔掉电源设置也还在。
| 名称 | 是什么 | 存在哪、断电后 |
|---|---|---|
| BIOS 基本输入输出系统 | 固化在主板上的一组程序:加电自检、设置程序(Setup)、引导程序、最基本的 I/O 驱动例程。CPU 加电后执行的第一条指令就在 BIOS 里 | ROM(现在多为可改写的闪存);断电不丢 |
| CMOS | 本是一种芯片工艺的名字,这里指主板上一小块用这种工艺做的 RAM,保存 BIOS 用到的设置参数:日期时间、启动顺序、硬件配置 | RAM,靠主板纽扣电池供电;电池没电或“清 CMOS”后恢复默认值 |
| POST 加电自检 | BIOS 开机后做的第一件事:检查 CPU、内存、显卡、键盘等是否正常,出错用蜂鸣声或屏幕提示报警 | 是 BIOS 里的一段程序 |
| BIOS 设置 俗称“进 CMOS” | 开机时按 Del、F2 等键进入的设置界面,用来修改启动顺序等参数 | 程序在 BIOS 里,改的是 CMOS 里的数据 |
真题怎么考:2013-29 问“操作系统最终被加载到哪里”(RAM,不是 BIOS、ROM),2021-46 考引导各步的先后。CMOS、POST 本身还没有单独出题,但“BIOS 存在 CMOS 里”“POST 在读 MBR 之后”这类错误说法很适合做选项。
① BIOS 不在 CMOS 里:BIOS 程序在 ROM / 闪存里,CMOS 存的只是设置数据。② 清 CMOS 不会删掉 BIOS,只是把设置恢复成出厂默认值。③ POST 在读 MBR 之前:硬件都没检查完,不会去读磁盘。
- 主板电池没电后,每次开机日期都变回出厂时的日期,坏掉的是什么?
答案
CMOS RAM 失去供电,保存的日期时间等设置丢失;BIOS 程序本身没坏,电脑仍能开机。 - 把下列步骤排序:读入 MBR、POST、执行 BIOS 第一条指令、按 CMOS 设置确定启动盘。
答案
执行 BIOS 第一条指令 → POST → 按 CMOS 设置确定启动盘 → 读入 MBR。
UEFI + GPT:今天的电脑怎么开机 拓展
问题引入:上面的“BIOS + MBR”是 408 的考法(2021-46)。可你今天买的电脑,设置界面多半写着“UEFI”,硬盘分区表是“GPT”。为什么要换?两套流程哪里一样、哪里不同?
大白话:MBR 是一张只能写 4 行的小卡片:最多记 4 个主分区,而且分区地址只有 32 位,大硬盘记不下;BIOS 也很“笨”,只会执行第一个扇区里那几百字节的小程序。GPT 是一本大目录,UEFI 是一个“聪明”的固件,能直接看懂 FAT32 文件系统,去专门的分区里找启动程序文件来执行。
| BIOS + MBR | UEFI + GPT | |
|---|---|---|
| 分区表 | MBR 中的 64 字节,4 个分区项 → 最多 4 个主分区(更多要靠扩展分区) | GPT,通常预留 128 个分区项 |
| 单盘容量上限 | 扇区号 32 位:232 × 512B = 2TiB(扇区为 4KB 时是 16TiB) | 扇区号 64 位,理论上 264 × 512B = 8ZiB,实际用不完 |
| 引导代码放在 | MBR 的 446 字节 + 活动分区的 PBR | EFI 系统分区里的 .efi 文件 |
| 可靠性 | 分区表只有一份 | 磁盘开头和末尾各存一份 GPT,带 CRC 校验 |
| 安全 | 不检查引导程序是否被篡改 | 支持“安全启动”:只执行有合法签名的启动程序 |
GPT 磁盘的 0 号扇区仍保留一个“保护性 MBR”,让只认识 MBR 的老工具把整块盘看成一个已占用的分区,不会误当成空盘去格式化。上面的数字已用 verify/08/p01_calc.py 验算。考试按题目给的口径答:题目说 BIOS、MBR、PBR,就按传统流程作答。
- 扇区为 512B 时,MBR 分区方式最多能管理多大的磁盘?为什么是这个数?
答案
MBR 的分区项用 32 位记录扇区号,最多 232 个扇区,232 × 29 B = 241 B = 2TiB。 - UEFI 启动时还需要执行 MBR 里的引导代码吗?
答案
不需要。UEFI 固件读 GPT 找到 EFI 系统分区,直接执行其中的 .efi 启动程序文件。
虚拟机
把一台物理机器变成多台“看起来完整”的机器,每台都能装自己的操作系统——像一栋楼隔成好几套房子,每套都有自己的厨房卫生间。负责“隔房子”的软件叫虚拟机管理程序(VMM,又叫 Hypervisor),它有两种装法:
要记住的几点:① 一台主机可以同时跑多个虚拟机,每个虚拟机里都能装操作系统;② “VMM 与操作系统特权级相同”是错的(2025-24):第一类 VMM 的特权级比客户操作系统高;③ 虚拟机技术还可以用软件模拟另一种指令集(在 x86 电脑上模拟 ARM),即一台主机模拟多种 ISA;④ 第一类的性能一般比第二类好(少隔一层宿主操作系统)。
| 第一类 VMM(裸机型) | 第二类 VMM(寄居型,也叫宿主型) | |
|---|---|---|
| 装在哪 | 直接装在硬件上,本身就像一个精简的操作系统 | 装在宿主操作系统上,像一个普通应用程序 |
| 物理资源 | VMM 直接控制全部硬件,直接分给各虚拟机 | VMM 向宿主 OS 申请,由宿主 OS 分配,再转给虚拟机 |
| 特权级 | VMM 运行在最高特权级(真正的内核态);客户 OS 实际运行在用户态(“虚拟内核态”),执行特权指令时陷入 VMM,由 VMM 代为完成 | VMM 主体作为宿主机上的进程在用户态运行,借助宿主内核里的驱动 / 模块完成底层操作 |
| 性能 | 高(少了宿主 OS 这一层) | 较低(请求要经过宿主 OS 转发) |
| 能支持的虚拟机数 | 多 | 较少,受宿主机限制 |
| 典型产品 | VMware ESXi、Xen、Hyper-V | VirtualBox、VMware Workstation |
记一句:第一类“自己当房东”,第二类“租宿主 OS 的房子再转租”。2025-24 的陷阱“VMM 与操作系统特权级相同”就是在考第一类 VMM 的特权级比客户 OS 高。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 可靠性差
- B. 扩展性差
- C. 需要在用户态与内核态之间频繁切换,性能较差
- D. 内核代码量大
- A. 操作系统在开机时直接从 ROM 中执行
- B. 先执行 ROM 中的引导程序,由它把磁盘上的引导程序读入内存
- C. 分区引导程序存放在主引导记录(MBR)中
- D. 操作系统最终被加载到磁盘缓存中运行
- A. 加电自检(POST)由 BIOS 执行,发生在读入主引导记录(MBR)之前
- B. BIOS 程序存放在由纽扣电池供电的 CMOS RAM 中
- C. 清除 CMOS 设置会删除 BIOS 程序,使计算机无法启动
- D. 操作系统的初始化程序存放在主引导记录(MBR)中
展开解析
A加电后 CPU 先执行 BIOS,BIOS 做的第一件事就是 POST;硬件检查通过后,才按 CMOS 里的启动顺序找到启动盘、读入 MBR。B 错:BIOS 是程序,在 ROM / 闪存里;CMOS 存的是设置数据。C 错:清 CMOS 只是把设置恢复为默认值。D 错:MBR 中是磁盘引导程序和分区表,操作系统的初始化程序在活动分区里,要经 PBR、启动管理器一级级接力才装入内存(2021-46)。
考点:BIOS、CMOS、POST、操作系统引导。
1.8性能指标:怎样算“一台机器有多快”
最核心的公式只有一个,所有性能题都从它变形:
你抄一本书:指令条数 = 要抄多少个字;CPI = 每个字平均写几笔;时钟周期 = 每一笔花多少秒。总时间 = 字数 × 每字笔数 × 每笔秒数。想快可以:少写字(编译优化)、每个字少几笔(改进指令/结构)、每笔写快点(提高主频)。但它们互相牵制:主频提高可能让 CPI 变大,所以只看主频判断快慢是错的。
① 不同指令 CPI 不同(加法 4 拍、访存 5 拍),所以要按比例求平均。② 程序运行时的 CPI 还受 Cache 缺失影响:缺失一次就要多等几十上百个周期去主存取数,平均 CPI 随之变大(2025-18 说“与 Cache 缺失率无关”是错的)。③ 两种极端 CPU(第 4 篇):单周期 CPU 每条指令 1 个周期(CPI = 1),但时钟周期要按最慢的那条指令定,很长;流水线 CPU 的时钟周期按最慢的那一段定。
性能指标逐个过(选择题的概念题都在这张表里)
| 指标 | 含义 | 要注意 |
|---|---|---|
| 机器字长 | 计算机进行一次整数(定点)运算所能处理的二进制位数 | = ALU 宽度 = 通用寄存器宽度(2020-12);字长越长,数的表示范围越大、精度越高 |
| 数据通路带宽 | 数据总线一次所能并行传送信息的位数 | 指外部数据总线的宽度,可以和 CPU 内部的机器字长不同 |
| 主存容量 | 主存能存放的最大信息量,写成“字数 × 字长”(如 512K × 16 位)或字节数 | MAR 位数 → 单元个数,MDR 位数 → 存储字长。如 MAR 16 位、MDR 32 位 → 64K × 32 位 |
| 吞吐量 | 系统在单位时间内处理请求(任务)的数量 | 它取决于信息多快能进出主存、CPU 多快能取到指令和数据……几乎每一步都和主存有关,所以教材的结论是:系统吞吐量主要取决于主存的存取周期 |
| 响应时间 | 从用户发出一个请求,到系统作出响应并得到结果所等待的时间 | = CPU 时间 + 等待时间(磁盘访问、存储器访问、I/O 操作、操作系统开销、排队等) |
| 主频 / 时钟周期 | 主频 = 每秒多少个时钟周期;时钟周期 = 1 / 主频,是 CPU 中最小的时间单位 | 1GHz ↔ 1ns,500MHz ↔ 2ns |
| CPI | 执行一条指令平均需要多少个时钟周期 | 与指令种类、具体程序、Cache 缺失率都有关(上面的易错框) |
| IPS / MIPS | 每秒执行多少条 / 多少百万条指令,IPS = 主频 ÷ CPI | ISA 不同的机器不能用 MIPS 比快慢(下面的例子) |
| MFLOPS、GFLOPS、TFLOPS、PFLOPS、EFLOPS、ZFLOPS | 每秒浮点运算次数:106、109、1012、1015、1018、1021 | 衡量浮点运算(科学计算、向量机)性能(2011-12、2021-12) |
| CPU 执行时间 | 运行一个程序所花的 CPU 时间 = CPU 时钟周期数 × 时钟周期 | 分用户 CPU 时间(执行程序本身的指令)和系统 CPU 时间(为这个程序执行的操作系统代码);题目说“CPU 执行时间”“用户 CPU 时间”时一般只算前者,不含等 I/O 的时间 |
| 基准程序 | 专门用来测试性能的一组程序(如 SPEC) | 在同一组程序上比执行时间,比 MIPS 更能反映实际性能 |
性能 = 1 ÷ 执行时间。“M1 的性能(速度)是 M2 的 n 倍” ⇔ TM2 ÷ TM1 = n。
“速度提高 50%” = 速度变成 1.5 倍 = 时间变成原来的 1/1.5(2012-12),不是“时间减少 50%”;“时间减少 50%” = 速度变成 2 倍。
加速比 = 改进前的时间 ÷ 改进后的时间 = 改进后的性能 ÷ 改进前的性能。
MIPS 和 MFLOPS:一个数“指令”,一个数“浮点运算”
问题引入:2011-12 问“描述浮点数操作速度的指标”,2021-12 用 PFLOPS 考超级计算机。MIPS 也是“速度”,为什么超级计算机排行榜用 FLOPS 而不用 MIPS?
评比两家搬家公司:MIPS 像数“跑了几趟”,MFLOPS 像数“搬了多少箱”。一辆大卡车一趟能搬 4 箱,趟数少了,箱数一点没少。不像的地方:搬家的箱子大小差不多;而浮点运算里加法和除法的代价差很多,所以 MFLOPS 也不是完美的尺子。
| MIPS | MFLOPS | |
|---|---|---|
| 数什么 | 每秒执行多少百万条指令 | 每秒完成多少百万次浮点运算 |
| 与 ISA 的关系 | 大:ISA 不同,同一程序的指令条数不同,MIPS 不能直接比 | 小:同一个计算问题要做的浮点运算次数基本固定 |
| 适合衡量 | 同一 ISA 的机器之间比较 | 科学计算、图形、AI 等浮点密集的程序(超级计算机、GPU) |
| 不适合 | 跨 ISA 比较(下面“MIPS 为什么会骗人”的例子) | 几乎不做浮点运算的程序(编译、数据库),这时 MFLOPS 很低也不说明机器慢 |
展开解析
- ① 时间:T = 4×109 × 2 ÷ (2×109) = 4 s。MIPS = 2000 ÷ 2 = 1000。浮点运算次数 = 0.25 × 4×109 = 109 次 → MFLOPS = 109 ÷ 4 ÷ 106 = 250。
- ② 指令数:浮点指令 109 条 → 2.5×108 条;其余 3×109 条不变;共 3.25×109 条。
- ② 时间:T = 3.25×109 × 2 ÷ (2×109) = 3.25 s;MIPS = 2000 ÷ 2 = 1000(没变);浮点运算还是 109 次 → MFLOPS = 109 ÷ 3.25 ÷ 106 ≈ 307.7。
- 结论:程序真的快了(加速比 4 ÷ 3.25 ≈ 1.23),MIPS 却纹丝不动——因为 CPI 和主频都没变;MFLOPS 跟着真实性能一起提高了。数字已用
verify/08/p01_calc.py验算。
峰值 FLOPS 拓展:厂商宣传的“××TFLOPS”一般是理论峰值 = 处理器核数 × 主频 × 每核每周期最多完成的浮点运算次数。例:8 核、3GHz、每核每周期 16 次 → 8 × 3×109 × 16 = 3.84×1011 = 384 GFLOPS。真实程序要等内存、要做分支判断,通常达不到峰值。
真题怎么考:2011-12 考概念(浮点运算速度用 MFLOPS;CPI、IPC、MIPS 都不是);2021-12 考单位(P = 1015,换成中文是“千万亿”,再乘 93 得“亿亿”量级)。失效的情况:MFLOPS 也不能跨运算种类公平比较(一次除法比一次加法慢得多);真正公平的比较仍是同一基准程序的执行时间(上表最后一行)。
- 某程序运行 2 s,共完成 6×109 次浮点运算,它的浮点性能是多少 GFLOPS?
答案
6×109 ÷ 2 ÷ 109 = 3 GFLOPS。 - “MIPS 高的机器,MFLOPS 一定也高”对吗?
答案
不对。MIPS 数指令、MFLOPS 数浮点运算;指令很简单(一条做不了多少浮点运算)或程序里浮点指令很少时,MIPS 高而 MFLOPS 低。
MIPS 为什么会“骗人”
MIPS 只数“每秒执行了多少条指令”,不管每条指令干了多少活。不同指令集里,一条指令能做的事差别很大(CISC 一条复杂指令可能顶 RISC 好几条),所以 MIPS 高的机器不一定快:
展开解析
- M1:MIPS = 2000 ÷ 2 = 1000;时间 = 2×109 × 2 ÷ (2×109) = 2 s。
- M2:MIPS = 2000 ÷ 1 = 2000;时间 = 6×109 × 1 ÷ (2×109) = 3 s。
- M2 的 MIPS 是 M1 的两倍,跑同一个程序却慢了 50%。比较性能只能比“同一个程序的执行时间”;MIPS 只在 ISA 相同(指令条数相同)时才有比较意义——所以 2017-12 题干特意说明“具有相同的 ISA”。
只加速一部分,整体快多少:Amdahl 定律 拓展
2012-12 那种“CPU 快了、I/O 没变”的题,本质是一条通用规律:整体的加速受限于没被加速的那部分。设原来的执行时间里,能被改进的部分占比例 f(按时间算),这部分被加速到 s 倍,则:
套 2012-12:f = 90/100 = 0.9,s = 1.5 → 新时间 = 100 × (0.1 + 0.9/1.5) = 100 × 0.7 = 70 s,加速比 = 1/0.7 ≈ 1.43;就算 CPU 无限快,最多也只能加速到 1/0.1 = 10 倍,因为那 10 s 的 I/O 一秒都没少。
展开解析
- 改进前:平均 CPI = 0.4×1 + 0.6×3 = 2.2,时间 = 108 × 2.2 ÷ 109 = 0.22 s。
- 改进后:平均 CPI = 0.4×1 + 0.6×2 = 1.6,时间 = 0.16 s,加速比 = 0.22 ÷ 0.16 = 1.375。
- 用 Amdahl 验算:后一类指令占的时间比例 f = (0.6×3) ÷ 2.2 = 1.8/2.2 ≈ 0.818(不是 60%!),s = 3/2 = 1.5 → S = 1 ÷ (0.182 + 0.818/1.5) = 1 ÷ 0.727 ≈ 1.375 ✓。
如果误把 f 当成指令条数比例 0.6,会算成 1 ÷ (0.4 + 0.4) = 1.25 ✗。
- A. 100
- B. 200
- C. 400
- D. 600
展开解析
C先按比例求平均 CPI,再用 MIPS = 主频 ÷ CPI ÷ 106。
- 平均 CPI = 50%×2 + 20%×3 + 10%×4 + 20%×5 = 1 + 0.6 + 0.4 + 1 = 3。
- 每秒指令数 = 1.2 × 109 ÷ 3 = 4 × 108 条 = 400 MIPS。
- A 错100 MIPS 对应平均 CPI = 1.2G ÷ 100M = 12,比四类指令中最大的 CPI(5)还大,加权平均不可能这样。
- B 错200 MIPS 对应平均 CPI = 6,也超过了最大的 5;加权平均一定落在最小 2 和最大 5 之间。
- C 对平均 CPI = 3,1.2GHz ÷ 3 = 400 MIPS。
- D 错600 MIPS 对应 CPI = 2,即只用了占比最大的 A 类指令的 CPI,没有加权平均。
平均 CPI 一定要按比例加权,不能直接取算术平均或取占比最大的那一类。
- A. 8.4 秒
- B. 11.7 秒
- C. 14 秒
- D. 16.8 秒
展开解析
D同一台机器,时钟周期不变,执行时间 ∝ 指令条数 × CPI。
- 新时间 = 20s × 0.7 × 1.2 = 16.8s。
- A 错8.4s 比“只算指令数减少”的 14s 还短;可 CPI 是增加的(× 1.2),时间只会比 14s 更长。
- B 错11.7s = 20 × 0.7 ÷ 1.2:CPI 增加到 1.2 倍,却当成除以 1.2。
- C 错14s = 20 × 0.7:只考虑了指令数减少,忘了 CPI 增加到 1.2 倍。
- D 对20 × 0.7 × 1.2 = 16.8s。
比例题不必求出具体数值,把变化倍数连乘即可。
- A. 0.4
- B. 0.625
- C. 1.6
- D. 2.5
展开解析
CISA 相同 → 同一程序编译出的指令条数相同,时间只和 CPI ÷ 主频有关。
- M1:每条指令时间 = 2 ÷ 1.5GHz ≈ 1.333ns;M2:1 ÷ 1.2GHz ≈ 0.833ns。
- 时间比 M1 : M2 = 1.333 ÷ 0.833 = 1.6。
- A 错0.4 = (1/2) × (1.2/1.5):CPI 之比用反了,应为 M1 : M2 = 2 : 1。
- B 错0.625 = 1 ÷ 1.6:比值写反了,算成了 M2 : M1。
- C 对(2 ÷ 1.5) ÷ (1 ÷ 1.2) = 1.6,主频高的 M1 反而更慢。
- D 错2.5 = (2/1) × (1.5/1.2):主频之比用反了,主频高的 M1 每个周期更短,应乘 1.2/1.5。
只看主频判断快慢是错的:主频高的 M1 因为 CPI 大,反而更慢。
- A. 55s
- B. 60s
- C. 65s
- D. 70s
展开解析
考点1.8 性能计算
D“CPU 速度提高 50%”是速度变成原来的 1.5 倍,所以 CPU 时间变成原来的 1/1.5,而不是“减少 50%”。
- 原来:CPU 时间 90s,I/O 时间 10s。
- CPU 时间变为 90 ÷ 1.5 = 60s,I/O 时间不变仍为 10s。
- 总时间 = 60 + 10 = 70s。
- A 错55s:把“速度提高 50%”当成“时间减半”(90 ÷ 2 + 10)。
- B 错60s:只算了 CPU 时间,忘了加上不变的 I/O 时间 10s。
- C 错65s = 60 + 5,相当于把 I/O 时间也减了一半;题目说 I/O 速度不变,I/O 仍要 10s。
- D 对70s = 90 ÷ 1.5 + 10。
速度 × 时间 = 工作量不变:速度变 k 倍,时间变 1/k。
- A. 0.8GIPS、0.4ms
- B. 0.8GIPS、0.4μs
- C. 1.25GIPS、0.4ms
- D. 1.25GIPS、0.4μs
展开解析
C指令执行速度 = 主频 ÷ CPI;CPU 时间 = 指令条数 × CPI ÷ 主频。单位分开换算。
- 速度 = 1.5 × 109 ÷ 1.2 = 1.25 × 109 条/秒 = 1.25 GIPS。
- 时钟周期数 = 5 × 105 × 1.2 = 6 × 105。
- CPU 时间 = 6 × 105 ÷ (1.5 × 109) = 4 × 10−4 s。
- 单位换算:10−3 s = 1ms,所以 4 × 10−4 s = 0.4ms。
- A 错0.8 = 1.2 ÷ 1.5,是把 CPI ÷ 主频当成了速度(单位其实是“纳秒/条”);速度应是主频 ÷ CPI = 1.25 GIPS。
- B 错速度 0.8 是 CPI ÷ 主频(1.2 ÷ 1.5),应为主频 ÷ CPI = 1.25 GIPS;时间单位也错,4 × 10−4 s 是 0.4ms,0.4μs 只有 4 × 10−7 s。
- C 对1.25 GIPS、0.4ms。
- D 错速度对了,但 4 × 10−4 s = 0.4 × 10−3 s = 0.4ms;0.4μs = 4 × 10−7 s,差了 1000 倍。
ms = 10−3 s,μs = 10−6 s,ns = 10−9 s。同类:2022-12(CPI = 0.8 × 1 + 0.2 × 10 = 2.8,10000 × 2.8 ÷ 109 = 28μs)。
- A. 仅Ⅰ和Ⅱ
- B. 仅Ⅰ和Ⅲ
- C. 仅Ⅱ和Ⅲ
- D. Ⅰ、Ⅱ和Ⅲ
展开解析
D执行时间 = 指令条数 × CPI × 时钟周期,三个因子任何一个变小都能缩短时间。
- Ⅰ 对提高时钟频率 → 时钟周期变短。
- Ⅱ 对优化数据通路 → 可以减少每条指令的周期数(CPI)或缩短关键路径(时钟周期)。
- Ⅲ 对编译优化 → 生成的指令条数变少(或用更快的指令)。
性能题先写公式“时间 = 条数 × CPI × 周期”,再看选项改的是哪个因子。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 25s
- B. 30s
- C. 35s
- D. 40s
展开解析
B“速度提高 300%”是变为原来的 4 倍,CPU 时间 = 80 ÷ 4 = 20s;“提高 1 倍”是变为 2 倍,I/O 时间 = 20 ÷ 2 = 10s。共 30s。
变化点:2012-12 只提高 CPU 速度(提高 50%,即 90 ÷ 1.5 = 60s)。这里两部分都加速,而且要先把“提高 x%”换成“变为原来的 (1 + x%) 倍”,再用它去除原来的时间。
- A. 600
- B. 800
- C. 1000
- D. 2000
展开解析
B平均 CPI = 0.5 × 1 + 0.3 × 2 + 0.2 × 7 = 2.5,MIPS = 主频 ÷ CPI ÷ 106 = 2000 ÷ 2.5 = 800。
变化点:CPI 要按比例加权平均。若把三类的 CPI 直接平均:(1 + 2 + 7) ÷ 3 ≈ 3.33,就会错选 600。
- A. 5.63s
- B. 7.04s
- C. 8.8s
- D. 11s
- A. 2 万亿次
- B. 20 万亿次
- C. 2 千万亿次
- D. 20 亿亿次
展开解析
BT = 1012,20TFLOPS = 2 × 1013 次/秒。1 万亿 = 1012,所以是 20 万亿次。
变化点:2021-12 是 PFLOPS(1015)。记住 M、G、T、P、E 依次是 106、109、1012、1015、1018;换成中文时,1 万亿 = 1012,1 亿亿 = 1016。
- A. 1000 MFLOPS
- B. 333 MFLOPS
- C. 500 MFLOPS
- D. 1500 MFLOPS
展开解析
B执行时间 T = 3×109 × 1.5 ÷ (1.5×109) = 3 s;浮点运算次数 = 109 次;MFLOPS = 109 ÷ 3 ÷ 106 ≈ 333。A 是 MIPS(所有指令都数了);C 是忘了乘 CPI(把 T 算成 2 s);D 是直接拿主频当答案。
变化点:2011-12 只问“哪个指标描述浮点速度”,这里要真算一次 MFLOPS:分子只数浮点运算,分母是整个程序的执行时间(MIPS 和 MFLOPS)。
- MAR 为 20 位、MDR 为 16 位、按字编址,主存容量是多少字节?
答案
220 个单元 × 16 位 = 1M × 2B = 2MB。 - “并发”和“并行”哪个是 OS 最基本的特征?单核 CPU 上两个进程能并行吗?
答案
并发;单核上两个进程只能并发(轮流),不能并行。但 CPU 和 I/O 设备可以并行。 - M 主频 2GHz,程序 P 运行 10 s。改进后主频提升,但 CPI 变为原来 1.5 倍,要使 P 只跑 6 s,主频至少多少?
答案
原时钟周期数 = 10×2G = 2×1010;新 = 1.5×2×1010 = 3×1010;主频 = 3×1010/6 = 5 GHz(王道例 1.1)。 - 微内核为什么性能比宏内核差?
答案
很多服务在用户态,服务之间、服务与内核之间要靠消息传递,频繁在用户态和内核态之间切换,开销大。 - 硬盘属于主机吗?CPU 能直接执行存在硬盘上的程序吗?
答案
不属于,主机 = CPU + 主存,硬盘是辅存、属于外设。CPU 不能直接执行,程序要先由操作系统装入主存。 - 用教材记号写出单累加器机器上 STORE X 的全部过程。
答案
取指:(PC)→MAR;M(MAR)→MDR;(MDR)→IR;(PC)+1→PC。分析:OP(IR)→CU。执行:Ad(IR)→MAR;(ACC)→MDR;(MDR)→M(MAR)。 - 下列哪些是系统软件:操作系统、DBMS、编译程序、办公软件、学生成绩管理系统?
答案
操作系统、DBMS、编译程序是系统软件;办公软件、学生成绩管理系统是应用软件。 - 某程序 80% 的执行时间在做浮点运算。把浮点部件的速度提高到原来的 4 倍,整体加速比是多少?浮点部件再快,最多能加速多少?
答案
S = 1 ÷ (0.2 + 0.8/4) = 1 ÷ 0.4 = 2.5;上限 1 ÷ 0.2 = 5。 - 单道批处理、多道批处理、分时系统的特点各是哪几个词?
答案
单道:自动性、顺序性、单道性;多道:多道、宏观上并行、微观上串行;分时:同时性(多路性)、交互性、独立性、及时性。
1.✎本篇真题闯关(34 题)
本篇各小节挂的真题全部在这里,按小节排好。可以展开后一道道做,也可以用弹窗模式连续做。做题记录(对/错)只保存在你这台电脑的浏览器里,小节标题下的题号会随之变绿或变红。
数据的表示与运算:补码、类型转换、浮点数
一串 0 和 1 可以是正数、负数、小数、字符。这一篇回答三个问题:负数怎么存(补码)、C 语言里不同类型互相赋值时位串怎么变、小数怎么存(IEEE 754)。它是计组选择题里分值最密的一章,而且每年综合题都会拿 C 程序来考它。
2.1无符号数和补码:用“时钟”理解负数
先认几个名词:真值、机器数、定点数
真值是带“+、−”号的数本身(如 −13、+0.625);机器数是它在机器里的 0/1 编码,连正负号也用 0(正)、1(负)来表示。机器里还要约定小数点在哪:小数点位置固定不变的叫定点数,小数点位置随“阶码”浮动的叫浮点数(2.6 节)。定点数分两种:
无符号数最简单:n 位二进制直接按权重相加,范围 0 ~ 2n−1。8 位是 0~255,16 位是 0~65535。
负数怎么办?最直观的想法是拿最高位当符号(0 正 1 负),后面放绝对值,这叫原码。但原码有两个 0(+0 和 −0),而且加法电路要先比较符号,很麻烦。计算机真正用来存整数的是补码,它的想法来自钟表:
求 −x 的 n 位补码:先写出 x,再“取反加 1”;或者直接算 2n−x。例:16 位 −9 → 216−9 = 65527 = FFF7H;或 9 = 0009H,取反 FFF6H,加 1 → FFF7H。
十六进制补码读真值:最高位 ≥ 8 就是负数,真值 = 该数 − 2n。例:8 位 F2H = 242 − 256 = −14;32 位 FFFFFFDFH = −(100000000H − FFFFFFDFH) = −21H = −33。
特殊的那个数:1000…0 是最小负数 −2n−1,对它取反加 1 还是它自己(−(−128) 在 8 位里溢出)。
四种编码放在一起比一比
补码是主角,另外三种选择题会考“范围、0 有几个、怎么互相转换”。以 8 位的 −5 为例:
| 编码 | 正数 | 负数怎么得 | −5 的 8 位编码 | 0 有几种 | 8 位范围 | 用在哪 |
|---|---|---|---|---|---|---|
| 原码 | 符号位 0 + 数值 | 符号位 1 + 绝对值 | 1000 0101 | 2(+0、−0) | −127 ~ 127 | 浮点数的尾数(“符号 + 绝对值”) |
| 反码 | 绝对值的各位取反 | 1111 1010 | 2 | −127 ~ 127 | 过渡用,基本不单独用 | |
| 补码 | 反码末位加 1(= 2n + x) | 1111 1011 | 1 | −128 ~ 127 | 整数 | |
| 移码 | 真值 + 偏置(8 位常用偏置 128) | 0111 1011 | 1 | −128 ~ 127 | 浮点数的阶码 | |
记三句话:① 正数的原、反、补码都一样;② 偏置为 2n−1 的移码 = 补码把符号位取反(−5:补码 1111 1011 → 移码 0111 1011);③ 移码的好处是比大小可以直接按无符号比,所以浮点数的阶码用它。注意 IEEE 754 的偏置是 127 而不是 128(2.6 节),所以它的阶码不是“标准”移码。
定点小数的编码和四种编码的表示范围
定点小数的编码规则和整数一模一样,只是数值位的权重变成 2−1、2−2……。例:x = −0.1011₂(= −0.6875),用 1 位符号 + 4 位数值:原码 1.1011;反码 1.0100;补码 1.0101(= 2 + x,“绕一圈”的圈长从 2n 变成了 2)。补码的符号位权重是 −1:1.0101 = −1 + 0.0101₂ = −1 + 0.3125 = −0.6875 ✓。
| 编码 | 共 n+1 位(1 位符号 + n 位数值) | 例:8 位(n = 7) | ||
|---|---|---|---|---|
| 定点整数 | 定点小数 | 整数 | 小数 | |
| 原码 | −(2n−1) ~ 2n−1 | −(1−2−n) ~ 1−2−n | −127 ~ 127 | −127/128 ~ 127/128 |
| 反码 | 同原码(都有 +0、−0 两个 0) | −127 ~ 127 | −127/128 ~ 127/128 | |
| 补码 | −2n ~ 2n−1 | −1 ~ 1−2−n | −128 ~ 127 | −1 ~ 127/128 |
| 移码 | −2n ~ 2n−1 | 不用(移码只用来表示整数,即浮点数的阶码) | −128 ~ 127 | — |
原码和反码里 0 有两种写法(+0、−0),浪费了一个编码;补码只有一个 0,省下来的那个编码 1000…0 用来表示最小的那个负数:在定点整数里它是 −2n(8 位就是 −128),在定点小数里它是 −1([−1]补 = 1.0000000)。这两个数只有补码(和移码),没有原码、反码——“−128 的原码是多少”这种问法本身就不成立。
上表里 n 是数值位个数(总位数 n+1)。前面公式“n 位补码范围 −2n−1 ~ 2n−1−1”里的 n 是总位数,两种写法说的是同一件事,做题时先看清题目给的是总位数还是数值位数。
四种编码互相转换的速算法
| 要做什么 | 方法 | 例(8 位) |
|---|---|---|
| 原码 → 补码(负数) | 符号位不变,数值位取反,末位加 1 | −13:1000 1101 → 1111 0010 → 1111 0011 |
| ↑ 的快捷法 | 符号位不变;数值位从右往左找第一个 1,这个 1 和它右边的 0 都不变,它左边的数值位全部取反 | −40:1010 1000 → 最右的 1 在第 3 位,右边 000 不变,左边数值位 010 取反为 101 → 1101 1000 |
| 补码 → 原码(负数) | 完全同上(再做一次“数值位取反、末位加 1”,或用同一个快捷法) | 1111 0011 → 1000 1100 → 1000 1101 |
| [x]补 → [−x]补 | 连同符号位一起取反,末位加 1 | [−13]补 1111 0011 → 0000 1100 → 0000 1101 = [+13]补 |
| 补码 ↔ 移码 | 偏置为 2n−1(8 位偏置 128)时:符号位取反,其余不变 | [−13]补 1111 0011 ↔ [−13]移 0111 0011 |
| 负数的 反码 | = 原码数值位取反 = 补码 − 1 | [−13]反 = 1111 0010 |
求补码(或由补码求原码):符号位不动,只动数值位——这是在同一个数的两种编码之间换。
由 [x]补 求 [−x]补:符号位也要一起取反——这是在求另一个数(相反数)的补码,减法 A − B 就是这样把 −B 做出来的(2.4 节)。
还有一个常用的:由 [x]补 求 [x/2]补 = 算术右移 1 位,求 [2x]补 = 算术左移 1 位(2.3 节,2013-14)。
- A. E002H
- B. E001H
- C. 9FFFH
- D. 9FFEH
展开解析
A两种算法任选:① 216 − 8190 = 65536 − 8190 = 57346 = E002H;② 8190 = 1FFEH,取反得 E001H,再加 1 得 E002H。
- A 对E002H。
- B 错E001H 是反码(取反后忘了加 1)。
- C 错9FFFH 既不是 −8190 的原码(9FFEH),也不是补码。
- D 错9FFEH 是 −8190 的原码(符号位 1 + 绝对值 1FFEH)。
负数补码 = 2n − |x| = 绝对值取反加 1;别和原码(符号位置 1)、反码(只取反)混。
- A. −126
- B. −125
- C. −32
- D. −3
展开解析
B8 位补码的最高位权重是 −128,其余位是 +64 … +1。要最小:最高位必须是 1(先拿到 −128),剩下两个 1 放在权重最小的两位,加回来的正数最小。
- 1000 0011 = −128 + 2 + 1 = −125。
- A 错−126 = 1000 0010,只有 2 个 1。
- B 对1000 0011,恰好 3 个 1。
- C 错−32 = 1110 0000,虽然是 3 个 1,但不是最小的。
- D 错−3 = 1111 1101,有 7 个 1。
补码读值:最高位权重 −2n−1,其他位照常相加。
- A. 若 x、y 和 z 为无符号整数,则 z < x < y
- B. 若 x、y 和 z 为无符号整数,则 x < y < z
- C. 若 x、y 和 z 为带符号整数,则 x < y < z
- D. 若 x、y 和 z 为带符号整数,则 y < x < z
展开解析
D同一串位,当无符号数就直接比十六进制大小;当补码就先看最高位定正负,再算真值。
- 当带符号(补码):FFFDH = −3,FFDFH = −33,7FFCH = 32764 → y < x < z。
- 当无符号:直接比十六进制,7FFCH < FFDFH < FFFDH → z < y < x。
- A 错无符号时 z 最小没错,但 x 与 y 比反了:FFFDH 与 FFDFH 前两位相同,第三位 F > D,所以 x > y,应为 z < y < x。
- B 错无符号时 7FFCH 最高位是 0,是三个里最小的,不可能最大;x < y < z 的顺序完全反了。
- C 错带符号时 x = −3、y = −33,−3 > −33,所以 x > y,不是 x < y。
- D 对带符号:y = −33 < x = −3 < z = 32764。
无符号比位串;补码先看符号位:负数的补码位串越大,真值越大(越接近 0)。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 96
- B. 3
- C. −127
- D. 127
展开解析
A要最大,最高位(符号位)必须是 0,两个 1 放在其余位中权值最大的两位:0110 0000B = 96。
变化点:2015-13 问最小值:符号位放 1,其余的 1 放在最低位。求最大值时正好反过来。
- A. 若为带符号整数,则 x < z < y
- B. 若为带符号整数,则 z < x < y
- C. 若为无符号整数,则 x < y < z
- D. 若为无符号整数,则 z < x < y
展开解析
A带符号:x = 8001H − 10000H = −32767,y = 32767,z = −1,所以 x < z < y。无符号:x = 32769,y = 32767,z = 65535,所以 y < x < z,C、D 都不对。
变化点:同样是“两种读法比大小”。最高位为 1 的数:按无符号读很大,按有符号读是负数,排序会整个翻过来。
- A. FED4H
- B. FED3H
- C. 812CH
- D. 012CH
展开解析
A216 − 300 = 65236 = FED4H。也可以写出 300 = 012CH,取反得 FED3H,再加 1 得 FED4H。
变化点:只换了数。B 是忘了加 1(反码),C 是原码。
2.2C 语言里的类型转换:位串到底变没变
这是真题最爱考的地方(2009、2012、2016、2019、2024、2025 都有选择题,2011-43、2017-43 是大题)。只要记住三条规则:
| 情况 | 位串怎么变 | 例子(short 16 位,int 32 位) |
|---|---|---|
| ① 等长,有符号 ↔ 无符号 | 位串一点不变,只是换个读法 | short −32767 = 8001H → 当 unsigned short 读 = 32769(2016-13) |
| ② 短 → 长(扩展) | 看原来的类型:原来有符号 → 符号扩展(高位补符号位);原来无符号 → 零扩展(高位补 0) | short −9 = FFF7H → int FFFFFFF7H unsigned short 65530 = FFFAH → unsigned int 0000FFFAH(2012-13) |
| ③ 长 → 短(截断) | 直接砍掉高位,只留低位,值可能完全变了 | int 32777 = 00008009H → short 8009H = −32759(2024-12) |
① 短有符号 → 长无符号(如 short → unsigned int):C 规定先按“原来是有符号”做符号扩展,再按无符号读。short −32767 = 8001H → FFFF8001H → 当无符号 = 232 − 32767 = 232 − 215 + 1(2025-12 选 D)。
② 有符号和无符号混在一起运算/比较时,C 会把有符号的那个转成无符号。所以 unsigned n = 0; n - 1 得到的是 FFFFFFFFH = 4294967295,而不是 −1(2017-43 第 1 问的死循环就是这么来的)。
- A. x=0000007FH,y=FFF9H,z=00000076H
- B. x=0000007FH,y=FFF9H,z=FFFF0076H
- C. x=0000007FH,y=FFF7H,z=FFFF0076H
- D. x=0000007FH,y=FFF7H,z=00000076H
展开解析
D先用“数值”排除最快:y 的补码看正负和大小,z = 127 − 9 = 118 是正数;再算机器数验证。
- x = 127 = 0000007FH(四个选项相同)。
- y = −9 的 16 位补码:9 = 0009H,取反 FFF6H,加 1 → FFF7H。验算:FFF7H + 0009H = 1 0000H,低 16 位为 0 ✓。
- z = x + y:y 先符号扩展成 FFFFFFF7H,0000007FH + FFFFFFF7H = 1 00000076H,最高位进位丢掉 → 00000076H = 118 ✓。
- A 错y 写成了 FFF9H。FFF9H 是 −7 的补码(FFF9H + 0007H = 1 0000H),常见错法是“前面补 F、后面照抄 9”,没有做取反加一。
- B 错y 同样错成了 FFF9H(那是 −7);z = FFFF0076H 最高位为 1 是个负数(−65418),而 127 − 9 = 118 是正数。
- C 错y 对了,但 z = FFFF0076H 是负数。错法是把 y 符号扩展出来的高 16 位 FFFF 直接抄进结果,忘了相加时低位来的进位会把高位 FFFF 全变成 0(进位丢掉)。
- D 对y = FFF7H,z = 00000076H = 118,与真值一致。
short 转 int 做符号扩展;补码加法的进位丢掉。选择题先比“正负和大小”,再动手算机器数。
int i = 32777; short si = i; int j = si; 执行上述代码段后,j 的值为( )。- A. −32777
- B. −32759
- C. 32759
- D. 32777
展开解析
B两步转换:int → short 是截断(砍掉高 16 位);short → int 是符号扩展(数值不变)。
- 32777 = 32768 + 9 = 0000 8009H。
- 截断成 short:8009H,最高位是 1,按补码读 = 8009H − 10000H = 32777 − 65536 = −32759。
- si 赋给 int:符号扩展为 FFFF 8009H,值仍是 −32759。
- A 错−32777 是把原值直接加了负号;而 −32777 小于 short 的最小值 −32768,根本存不进 short,j 不可能是它。
- B 对−32759。
- C 错32759 符号弄反了(8009H 最高位是 1,是负数)。
- D 错32777 超出 short 范围(最大 32767),截断后不可能保持原值。
长 → 短:只有原值在短类型范围内才保持不变;短 → 长:按原类型扩展,数值一定不变。
unsigned short usi = 65535; short si = usi; 执行后 si 的值是( )。- A. −1
- B. −32767
- C. −32768
- D. −65535
展开解析
考点2.2 等长转换
Aunsigned short → short 等长,位串不变:65535 = FFFFH,按补码读,16 个 1 就是 −1。
- A 对FFFFH 的补码真值 = 65535 − 65536 = −1。
- B 错−32767 = 8001H。
- C 错−32768 = 8000H,是 16 位补码能表示的最小数。
- D 错−65535 超出 short 的范围(−32768 ~ 32767)。
全 1 的位串:无符号是最大数 2n − 1,有符号是 −1。
C 语言各类型占多少位(408 默认 32 位机器)
| 类型 | char | short | int | long | long long | 指针 | float | double |
|---|---|---|---|---|---|---|---|---|
| 位数 | 8 | 16 | 32 | 32 64 位 Linux 为 64 | 64 | 32 64 位系统为 64 | 32 | 64 |
| 表示 | 补码(有符号)/ 无符号二进制;unsigned 版本位数相同 | 地址 | IEEE 754 | |||||
题目若另有规定(如 2011-43 的“8 位字长计算机”里 int 是 8 位),以题目为准。
整数和浮点数之间怎么转
| 转换 | 会发生什么 | 例 |
|---|---|---|
| int → float | 不会溢出(float 范围大得多),但 float 只有 24 位有效位,|x| 超过 224 时末尾的位要舍入 | 16777217(224+1)→ 16777216.0 |
| int → double | 精确:double 有 53 位有效位,装得下任何 int | |
| float → double | 精确:位数只多不少 | 2010-14 Ⅲ |
| double → float | 可能溢出(超过 float 最大值变成 ±∞),也可能舍入(有效位从 53 降到 24) | (float)1e40 = +∞ |
| float / double → int | 小数部分直接截断(向 0 取整,不是四舍五入);超出 int 范围时结果不确定 | (int)3.7 = 3,(int)−3.7 = −3;2010-14 Ⅱ |
有符号数与无符号数混用:比较结果会“反常”
C 规定:一个运算(包括比较)的两边一个是 int、一个是 unsigned int 时,先把 int 按位串不变转成 unsigned 再算。于是负数一下变成很大的正数:
| 表达式(32 位) | 结果 | 原因 |
|---|---|---|
| -1 < 0 | 真 | 两边都是 int,正常比较 |
| -1 < 0U | 假 | 0U 是 unsigned,−1 被当成 FFFFFFFFH = 4294967295 |
| 2147483647 > -2147483647-1 | 真 | 两边都是 int:最大值 > 最小值 |
| 2147483647U > -2147483647-1 | 假 | 右边的 INT_MIN(80000000H)被当成 2147483648 |
| (unsigned)-1 > -2 | 真 | 4294967295 > 4294967294 |
| unsigned n = 0; n - 1 | 4294967295 | 无符号减法“绕一圈”,所以 for (i = 0; i <= n - 1; i++) 在 n = 0 时死循环(2017-43) |
以上结果都用 gcc 实测过。char、short 参与运算时先被提升成 int(“整型提升”),所以两个 short 相加不会在 16 位上溢出,而是先变成 int 再加。
跟我判:混合类型的表达式到底按什么类型算
问题引入:上面两张表分别讲了“整数 ↔ 浮点”和“有符号 ↔ 无符号”。真题常把它们揉进一个表达式:-1 > 1u 为什么是真?两个 short 比较为什么又不会“反常”?关键是先判断这个运算最终按哪种类型做。
大白话:C 语言做二元运算前,先把两边“拉到同一个类型”,而且总是往“能装下更多东西”的方向拉,像两个人对暗号前先统一用哪种语言。不像的地方:往 unsigned 拉的时候并不保证“装得下”负数——−1 会被硬读成 4294967295,这正是坑所在。
教材 / C 标准的说法(408 默认 32 位,int 与 long 都是 32 位):① 整型提升:char、short(不论有无符号)参加运算前先变成 int;② 寻常算术转换:两边类型不同时,向下图中靠右的类型转换;③ 赋值时转换成左边变量的类型(可能截断、可能舍入)。
跟我判(32 位,结果全部 gcc 实测,程序见 verify/08/p02_mix.c):
| 表达式 | 结果 | 判断过程 |
|---|---|---|
| -1 > 1u | 1(真) | int 遇 unsigned → −1 按无符号读 = 4294967295 > 1 |
| short s=-1; unsigned short us=1; s > us | 0(假) | 两个都先整型提升成 int(−1 和 1),按 int 比较,−1 < 1。“反常”只发生在 int 与 unsigned int 之间 |
| unsigned u=10; int i=-20; u + i > 0 | 1(真) | u + i 按无符号算:10 − 20 绕一圈 = 4294967286 > 0;若赋给 int 再看,(int)(u+i) = −10 |
| float f=16777217; (int)f == 16777217 | 0(假) | 224+1 存进 float 要舍入(24 位有效位),f = 16777216.0,转回 int 是 16777216 |
| 0.1f == 0.1 | 0(假) | float 转成 double 比较;0.1 在两种精度下舍入到的值不同 |
| 3 / 2 * 2.0 | 2 | 从左往右:3/2 两边都是 int,先做整数除法得 1,再 1 × 2.0 = 2.0。对比 3 / 2.0 * 2 = 3 |
真题怎么考:2009-12(short 与 int 相加,y 先符号扩展)、2016-13 / 2019-13(等长换读法)、2017-43(无符号 n − 1 绕圈导致死循环)、2010-14(int、float、double 来回转)。判别法:先找出运算的最终类型,再决定是“位串不变换读法”“扩展”“截断”还是“舍入”。失效的情况:题目换了机器(如 2011-43 的 8 位机里 int 只有 8 位),类型位数要按题目改。
- 32 位机上,
unsigned char a = 200, b = 100;则a + b的值和类型是什么?答案
两者先整型提升为 int:200 + 100 = 300,类型 int,不会在 8 位上溢出变成 44。只有把结果再赋给 unsigned char 时才截断成 44。 int i = -1; unsigned int u = 0;判断i < u。答案
假。i 转成 unsigned = 4294967295,不小于 0。
大题精讲:2011-43(8 位机上的有符号/无符号运算)
unsigned int x = 134; unsigned int y = 246; int m = x; int n = y; unsigned int z1 = x - y; unsigned int z2 = x + y; int k1 = m - n; int k2 = m + n;
展开解析
- 写机器数。134 = 128+6 = 1000 0110 = 86H → R1 = 86H。246 = 256−10 = 1111 0110 = F6H。
- z1 = x − y:减法 = 加上 y 的补数:86H + (100H − F6H) = 86H + 0AH = 90H → R5 = 90H(无符号意义下是 134−246+256 = 144,发生了借位)。
z2 = x + y = 86H + F6H = 17CH,8 位只留 7CH → R6 = 7CH(最高位进位丢了)。 - m 的位串就是 86H,当有符号读:86H − 100H = 134 − 256 = −122。n = F6H = −10。k1 = m − n 的位串和 z1 一样是 90H,当有符号读 = 144 − 256 = −112(验算:−122 − (−10) = −112 ✓)。
- (3)能。n 位加法器做的就是“模 2n 的加法”(图 2-2 顺时针走格子)。无符号和补码的加法都是模 2n 相加,减法都是“加上减数的补数”(取反加 1),所以一个加法器 + 取反电路 + 进位输入就够了,区别只在于怎么解读结果和标志。
- (4)溢出判断:两个同号数相加,结果符号却与它们不同 → 溢出;或者:最高位的进位 ≠ 次高位的进位 → 溢出。
k1 = −112 在 −128~127 内,不溢出;k2 = m + n = −122 + (−10) = −132 < −128,溢出:1000 0110 + 1111 0110 = (1)0111 1100,两个负数加出了正数 7CH。
答案:R1 = 86H,R5 = 90H,R6 = 7CH;m = −122,k1 = −112;能;只有 k2 = m + n 溢出。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
short si = -2; unsigned int ui = si;,则 ui 的真值为( )。- A. 216 − 2
- B. 231 − 2
- C. 232 − 2
- D. 2
展开解析
Csi 是有符号数,短变长做符号扩展:FFFEH → FFFF FFFEH。再按无符号数读:232 − 2。
变化点:2025-12 是 −32767(答案 232 − 215 + 1)。规律:负数 −k 先符号扩展成 32 位,再按无符号读,就是 232 − k。若误做零扩展,就会得到 A。
int i = 98305; short si = i; int j = si; 执行后,j 的值为( )。- A. 98305
- B. 32769
- C. −32767
- D. −32769
展开解析
C98305 = 1 8001H。赋给 short 时截去高位,得 8001H,最高位为 1,是负数:8001H − 10000H = −32767。再赋给 int 做符号扩展,值不变。
变化点:2024-12 是 32777(= 8009H,答案 −32759)。这里的数超过了 216,截断时连第 17 位也砍掉了。方法一样:只看低 16 位,最高位为 1 就减 65536。
unsigned short x = 65530; int y = x; short z = x; 后,y 和 z 的值分别为( )。- A. y = 65530,z = −6
- B. y = −6,z = −6
- C. y = 65530,z = 65530
- D. y = −6,z = 65530
short a = -1; unsigned short b = 1; int x = (a > b); unsigned int c = 1; int y = (-1 > c); 则 x、y 的值分别为( )。- A. 1、1
- B. 0、0
- C. 1、0
- D. 0、1
展开解析
Da、b 都是 16 位,参加比较前先整型提升成 int:−1 > 1 为假,x = 0。−1 与 unsigned int 比较时,−1 按位串不变转成无符号数 4294967295,大于 1,y = 1(gcc 实测)。
变化点:同样是“有符号与无符号比较”,只有 int 遇到 unsigned int 才会“反常”;short、unsigned short 会先变成 int 再比(跟我判:混合类型的表达式)。
2.3移位:逻辑移位与算术移位
逻辑移位(给无符号数用)
左移、右移,空出来的位都补 0。1101 1000 逻辑右移 1 → 0110 1100
算术移位(给补码用)
左移低位补 0;右移高位补符号位(保持正负不变)。1101 1000 算术右移 1 → 1110 1100
- 左移 1 位 = ×2,右移 1 位 = ÷2。算术右移对负数是向下取整:−513 算术右移 1 位 = −257(不是 −256)。16 位 −513 = FDFFH → FEFFH(2012-44 第 1 问)。
- 算术左移时,如果移出去的位和新的符号位不同,就溢出了(值超出范围)。
- 编译器常把“乘以常数”优化成移位和加法:x*2 → 左移 1 位;a[i] 的地址 = 首地址 + i×4 → 用
sll R4, R2, 2(左移 2 位)算 i×4(2014-44、2024-44)。
- A. 1101 0000B
- B. 1101 0011B
- C. 0101 0000B
- D. 0101 0011B
展开解析
A补码算术右移:高位补符号位;算术左移:低位补 0。
- 算术右移 3 位:符号位是 1,高位补 3 个 1,低 3 位 110 被移出 → 1111 0100。
- 算术左移 2 位:低位补 0,高 2 位 11 被移出 → 1101 0000。
- A 对1010 0110 → 1111 0100 → 1101 0000。
- B 错低 2 位是 11:算术左移时把“补符号位”的规则也用到了低位上。补符号位只用于右移的高位,左移低位一律补 0。
- C 错右移时高位补了 0(做成了逻辑右移):1010 0110 → 0001 0100,再左移得 0101 0000。补码算术右移必须补符号位 1。
- D 错两处都错:右移高位补 0(逻辑右移),左移低位又补了 1。
算术右移补符号位、逻辑右移补 0;左移(算术、逻辑)低位都补 0。同类:2018-16(1101 1000 逻辑右移 0110 1100、算术右移 1110 1100,选 B);2013-14(x = 1111 0100,y = 1011 0000,2x + y/2 = 1110 1000 + 1101 1000 = 1100 0000,选 A)。
原码、反码、补码各自怎么算术移位
上面讲的“算术移位”是对补码而言的。教材还要求知道原码、反码的算术移位:规则是符号位不动,只移数值位,空出来的位补什么,取决于“补什么才能让值恰好 ×2 或 ÷2”:
| 码制 | 正数(左移、右移) | 负数左移,低位补 | 负数右移,高位补 | 为什么 |
|---|---|---|---|---|
| 原码 | 都补 0 | 0 | 0 | 负数原码的数值位就是绝对值,和正数一样补 0 |
| 反码 | 1 | 1 | 负数反码的数值位是绝对值取反,原来该补 0 的地方取反后变 1 | |
| 补码 | 0 | 1 | 补码从右往左第一个 1 的右边和原码相同(低位补 0),左边和反码相同(高位补 1) |
| −26(8 位) | 机器数 | 左移 1 位(−52) | 右移 1 位(−13) |
|---|---|---|---|
| 原码 | 1 001 1010 | 1 011 0100 | 1 000 1101 |
| 反码 | 1 110 0101 | 1 100 1011 | 1 111 0010 |
| 补码 | 1 110 0110 | 1 100 1100 | 1 111 0011 |
红色是新补进来的位。三种码移位后分别是 −52、−13 的原码、反码、补码(已验算)。什么时候出错:左移时如果移出去的数值位不是“本该补进来的那个值”(正数移出 1;原码负数移出 1;反码、补码负数移出 0),结果就超出范围了(溢出);右移时移出 1 只会丢精度(相当于舍入)。
算术右移对负数是向下取整(往 −∞ 方向),C 语言的整数除法 / 是向 0 取整:−7 >> 1 = −4,而 −7 / 2 = −3(gcc 实测)。正数两者一样。另外 C 语言里 >> 作用于有符号数时(常见编译器)是算术右移,作用于 unsigned 时是逻辑右移:-8 >> 1 = -4,(unsigned)-8 >> 1 = 2147483644。
循环移位:不带进位(小循环)与带进位(大循环)
问题引入:逻辑移位、算术移位都会把移出去的那一位“扔掉”。可如果想把一个 16 位数的高低字节对调,或者在 8 位机上把一个 16 位数整体左移一位,扔掉的那一位就必须“找回来”——这就要用循环移位。
把 8 个位排成一圈转一格。不带进位(小循环)像 8 个人围着一张桌子换座位,从一头挤出去的人从另一头坐回来,顺便在门口登记一下(抄进进位标志 CF);带进位(大循环)是把门口登记的那个人(CF)也拉进圈里,9 个人一起转。不像的地方:位永远不会丢,小循环转 8 次、大循环转 9 次就回到原样。
| 名称 | 移出去的位去哪 | 空出来的位补什么 | 典型用途 |
|---|---|---|---|
| 循环左移(不带进位,ROL) | 最高位移到最低位,同时送进 CF | 原来的最高位 | 交换高低字节、逐位检查 |
| 循环右移(不带进位,ROR) | 最低位移到最高位,同时送进 CF | 原来的最低位 | 同上 |
| 带进位循环左移(RCL) | 最高位送进 CF | 原来的 CF | 多字长数的移位 |
| 带进位循环右移(RCR) | 最低位送进 CF | 原来的 CF | 多字长数的移位 |
跟我做一遍(已用 verify/08/p02_calc.py 验算):
- 交换高低字节:16 位 1234H 不带进位循环左移 8 位 → 3412H。高字节 12H 整个转到了低位——2.7 节大小端互换时,硬件或库函数就是这样做的。
- 多字长移位:8 位机上把 16 位数 0001 0010 | 1011 0100 左移 1 位。先对低字节逻辑左移:1011 0100 → 0110 1000,移出的最高位 1 进入 CF;再对高字节做带进位循环左移:0001 0010 → 0010 0101(CF 的 1 补进最低位)。拼起来 0010 0101 0110 1000,和直接 16 位左移的结果一样。
真题怎么考:408 选择题考的多是逻辑移位和算术移位(2013-14、2018-16、2026-13),循环移位一般作为“移位类型”的概念出现。判别只看两件事:移出去的位去哪、空出来的位补什么。失效的情况:带进位循环移位的结果取决于原来的 CF,题目没给 CF 的值就不能确定最低(高)位。
- 8 位数 0110 1001,CF = 1,带进位循环右移 1 位后,结果和 CF 各是什么?
答案
原 CF = 1 补进最高位,最低位 1 送进 CF:1011 0100,CF = 1。 - 8 位数不带进位循环左移 8 次后是什么?带进位循环左移几次才回到原样?
答案
不带进位转 8 次回到原数;带进位时 CF 也在圈里,共 9 位,要转 9 次才回到原样(数和 CF 都复原)。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 1011 1100
- B. 0011 1100
- C. 1100 0000
- D. 溢出
展开解析
Dx = −8,y = 96。2y = 192 超出 8 位补码范围(−128 ~ 127),溢出。从移位看:0110 0000 左移一位,最高两位 0、1 不同,符号位由 0 变 1,所以溢出。
变化点:2013-14 的 2x + y/2 不溢出(答案 1100 0000)。这里只把 x、y 的位置对调,左移的对象变成了 96,就溢出了。A 是忽略溢出、把 2y 当成 −64 算出的结果。
- A. 0000 1011B
- B. 1110 0101B
- C. 1100 1011B
- D. 0010 1100B
展开解析
A逻辑左移 1 位:0010 1100B(最高位的 1 被移出)。再算术右移 2 位,高位补符号位 0:0000 1011B。
变化点:2026-13 是先算术右移再算术左移。这里第一步换成逻辑左移,符号位被移掉了,第二步补的是新的符号位 0,不是原来的 1。
- A. 0110 0101,0
- B. 1110 0101,0
- C. 0110 0101,1
- D. 1110 0101,1
展开解析
B带进位循环右移时 CF 也在“圈”里:原来的 CF(1)补进最高位,原来的最低位(0)送进 CF。1100 1010 → 1110 0101,CF = 0。A 是把它当成逻辑右移(最高位补 0);C、D 把 CF 弄错了。
考点:循环移位。看清两件事:移出去的位去哪、空出来的位补什么。
2.4加减法电路与四个标志位
CPU 里只有一个加法器,减法 A − B 是这样做的:把 B 按位取反,再让最低位进位输入为 1,于是算的是 A + (~B) + 1 = A + [−B]补。运算完,电路顺手生成四个标志位存进标志寄存器(PSW),后面的条件跳转指令就靠它们判断大小。
不用硬背电路公式,直接用“数值”判断:
CF:把两个数当无符号数,做加法结果 ≥ 2n(装不下)→ CF=1;做减法被减数 < 减数(不够减)→ CF=1。
OF:把两个数当补码,真实结果超出 −2n−1~2n−1−1 → OF=1。
ZF:结果为 0 → 1。SF:结果最高位。
| 比较 A 和 B(先做 A − B) | 无符号数(看 CF、ZF) | 有符号数(看 SF、OF、ZF) |
|---|---|---|
| A = B | ZF = 1 | |
| A < B | CF = 1(借位了) | SF ⊕ OF = 1 |
| A > B | CF = 0 且 ZF = 0 | SF ⊕ OF = 0 且 ZF = 0 |
| A ≤ B | CF = 1 或 ZF = 1 | (SF ⊕ OF) = 1 或 ZF = 1 |
- A. CF=0,OF=0
- B. CF=1,OF=0
- C. CF=0,OF=1
- D. CF=1,OF=1
展开解析
A硬件同时算出 CF 和 OF:CF 按无符号判断是否借位,OF 按有符号判断是否溢出。
- A 对无符号:FFFF FFFFH ≥ FFFF FFF0H,够减 → CF = 0;有符号:(−1) − (−16) = 15,在范围内 → OF = 0。
- B 错CF = 1 错:按无符号比,被减数 FFFF FFFFH ≥ 减数 FFFF FFF0H,够减,不借位,CF = 0。
- C 错OF = 1 错:按有符号算 (−1) − (−16) = 15,远在 int 范围内,不溢出,OF = 0。
- D 错两个标志都判错:无符号够减(CF = 0),有符号结果 15 不溢出(OF = 0)。
判 CF:当无符号,“不够减”才借位;判 OF:当补码,算真值看是否越界。
- A. 0, 0
- B. 0, 1
- C. 1, 0
- D. 1, 1
展开解析
B同一次减法,OF 按有符号结果判(真值是否超范围),CF 按无符号判(被减数是否不够减)。第一句告诉你 CF 是“借位”口径:100 − 200 不够减,CF = 1。
- OF:有符号 10 − (−20) = 30,在 int 范围内 → OF = 0。
- CF:−20 的位串是 FFFF FFECH,当无符号数约 4.29 × 109;10 比它小,不够减,要借位 → CF = 1。
- A 错OF = 0 对;CF 错:不能看有符号结果 30 是正数就说不借位,CF 要把两个数都当无符号比,10 < FFFF FFECH,必须借位。
- B 对OF = 0、CF = 1。
- C 错两个都反了:30 在 int 范围内,OF 应为 0;无符号 10 < FFFF FFECH 不够减,CF 应为 1。
- D 错CF = 1 对;OF 错:异号相减(正 − 负)可能溢出,但这里真值只有 30,远没超出 int 范围。
OF 管有符号、CF 管无符号,同一条指令两个标志各算各的。减法时 CF = 1 表示“无符号不够减”(借位口径)。
- A. CF + OF = 1
- B. SF + ZF = 1
- C. CF + ZF = 1
- D. CF + SF = 1
展开解析
C比较 A 和 B 就是做 A − B。无符号数只看 CF 和 ZF:A > B ⇔ 不借位(CF = 0)且结果不为 0(ZF = 0)⇔ CF + ZF = 0 ⇔ CF + ZF = 1。
- A 错OF 是有符号数的溢出标志,无符号比较不看它。
- B 错SF 是有符号数的符号,无符号比较不看它。
- C 对CF、ZF 都为 0 时转移,正是“无符号大于”。
- D 错含 SF,而且没考虑结果为 0(相等)的情况。
无符号:小于看 CF = 1,等于看 ZF = 1,大于看 CF = 0 且 ZF = 0;有符号:小于看 SF ⊕ OF = 1。
- A. 24,0
- B. 24,1
- C. 46,0
- D. 46,1
展开解析
D运算器做 x − y = [x]补 + [−y]补,结果只留 8 位;OF 看真值是否超出 −128 ~ 127。
- 真值:x = A3H − 100H = −93,y = 75H = 117;x − y = −210 < −128 → OF = 1。
- [−y]补 = 8BH;A3H + 8BH = 12EH,只留 8 位 → 2EH = 46(负数减正数得正数,典型溢出)。
- A 错24 = A3H + 75H 的低 8 位 18H,是把减法做成了加法(忘了先求 [−y]补);x + y = −93 + 117 = 24 确实不溢出,所以 OF = 0 也是按加法判的。
- B 错24 是把减法做成了加法的结果(A3H + 75H → 18H);而且就算按加法,x + y = 24 也不溢出,OF = 1 自相矛盾。正确的减法结果是 46。
- C 错46 对,OF 错:不能看运算器得到的 46 在范围内就说不溢出,要看真值 −210 已超出 −128 ~ 127。
- D 对结果 2EH = 46,OF = 1。
溢出时运算器给出的结果是“错的”,判 OF 必须看真值(或看“负 − 正 = 正”这种符号矛盾)。
展开解析
SF = F15。
加法溢出 = 两负得正 或 两正得负:OF = A15·B15·F15 + A15·B15·F15。
减法 A − B 溢出 = 负减正得正 或 正减负得负:OF = A15·B15·F15 + A15·B15·F15。
补码加减法的公式与溢出判断的三种方法
补码加减法的全部规则就两条公式(都是“模 2n”,最高位的进位直接丢掉):
符号位和数值位一起参加运算,不用单独处理符号——这就是计算机用补码的原因。(原码加减要先比较两数符号:同号相加、异号用大绝对值减小绝对值,结果取大者的符号,电路麻烦得多,所以只作了解。)
溢出:结果超出了 n 位补码的表示范围。只有同号相加(或异号相减)才可能溢出;异号相加的结果绝对值不会变大,永远不溢出。判断方法有三种,考研要求三种都会:
| 方法 | 怎么判 | 逻辑表达式 |
|---|---|---|
| ① 一位符号位:看符号 | 两个加数符号相同,而结果符号与它们不同 → 溢出(正 + 正得负、负 + 负得正)。减法先变成加法再看 | V = AsBsS̄s + ĀsB̄sSs |
| ② 一位符号位:看进位 | 符号位产生的进位 Cs 与最高数值位向符号位的进位 C1 不同 → 溢出(硬件最常用,也就是 OF 的算法) | V = Cs ⊕ C1 |
| ③ 双符号位(变形补码、模 4 补码) | 运算时每个数用两个符号位(正数 00、负数 11),结果的两个符号位:00 正、11 负、01 正溢出、10 负溢出。最高符号位永远代表真正的符号 | V = Ss1 ⊕ Ss2 |
- A. x+y
- B. −x+y
- C. x−y
- D. −x−y
展开解析
C先用数值筛最快:8 位补码范围是 −128 ~ 127,逐个算真实结果。
- A 错x + y = 103 + (−25) = 78。异号相加,结果的绝对值不会变大,永远不溢出。
- B 错−x + y = −103 + (−25) = −128,恰好是 8 位补码能表示的最小值,不溢出——本题的陷阱。
- C 对x − y = 103 + 25 = 128 > 127,溢出。三种方法验证:① 看符号:0110 0111 + 0001 1001 = 1000 0000,正 + 正得“负”;② 看进位:最高数值位向符号位的进位 C1 = 1,符号位向外的进位 Cs = 0,Cs ⊕ C1 = 1;③ 双符号位:00 110 0111 + 00 001 1001 = 01 000 0000,符号位 01 → 正溢出。
- D 错−x − y = −103 + 25 = −78。
补码的负数比正数多一个:8 位能表示 −128,不能表示 +128。选择题判溢出,先算真值最快。
以上三种方法都是针对有符号(补码)运算的,对应 OF。无符号数加法溢出看最高位有没有进位,减法看有没有借位,对应 CF(上面“笨办法”框)。同一次运算,OF 和 CF 各算各的,互不相干(2011-43 第 4 问、2018-19)。
加法器是怎么搭起来的
大纲要求知道“加法器、ALU 的基本结构”。从最小的积木说起:一位全加器(FA)把两个加数的第 i 位 Ai、Bi 和低位送来的进位 Ci 相加,得到本位和 Si 和向高位的进位 Ci+1。n 个全加器连起来就是 n 位加法器。
串行加法器、并行加法器和 ALU
| 结构 | 怎么做 | 速度 |
|---|---|---|
| 串行加法器 | 只有一个全加器,加上移位寄存器和一个进位触发器:每个时钟周期加一位,结果移入寄存器,进位留给下一拍 | n 位要 n 拍,最省器件、最慢 |
| 并行加法器 · 串行进位 | n 个全加器同时工作,但进位从低位一级一级传上去(行波进位,图中上半部分) | 进位链有 n 级,位数越多越慢 |
| 并行加法器 · 并行进位(先行进位) | 用 Gi = AiBi、Pi = Ai ⊕ Bi(或 Ai+Bi)把每一位的进位都直接写成 C0 和各位 G、P 的表达式,所有进位同时算出 | 进位延迟与位数无关,最快;但位数多了公式太长、电路太大 |
| 分组先行进位 拓展 | 折中:每 4 位一组。组内并行、组间串行(单级先行进位,如 74181 芯片);组内并行、组间也并行(多级先行进位,再加 74182 先行进位部件) | 介于两者之间 |
结论记一句:并行加法器的速度主要取决于进位的传递速度;提高速度的关键是改进进位方式。“加法器的进位方式”属于计算机组成,不属于 ISA(2022-18 Ⅳ)。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. x + y
- B. −x + y
- C. x − y
- D. −x − y
展开解析
C逐个算真值:x + y = 72,−x + y = −128,x − y = 128,−x − y = −72。8 位补码的范围是 −128 ~ 127,只有 x − y = 128 超出,溢出。
变化点:2014-13 的 y = −25。这里故意让 −x + y 恰好等于 −128:它在范围内,不溢出。补码的负数比正数多一个,边界要记准。
- A. 0,0
- B. 0,1
- C. 1,0
- D. 1,1
展开解析
C有符号:−2147483647 − 2 = −2147483649 < −231,溢出,OF = 1。无符号:x 的机器数 8000 0001H,当作无符号数是 231 + 1,减 2 够减,不借位,CF = 0。
变化点:2023-16 的答案是 OF = 0、CF = 1,这里恰好反过来。两个标志各看各的:OF 按有符号的真值判断,CF 按无符号的真值判断。
- A. CF = 1
- B. SF = 1
- C. SF ⊕ OF = 1
- D. CF + ZF = 1
展开解析
C做 A − B:没溢出时,结果为负(SF = 1)就是 A < B;溢出时符号是反的,所以要用 SF ⊕ OF = 1 判断。
变化点:2011-17 是无符号数“大于”(CF + ZF = 0)。记住:无符号看 CF、ZF;有符号看 SF ⊕ OF、ZF。A 是无符号的“小于”,D 是无符号的“小于等于”。
2.5乘法和除法:原理和电路结构
大纲对乘除法只要求两件事:基本原理和电路的基本结构。真题怎么考?2020-43 问“没有乘法指令怎么算乘法、控制逻辑干什么、三种实现谁快”,2024-15 问“乘法能不能一个周期完成”,2025-44 直接画了一个除法器问寄存器初值和计数器在哪。所以不用背复杂的算法,把“竖式”搬到电路上就够了。
乘法 = 移位 + 加法(和小学竖式一模一样)
先用十进制回忆:123 × 45,就是“123 × 5”加上“123 × 4 再左移一位(×10)”。二进制更简单,因为乘数的每一位只可能是 0 或 1:这一位是 1,就把被乘数(左移到对应位置)加上;是 0,就什么都不加。
乘法器的基本结构:ALU + 移位 + 寄存器 + 控制逻辑
用图里的电路把 13 × 11 跑一遍(C 是加法产生的进位,右移时一起移进 P 的最高位):
| 第几步 | y₀ | 做什么 | C | P | Y |
|---|---|---|---|---|---|
| 初始 | 0 | 0000 | 1011 | ||
| 1 | 1 | P ← P + X | 0 | 1101 | 1011 |
| C、P、Y 一起右移 | 0 | 0110 | 1101 | ||
| 2 | 1 | P ← P + X | 1 | 0011 | 1101 |
| 右移 | 0 | 1001 | 1110 | ||
| 3 | 0 | 只右移 | 0 | 0100 | 1111 |
| 4 | 1 | P ← P + X | 1 | 0001 | 1111 |
| 右移,结束 | 0 | 1000 | 1111 |
最后 P、Y 拼起来 1000 1111 = 143。乘数 Y 的位被一位一位“挤出去”,腾出的位置正好装积的低位——所以一个寄存器身兼两职。
三种实现方式谁快(2020-43 第 3 问、2024-15)
| 实现方式 | 怎么做 | 速度 |
|---|---|---|
| ① 没有乘法指令 | 编译器把 x*y 翻译成一个循环程序:判断、加法、移位、计数……每一轮都是好几条指令,每条指令都要取指、译码、执行 | 最慢 |
| ② ALU + 移位器实现的乘法指令 | 一条指令,但硬件内部循环 n 次(上面的图),要多个时钟周期 | 中等 |
| ③ 阵列乘法器 | 把竖式里的 n 行“同时”用 n 排加法器加起来,纯组合电路,一个时钟周期出结果 | 最快(硬件最多) |
另外两条常考结论:变量乘以常数,编译器会优化成移位和加减(x*9 = (x<<3) + x,a[i] 的地址 i*4 = i<<2);两个变量相乘也能编译成“移位+加法”的循环(就是 ①),所以 2024-15 的 D 说“无法”是错的。
带符号的数怎么乘:原码一位乘法、补码一位乘法
上面的电路算的是两个无符号数(或绝对值)。带符号数有两种办法:
原码一位乘法
符号和数值分开算:积的符号 = 两数符号异或(同号为正、异号为负);积的数值 = 两个绝对值相乘,用的就是上面“看乘数最低位、加或不加、右移”的方法,循环 n 次。例:x = −1101、y = +1011 → 符号 1 ⊕ 0 = 1,数值 1101 × 1011 = 1000 1111 → 积 = −1000 1111(−143)。
补码一位乘法(Booth 算法)拓展
符号位直接参加运算,不用分开处理。在乘数最低位后面补一个附加位 y−1 = 0,每一步看最低两位 y0y−1:00、11 → 只右移;10 → 部分积 − 被乘数(加 [−x]补)再右移;01 → 部分积 + 被乘数再右移。右移是算术右移(补符号位)。| Booth 例:x = 0101(+5),y = 1101(−3),4 位补码,积 8 位。A = 部分积,Q = 乘数,Q−1 = 附加位 | |||||
|---|---|---|---|---|---|
| 步 | 看 Q0Q−1 | 操作 | A | Q | Q−1 |
| 初始 | 0000 | 1101 | 0 | ||
| 1 | 10 | A − x:0000 + 1011 | 1011 | 1101 | 0 |
| A、Q、Q−1 一起算术右移 | 1101 | 1110 | 1 | ||
| 2 | 01 | A + x:1101 + 0101 | 0010 | 1110 | 1 |
| 算术右移 | 0001 | 0111 | 0 | ||
| 3 | 10 | A − x:0001 + 1011 | 1100 | 0111 | 0 |
| 算术右移 | 1110 | 0011 | 1 | ||
| 4 | 11 | 只算术右移 | 1111 | 0001 | 1 |
积 = A、Q 拼起来 1111 0001 = −15 = 5 × (−3) ✓(已用程序验算)。为什么这样做是对的:乘数里一串连续的 1(如 0111 1000)等于“高一位的权 − 最低那个 1 的权”(1000 0000 − 0000 1000),所以只需在一串 1 的开始处减一次、结束处加一次——“10”正是一串 1 的开始,“01”正是结束。王道教材用的是定点小数写法(部分积用双符号位,共做 n+1 次加法、最后一次不移位),原理相同。
乘法溢出:积要 2n 位,只留低 n 位时怎么判断
| 不溢出的条件(看 2n 位完整乘积) | 例:n = 32,x = 231−1,y = 2,积 = 0000 0000 FFFF FFFEH | |
|---|---|---|
| 无符号 | 高 n 位全为 0 | 高 32 位全 0 → umul 不溢出(232−2 放得进 unsigned) |
| 有符号 | 高 n 位每一位都等于低 n 位的最高位(即高 n+1 位全 0 或全 1) | 低 32 位最高位是 1,高 32 位却全 0 → imul 溢出(232−2 > int 最大值) |
直觉:有符号数的低 n 位最高位是“符号位”,如果高 n 位只是它的符号扩展,说明真值没超过 n 位补码能表示的范围。2019-45 第 5 问、2020-43 第 4 问都这样判。
verify/08/p02_calc.py 验算)。真题怎么考:选择题(2010-13)直接给 8 位补码让你判断哪个乘法溢出——最快的办法是换成真值相乘、看是否超出 −128 ~ 127;大题(2019-45 第 5 问、2020-43 第 4 问)要你说出机器“怎么判断”,就答上面的规则。失效的情况:无符号乘法和有符号乘法的规则不同,同一个 2n 位积(如 008CH)当无符号不溢出、当有符号却溢出,先看清题目是哪种。
- 8 位补码做 (−16) × 8 和 16 × 8,各自的 16 位积是多少?哪个溢出?
答案
(−16) × 8 = −128 → FF80H,低 8 位最高位 1、高 8 位全 1 → 不溢出(−128 正好能表示);16 × 8 = 128 → 0080H,低 8 位最高位 1、高 8 位全 0 → 溢出。
除法 = 移位 + 试减
除法同样照搬竖式:从高位开始,每次把余数左移一位、拉下一位被除数,然后试着减去除数——够减,商上 1、保留差;不够减,商上 0(再把减掉的加回来,或者下一步改做加法补偿)。
除法器(2025-44 的图就是它):余数寄存器 R 和被除数/商寄存器 Q 连成 2n 位。开始时 2n 位被除数放在 R、Q 里(32 位有符号被除数要先符号扩展成 64 位:高 32 位进 R,低 32 位进 Q),除数放 Y。每一步 R、Q 一起左移一位,ALU 算 R − Y 或 R + Y,根据结果的正负在 Q 的最低位上商 0 或 1。循环 n 次后,Q 里是商,R 里是余数。所以 ALU 只要会加法和减法两种运算,计数器在控制逻辑里。
恢复余数法与加减交替法(不恢复余数法)
“试减”试错了怎么办?有两种处理方式:
恢复余数法
每步:左移,减除数。差 ≥ 0 → 商 1;差 < 0 → 商 0,并把除数加回去恢复原来的余数。缺点:减错一次就要多做一次加法,步数不固定,控制复杂。加减交替法(不恢复余数法)
规则:余数为正 → 商 1,下一步左移后减除数;余数为负 → 商 0,下一步左移后加除数。不恢复,下一步直接用“加”来补偿(因为 2(r+Y) − Y = 2r + Y)。最后一步余数若为负,才恢复一次。每步都是一次加或减,节奏固定,计算机实际用的是它。| 加减交替法例:1101(13)÷ 0011(3),n = 4。余数寄存器 A 取 5 位(多一位放符号),Q 放被除数,最后变成商 | ||||||
|---|---|---|---|---|---|---|
| 步 | 上一步余数 | 左移后 A、Q | 操作 | A | 上商 | Q |
| 初始 | 00000 | 1101 | ||||
| 1 | 正 | 00001 101_ | A − 3 | 11110 | 负 → 0 | 1010 |
| 2 | 负 | 11101 010_ | A + 3 | 00000 | 正 → 1 | 0101 |
| 3 | 正 | 00000 101_ | A − 3 | 11101 | 负 → 0 | 1010 |
| 4 | 负 | 11011 010_ | A + 3 | 11110 | 负 → 0 | 0100 |
| 收尾 | 最后余数为负,恢复一次:A + 3 | 00001 | 0100 | |||
商 Q = 0100 = 4,余数 A = 1,13 = 3 × 4 + 1 ✓(已用程序验算,恢复余数法得到同样结果)。带符号的原码除法同乘法:商的符号 = 两数符号异或,数值部分用绝对值相除;补码除法 拓展 规则类似,只是“商 1 还是商 0”要比较余数和除数的符号是否相同。
① 除数为 0(被除数任意)。② 商溢出:n 位补码里只有一种情况——最小负数除以 −1。32 位时 −231 ÷ (−1) = 231,比 int 最大值 231−1 还大 1。对应机器数:被除数 8000 0000H、除数 FFFF FFFFH(2025-44 第 2 问)。
乘法溢出不会触发异常(只是设置标志或直接截断),而除法这两种情况 CPU 会产生除法异常(属于“故障/异常”类,第 5 篇)。
教材里的“商溢出”条件:定点小数除法要求 |被除数| < |除数|,否则商的绝对值 ≥ 1,定点小数放不下;2n 位 ÷ n 位的无符号除法要求被除数的高 n 位 < 除数,否则商超过 n 位。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. a × b
- B. b × c
- C. a × c
- D. b × b
展开解析
Ca = 15,b = −8,c = 16。a × b = −120,b × c = −128,a × c = 240,b × b = 64。只有 240 超出 −128 ~ 127,溢出。
变化点:故意让 b × c 恰好等于 −128:它在 8 位补码范围内,不溢出。判断乘法溢出,先算真值再看范围最稳。
2.6浮点数 IEEE 754:计算机里的“科学计数法”
十进制科学计数法:−1234.5 = −1.2345 × 103。二进制也一样:−8.25 = −1000.01₂ = −1.00001₂ × 23。规格化后小数点前总是 1,所以这个 1 不用存(“隐藏位”),只存小数点后面的部分。
先看教材的一般格式:阶码 + 尾数
在讲 IEEE 754 之前,教材先讲一种“通用”的浮点格式,选择题(2009-13)和概念题都会用到。一个浮点数写成
阶码(含阶符)是定点整数,它的位数决定数的表示范围;尾数(含数符)是定点小数,它的位数决定精度;数符决定整个数的正负。
规格化:让尾数的最高位“有效”
同一个数有很多种写法:0.1×23 = 0.01×24 = 0.001×25……后面几种白白在尾数最前面浪费了几个 0,精度变差。规格化就是规定尾数的最高数值位必须是有效数字,这样表示唯一、精度最高:
| 尾数用什么码 | 规格化的标志 | 正数 | 负数 | 尾数范围 |
|---|---|---|---|---|
| 原码 | 最高数值位为 1 | 0.1××…× | 1.1××…× | 1/2 ≤ |M| < 1 |
| 补码 | 符号位与最高数值位不同 | 0.1××…× | 1.0××…× | 1/2 ≤ M < 1 或 −1 ≤ M < −1/2 |
−1/2 的补码是 1.100…0,符号位和最高数值位相同 → 不是规格化数(要左规成 1.000…0 × 2−1);−1 的补码是 1.000…0 → 是规格化数。
左规:尾数左移 1 位、阶码减 1(可能要做好几次);右规:尾数右移 1 位、阶码加 1(加减运算后尾数溢出时,最多一次)。基数 r = 4 时规格化要求尾数最高 2 位不全为 0,r = 8 时最高 3 位不全为 0 拓展——基数越大,范围越大,但精度越低。
| 例:阶码 8 位(含 1 位阶符,补码),尾数 24 位(含 1 位数符,补码),基数 2,规格化 | |||
|---|---|---|---|
| 尾数 | 阶码 | 真值 | |
| 最大正数 | 0.111…1(23 个 1)= 1 − 2−23 | 0111 1111 = 127 | (1 − 2−23) × 2127 |
| 最小正数(规格化) | 0.100…0 = 2−1 | 1000 0000 = −128 | 2−1 × 2−128 = 2−129 |
| 最大负数(绝对值最小,规格化) | 1.011…1 = −(2−1 + 2−23) | 1000 0000 = −128 | −(2−1 + 2−23) × 2−128 |
| 最小负数(绝对值最大) | 1.000…0 = −1 | 0111 1111 = 127 | −2127 |
最大负数的尾数不能取 1.100…0(−1/2),因为那不是规格化数;往下一格是 1.011…1。
IEEE 754:今天真正在用的格式
IEEE 754 和上面的一般格式相比有三处不同:尾数用原码且规格化形式是 1.M(隐藏最前面的 1,多出一位精度);阶码用移码但偏置是 127(不是 128);阶码全 0、全 1 留作特殊用途。
float(单精度,32 位)。double(双精度,64 位):1 + 11(偏置 1023)+ 52。
编码:十进制 → 机器数(四步走)
- 定符号:正数 S = 0,负数 S = 1,后面只处理绝对值。
- 化成二进制:整数部分除 2 取余,小数部分乘 2 取整(0.25 = 0.01₂,0.75 = 0.11₂,0.1 化不尽,只能近似)。
- 规格化:把小数点移到第一个 1 的后面,写成 1.xxx × 2k;阶码 E = k + 127,写成 8 位二进制。
- 拼接:S | E(8 位)| 小数点后的 xxx(补 0 到 23 位,最前面的 1 不存),32 位每 4 位一组换十六进制。
- A. C1040000H
- B. C2420000H
- C. C1840000H
- D. C1C20000H
展开解析
AIEEE 754 单精度:1 位符号 S、8 位阶码 E(偏置 127)、23 位尾数 M(隐藏了规格化后小数点前的 1)。
- 符号:负数,S = 1。
- 化二进制:8.25 = 8 + 0.25 = 1000.012。
- 规格化:1000.01 = 1.00001 × 23;阶码 E = 3 + 127 = 130 = 1000 0010;尾数 M = 00001 后面补 0 到 23 位。
- 拼起来四位一组:1100 0001 0000 0100 0000 … = C1040000H。
- A 对S = 1、E = 130、M = 000 01 → C1040000H。
- B 错解码:E = 1000 0100 = 132 → 指数 5,尾数 1.100001,值为 −48.5。它是把 8.25 写成 0.100001 × 24(把小数点前的 1 也存进尾数),阶码又按 4 + 128 算出来的,两处都错。
- C 错解码:E = 1000 0011 = 131 → 指数 4,值为 −1.00001 × 24 = −16.5。错在偏置用了 128(3 + 128 = 131),把移码的偏置 128 和 IEEE 754 的 127 混了。
- D 错解码:E = 131、尾数 100001,值为 −1.100001 × 24 = −24.25。它是按老式“0.1xxx”规格化成 0.100001 × 24、阶码 4 + 127,并把小数点后的 100001 全存进尾数——没用 IEEE 754 的“1.xxx + 隐藏位”。
IEEE 754 规格化成 1.xxx,小数点前的 1 不存;阶码偏置单精度 127、双精度 1023。做完把选项“解码回真值”验算,最不容易错。
解码:机器数 → 十进制
- A. −1.5×213
- B. −1.5×212
- C. −0.5×213
- D. −0.5×212
展开解析
A十六进制先展开成二进制,再按 1 | 8 | 23 切开。注意阶码跨过了十六进制的分组。
- C6400000H = 1100 0110 0100 0000 … → S = 1;E = 1000 1100 = 140;M = 100 0000 …。
- 指数 = 140 − 127 = 13;尾数加上隐藏位 1:1.12 = 1.5。
- 值 = −1.5 × 213。
- A 对S = 1、指数 13、尾数 1.5。
- B 错指数 12 是用 140 − 128 算的,把 IEEE 754 的偏置 127 记成了移码的 128。
- C 错尾数 0.5 是只取了存储的 0.12,忘了加上隐藏的 1(规格化数尾数是 1.M)。
- D 错两处都错:偏置用 128 算出指数 12,尾数又漏了隐藏位写成 0.5。
IEEE 754 解码:值 = (−1)S × 1.M × 2E − 127。同类:2025-13(47300000H = 1.375 × 215);2022-14(−0.4375 = −1.112 × 2−2,E = 125 → BEE00000H);2026-14(12.1 就近舍入 → 4141999AH)。
特殊值:阶码全 0 和全 1
| 阶码 E | 尾数 M | 表示 | 真值 |
|---|---|---|---|
| 全 0 | 全 0 | ±0 | 0 |
| 全 0 | 非 0 | 非规格化数(极小的数) | (−1)S × 0.M × 2−126(隐藏位变 0,指数固定 −126) |
| 1~254 | 任意 | 规格化数 | (−1)S × 1.M × 2E−127 |
| 全 1(255) | 全 0 | ±∞ | 7F800000H = +∞ |
| 全 1(255) | 非 0 | NaN(非数,如 0/0) |
| IEEE 754 一览 | float(单精度,32 位) | double(双精度,64 位) |
|---|---|---|
| 符号 / 阶码 / 尾数 | 1 / 8 / 23 | 1 / 11 / 52 |
| 偏置 | 127(7FH) | 1023(3FFH) |
| 规格化数的阶码 E | 1 ~ 254,指数 −126 ~ +127 | 1 ~ 2046,指数 −1022 ~ +1023 |
| 有效位(含隐藏位) | 24 位 | 53 位 |
| 最大正数 | (2 − 2−23) × 2127 ≈ 3.4 × 1038 | (2 − 2−52) × 21023 ≈ 1.8 × 10308 |
| 最小规格化正数 | 2−126 ≈ 1.18 × 10−38 | 2−1022 ≈ 2.2 × 10−308 |
| 最小非规格化正数 | 2−149 ≈ 1.4 × 10−45 | 2−1074 ≈ 4.9 × 10−324 |
非规格化数的指数固定是 −126(不是 0 − 127 = −127),这样最大的非规格化数 0.11…1 × 2−126 和最小的规格化数 1.0 × 2−126 正好首尾相接,0 附近没有“空洞”(叫渐进下溢)。
- A. −2−128
- B. −1.01×2−127
- C. −1.01×2−126
- D. 非数(NaN)
展开解析
A先切开:阶码全 0 且尾数不为 0 → 非规格化数,值 = (−1)S × 0.M × 2−126(隐藏位是 0,指数固定 −126)。
- 8020 0000H = 1 | 0000 0000 | 010 0000 …:S = 1,E = 0,M = 01000…≠ 0 → 非规格化数。
- 值 = −0.012 × 2−126 = −2−2 × 2−126 = −2−128。
- A 对−0.012 × 2−126 = −2−128。
- B 错当成了规格化数:隐藏位按 1 补成 1.01,指数按 0 − 127 = −127 算。阶码全 0 时隐藏位是 0、指数固定为 −126。
- C 错指数 −126 对了,但隐藏位仍按 1 算成 1.01;非规格化数的隐藏位是 0,应为 0.012。
- D 错NaN 要求阶码全 1(E = 255)且尾数不为 0;本题阶码全 0,是非规格化数。
阶码全 0:尾数 0 → ±0,尾数非 0 → 非规格化(0.M × 2−126);阶码全 1:尾数 0 → ±∞,尾数非 0 → NaN。
非规格化数:为什么阶码全 0 时隐藏位变成 0
问题引入:规格化数的尾数前面总有一个看不见的 1,所以最小的正规格化数是 1.0 × 2−126。那么 0 怎么表示?比 2−126 还小的数(科学计算里很常见)只能一律当成 0 吗?
浮点数像一把刻度不均匀的尺子:越靠近 0 刻度越密,但在 0 和第一个刻度之间留着一大段空白。非规格化数就是在这段空白里补上等间距的小刻度。不像的地方:补上的这些数有效位变少了(前面的 0 不算有效位),精度更低,不是“白送”的。
教材定义:阶码 E 全 0、尾数 M ≠ 0 时是非规格化数,值 = (−1)S × 0.M × 2−126;E 全 0、M 全 0 时是 ±0(+0 与 −0 位串不同,比较时相等)。指数取 −126(= 1 − 127)而不是 0 − 127 = −127,是为了让最大的非规格化数 0.11…1 × 2−126 和最小的规格化数 1.00…0 × 2−126 首尾相接,这种设计叫渐进下溢。
跟我做一遍:解码表(先看 E 是全 0、全 1 还是中间值,再决定隐藏位;数值已用 verify/08/p02_calc.py 验算)
| 机器数 | S | E | M | 类型 | 值 |
|---|---|---|---|---|---|
| 0000 0001H | 0 | 0 | 000…001 | 非规格化 | 2−23 × 2−126 = 2−149(最小正数) |
| 0040 0000H | 0 | 0 | 100…000 | 非规格化 | 0.1₂ × 2−126 = 2−127 |
| 007F FFFFH | 0 | 0 | 111…111 | 非规格化 | (1 − 2−23) × 2−126(最大非规格化数) |
| 0080 0000H | 0 | 1 | 000…000 | 规格化 | 1.0 × 2−126(最小规格化正数,2018-14) |
| 8000 0000H | 1 | 0 | 000…000 | −0 | −0(与 +0 比较相等) |
| 7F80 0000H | 0 | 255 | 000…000 | 无穷 | +∞ |
| 7FC0 0000H | 0 | 255 | 100…000 | NaN | 非数(如 0/0、√−1 的结果) |
真题怎么考:2023-14(8020 0000H:E = 0、M ≠ 0,按非规格化算得 −2−128;当成规格化数就会错选 B、C)、2018-14(最小规格化正数 2−126)、2012-14(最大正数,E 只能取到 254)。秒杀法:拿到机器数先看 E 那 8 位——全 0 → 隐藏位 0、指数 −126;全 1 → ∞ 或 NaN;其他 → 隐藏位 1、指数 E − 127。失效的情况:题目用的是教材“一般格式”(如 2009-13 阶码、尾数都用补码)时,没有隐藏位和非规格化数的规定,要按题目给的格式算。
- 8000 0001H 表示的 float 值是多少?
答案
S = 1,E = 0,M = 0…01 → 非规格化:−2−23 × 2−126 = −2−149(绝对值最小的负数)。 - “IEEE 754 中阶码全 0 的数都等于 0”对吗?
答案
不对。只有尾数也全 0 时才是 ±0;尾数不为 0 时是非规格化数,是非常小但不为 0 的数。
精度:为什么 int 转 float 会变
float 只有 24 位有效位。整数只要不超过 224 就能精确存进 float;超过了,末尾的位就要舍入。double 有 53 位有效位,任何 int 都能精确转成 double。
- A. 仅Ⅰ和Ⅱ
- B. 仅Ⅰ和Ⅲ
- C. 仅Ⅱ和Ⅲ
- D. 仅Ⅲ和Ⅳ
展开解析
考点2.6 IEEE 7542.6 大数吃小数2.2 类型转换
B逐个判断“转过去再转回来会不会丢信息”:float 有 24 位有效位,double 有 53 位;转 int 会截掉小数。
- Ⅰ 对785 < 224,float 能精确表示,转回 int 还是 785,为真。
- Ⅱ 错f = 1567.8,转 int 截掉小数变成 1567,再转回 float 是 1567.0 ≠ 1567.8,为假。
- Ⅲ 对float → double 是变宽,精确无损;double 再转回 float 恰好还原原值,为真。
- Ⅳ 错d = 1.5×10100 比 f 大约 1096 倍,对阶时 f 的尾数被右移出 53 位以外,d + f = d,所以 (d + f) − d = 0 ≠ f,为假。
干扰项从哪来:A 是把Ⅱ当成真(忘了转 int 会截掉小数),又漏了Ⅲ;C 是把Ⅱ当成真,又以为Ⅰ中 int 转 float 会丢精度(785 很小,不会);D 是把Ⅳ当成真(以为浮点加减满足结合律),又漏了Ⅰ
int → float 只在 |i| ≥ 224 时才可能丢精度;float → double 永远精确;转 int 截断小数;大数加小数,小数可能被“吃掉”。
- A. x<y 且符号相同
- B. x<y 且符号不同
- C. x>y 且符号相同
- D. x>y 且符号不同
展开解析
A先看符号位,再比绝对值。同号的 IEEE 754 数,去掉符号位后按无符号整数比位串,就是按绝对值比(阶码在高位且用移码)。
- CC90 0000H = 1100 1100 1001 … → S = 1,E = 1001 1001 = 153,指数 26,x = −1.0012 × 226。
- B0C0 0000H = 1011 0000 1100 … → S = 1,E = 0110 0001 = 97,指数 −30,y = −1.12 × 2−30。
- 两个都是负数(符号相同),|x| 远大于 |y|;负数绝对值越大值越小 → x < y。
- A 对x < y 且两数都为负,符号相同。
- B 错“符号不同”错:B0C0 0000H 的最高位是 B = 1011 的第一位 1,y 也是负数。
- C 错只比了绝对值 |x| > |y| 就下结论,忘了两个负数比较时绝对值大的反而小。
- D 错两处都错:y 的符号位是 1,两数同号;而且负数绝对值大的更小,应是 x < y。
比较两个 IEEE 754 数:先比符号;同为正数按位串大小比;同为负数位串大的反而小。
浮点加减:对阶 → 尾数加减 → 规格化 → 舍入 → 判溢出
- 对阶:小阶向大阶看齐(阶码小的那个尾数右移),因为右移只丢最低位,误差小。
- 尾数加减。
- 规格化:结果不是 1.xxx 的形式就调整:尾数溢出(如 10.xx)要右规(尾数右移、阶码 +1,可能阶码上溢);太小(0.0xx)要左规(尾数左移、阶码 −1,可能阶码下溢)。
- 舍入:IEEE 754 默认“就近舍入,一半时取偶数”,舍入进位也可能导致阶码 +1。
- 判溢出:看阶码是否超出范围。尾数溢出不等于结果溢出(右规一下就好)。
verify/08/p02_calc.py 验算。对阶时为什么是“小阶向大阶看齐”?小阶对大阶要把尾数右移,丢掉的是最低位,误差很小;反过来大阶对小阶要把尾数左移,丢掉的是最高位,结果全错。下面用 2009-13 把五步完整走一遍,教材习惯用双符号位补码表示阶码和尾数,溢出一眼能看出来:
- A. 00111 1100010
- B. 00111 0100010
- C. 01000 0010001
- D. 发生溢出
展开解析
D阶码、尾数都是双符号位补码:阶码 5 位 = 2 位符号 + 3 位数值(阶的范围 −8 ~ +7),尾数 7 位 = 2 位符号 + 5 位数值。先写机器数:X 的阶码 00 111、尾数 00.11101(29/32 = 0.11101₂);Y 的阶码 00 101、尾数 00.10100(5/8 = 0.101₂)。
| 步骤 | 阶码 | 尾数 | 说明 |
|---|---|---|---|
| ① 对阶 | 00 111 | Y:00.10100 → 00.00101 | 阶差 7 − 5 = 2,小阶的 Y 向大阶看齐,尾数右移 2 位(移出的是 00,没有损失) |
| ② 尾数相加 | 00 111 | 00.11101 + 00.00101 = 01.00010 | 29/32 + 5/32 = 34/32 ≥ 1,尾数双符号位 01:尾数溢出(不等于结果溢出) |
| ③ 右规 | 00 111 + 1 = 01 000 | 00.10001 | 尾数右移 1 位、阶码加 1;移出的位是 0,不用舍入 |
| ④ 判溢出 | 01 000 | 阶码双符号位 01 → 阶码上溢 → 结果溢出(真实结果 28 × 17/32,阶 8 超过最大阶 7) |
- A 错尾数 11.00010 是个负数,两个正数相加不可能得负数。
- B 错尾数 01.00010 是没有右规的中间结果,双符号位不同,不是合法的规格化数。
- C 错阶码 01 000、尾数 00.10001 正是右规后的样子,但阶码已经上溢,不能当结果——这是忘了第 ④ 步的陷阱。
- D 对发生溢出。
尾数溢出(双符号位 01、10)右规一下就好;结果是否溢出,只看阶码。
舍入:移出去的位怎么处理
| 方法 | 规则 | 用在哪 |
|---|---|---|
| 0 舍 1 入 | 右移时被移掉的最高位是 1,就在尾数末位加 1,是 0 就直接舍去(类似四舍五入)。加 1 后可能又溢出,还要再右规一次 | 教材一般格式 |
| 恒置 1 | 不管移掉的是什么,尾数末位一律置 1 | 教材一般格式(误差大,但简单) |
| 就近舍入(到偶数) | 舍入到最近的可表示值;正好在两者中间时,选末位为 0(偶数)的那个 | IEEE 754 默认 |
| 朝 +∞ / 朝 −∞ / 朝 0 | 分别总是往大的方向、往小的方向、往 0 的方向(截断)取 | IEEE 754 的另外三种模式 |
例:12.1 = 1100.0001 1001 1001…₂ = 1.1000 0011 0011 0011 0011 001 | 1001…₂ × 23,竖线后被舍掉的部分大于一半,就近舍入要进 1,尾数末段 …001 变成 …010,得到 4141 999AH(2026-14)。又如 int 16777217 = 224 + 1 转 float:被舍掉的恰好是“一半”,按“取偶”舍去,得到 16777216.0;而 16777219 会进位成 16777220.0(gcc 实测)。
四种舍入放在同一组数上比一比(保留 4 位小数,竖线右边是要舍去的位;已用程序验算):
| 舍入前 | 0 舍 1 入 | 恒置 1 | 就近舍入(偶) | 截断 |
|---|---|---|---|---|
| 1.0110 | 01 | 1.0110 | 1.0111 | 1.0110 | 1.0110 |
| 1.0110 | 11 | 1.0111 | 1.0111 | 1.0111 | 1.0110 |
| 1.0110 | 10 正好一半 | 1.0111 | 1.0111 | 1.0110 末位已是 0 | 1.0110 |
| 1.0101 | 10 正好一半 | 1.0110 | 1.0101 末位本来就是 1 | 1.0110 进位后末位为 0 | 1.0101 |
怎么记:0 舍 1 入只看被舍去的最高位;恒置 1 根本不看舍去的位,保留部分末位一律变 1;就近舍入把舍去的部分和“一半”比——大于进、小于舍、正好一半就选末位为 0 的那个;截断直接扔掉(对原码表示的数就是“朝 0 舍入”)。前三种的差别只在“正好一半”和“末位情况”上,题目里遇到这两种情况要格外小心。
- A. 仅Ⅱ、Ⅲ
- B. 仅Ⅰ、Ⅱ、Ⅳ
- C. 仅Ⅰ、Ⅲ、Ⅳ
- D. Ⅰ、Ⅱ、Ⅲ、Ⅳ
展开解析
D按浮点加减的五步(对阶 → 尾数加减 → 规格化 → 舍入 → 判溢出)逐句看阶码会不会越界。
- Ⅰ 对对阶是小阶向大阶看齐,大阶本来就是一个合法的阶码,所以不会上溢也不会下溢。
- Ⅱ 对右规时阶码 +1,可能超过最大阶码而上溢;舍入(如 0 舍 1 入)可能让尾数变成 10.00…,又要右规,同样可能上溢。
- Ⅲ 对左规时尾数左移、阶码 −1,阶码可能小于最小值而下溢。
- Ⅳ 对尾数溢出(如变成 01.xx 或 10.xx)可以右规修正,只要右规后阶码不上溢,结果就不溢出。
干扰项从哪来:A 是以为对阶会越界、又把“尾数溢出”等同于“结果溢出”;B 是以为左规只动尾数、不会改阶码;C 是以为舍入只影响尾数最低位、不会引起阶码变化
浮点运算真正的溢出只看阶码:阶码上溢才是溢出,阶码下溢按机器零处理。例:2009-13 中 X = 27 × 29/32,Y = 25 × 5/8,阶码最大 7;X + Y = 27 × 34/32,右规成 28 × 17/32,阶码 8 > 7 → 溢出。
- A. 32 位整数、32 位整数
- B. 单精度浮点数、单精度浮点数
- C. 32 位整数、双精度浮点数
- D. 单精度浮点数、双精度浮点数
展开解析
考点2.6 float、double 的有效位2.1 整数范围
C既要“精度”(整数不能被舍入)又要“速度”(整数运算比浮点快):能用整数就用整数;整数放不下,就选有效位足够的浮点数。
- A 错β 到 ±240,超出 32 位整数范围(±231)。
- B 错α 用 float:220 以内虽然能精确,但浮点运算比整数慢;β 用 float 只有 24 位有效位,240 附近的整数会被舍入。
- C 对α 在 32 位整数范围内,整数运算最快;β 用 double,53 位有效位足以精确表示 240 以内的整数。
- D 错α 用单精度浮点数没必要,速度不如整数。
float 能精确表示的整数到 224,double 到 253;32 位 int 到 231 − 1。
浮点运算的“反直觉”:结合律不成立、大数吃小数
先看一个怪现象。数学里 (a + b) + c 一定等于 a + (b + c)。可在 C 程序里令 float a = 16777216, b = 1, c = 1;(a = 224),(a + b) + c 算出来是 16777216,a + (b + c) 却是 16777218——同样三个数,只是换了加法的先后,结果就不一样了。2010-14 的 Ⅳ“(d + f) − d == f”为假,也是这个原因。
教材结论:浮点数的加法和乘法都满足交换律,但不满足结合律,乘法对加法也不满足分配律。原因是每一步运算后都要舍入,中间结果还可能溢出(如 (1020 × 1020) × 10−20 在 float 里先上溢成 +∞,而 1020 × (1020 × 10−20) = 1020)。对比整数:补码加法即使溢出也是按模 2n 计算,(u1 + u2) + u3 与 u1 + (u2 + u3) 的位串一定相同——整数加法的结合律总成立,浮点加法不一定。
verify/08/p13/a_assoc.c(gcc 实测)得到。跟我做一遍:float 计算 108 + 1,结果是多少?(gcc 实测)
- 写成规格化形式:108 = 101 1111 0101 1110 0001 0000 0000B(27 位)= 1.0111 1101 0111 1000 0100 0000 00B × 226,小数点后的有效部分能放进 23 位尾数,所以 108 本身能精确表示(机器数 4CBE BC20H)。
- 算间距:指数是 26,尾数最低位的权是 226 − 23 = 23 = 8——在 226 ~ 227 这一段,相邻两个 float 相差 8。一般地,间距 = 2指数 − 23(双精度把 23 换成 52)。
- 对阶:1 = 1.0 × 20,阶差 26,1 的尾数要右移 26 位,那个 1 落在小数点后第 26 位——已经在 23 位尾数之外,只能当作舍入时要看的位。
- 尾数相加 + 舍入:要舍去的部分是 1(单位是 20),而半个间距是 4,1 < 4 → 舍去。结果仍是 108,即 108 + 1 == 108。
- 换几个数检验:+4 正好是半个间距,108 的尾数末位是 0(偶),取偶 → 仍是 108;+5 超过一半 → 进到 100000008;+12 落在 100000008 和 100000016 正中间,100000008 的尾数末位是 1 → 取偶到 100000016。
同一个道理还有两个常见现象(都已 gcc 实测):① 用 float 把 1.0 累加 2000 万次,结果停在 16777216 = 224——加到这里以后,每次 +1 都只有半个间距,全被舍掉;② 1000 个 1 和一个 108 相加,先加大数再一个个加 1,得 100000000;先把 1000 个 1 加完再加 108,得 100001000(精确)。所以要求和时,先加绝对值小的更准。
- 2010-14 Ⅳ:d = 1.5 × 10100,f = 1567.8,(d + f) − d:对阶时 f 被右移出尾数,d + f 就是 d,减去 d 得 0 ≠ f → 假。
- 精度类(2024-14、2021-14、2026-14):float 只有 24 位有效位,大于 224 的整数和 0.1 这类无限循环二进制小数都不能精确表示。
- 可能的新问法:给 Ⅰ “x + y == y + x”、Ⅱ “(x + y) + z == x + (y + z)”、Ⅲ “(x + y) − y == x” 问哪些恒为真(不考虑 NaN)——只有 Ⅰ。Ⅱ 用上面的 224、1、1 就能举反例,Ⅲ 取 x = 1、y = 108:(1 + 108) − 108 = 0。
- 秒杀法:判断一个浮点等式“是否恒成立”,先拿一个大数 + 一个 1 试反例。失效条件:题目若限定所有数和中间结果都是绝对值小于 224 的整数,float 加减是精确的,此时等式可能成立——所以要看清题目问的是“一定”还是“可能”。
- float x = 225(= 33554432),x + 3 的结果是多少?x + 2 呢?
答案
225 附近间距 = 225 − 23 = 4,能表示的是 …432、…436、…440。+3 超过半个间距(2)→ 进到 33554436;+2 正好一半,225 的尾数末位为 0 → 取偶,仍是 33554432(gcc 实测)。 - double d = 1016,判断 d + 1 == d 的真假。
答案
真。253 ≈ 9.007 × 1015 ≤ 1016 < 254,指数 53,间距 253 − 52 = 2;+1 正好半个间距,1016 的尾数末位为 0 → 取偶,仍是 1016(gcc 实测 d + 1 == d 为 1)。 - “浮点加法满足交换律,所以 a + b + c 不管怎么加都一样”,错在哪?
答案
交换律只保证两个数相加与顺序无关;三个数时还涉及“先算哪两个”,那是结合律,浮点不满足(图中的 224、1、1)。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 40AC 0000H
- B. 412C 0000H
- C. 4156 0000H
- D. C12C 0000H
展开解析
B10.75 = 1010.11B = 1.01011B × 23。S = 0,E = 3 + 127 = 130 = 1000 0010B,M = 0101 1000…。拼起来是 0 10000010 01011000…0 = 0100 0001 0010 1100 0…B = 412C 0000H。
变化点:真题是负数(−8.25、−0.4375),这里是正数。三个错误选项分别对应三种常见错误:A 是阶码只加了 126,C 是把隐藏的 1 也写进了尾数,D 是符号位写成了 1。
- A. −21
- B. −10.5
- C. −42
- D. −5.25
展开解析
A1100 0001 1010 1000 0…B:S = 1;E = 1000 0011B = 131,指数 = 131 − 127 = 4;M = 0101 0…,值 = −1.0101B × 24 = −10101B = −21。
变化点:真题的选项写成“1.5 × 213”这种形式,这里要求算出十进制数。步骤不变:拆出 S、E、M,指数 = E − 127,尾数前面补上隐藏的 1。
- A. 0.375
- B. 3.125
- C. 0.3
- D. 1024.5
- A. (1 − 2−23) × 2−126
- B. (1 − 2−23) × 2−127
- C. 2−149
- D. (2 − 2−23) × 2−127
- A. 1.0110、1.0110
- B. 1.0101、1.0101
- C. 1.0110、1.0101
- D. 1.0101、1.0110
2.7大端、小端与边界对齐
一个 int 占 4 个字节、4 个地址。问题来了:先放高位字节还是先放低位字节?
看指令里的立即数或偏移量:2019-45 中 call 指令的偏移量应为 FFFFFFD6H,而机器码里写的是 D6 FF FF FF(最低字节 D6 在前)→ 小端。2018-15:int i = −64 的指令机器码末尾 4 字节是 −64 的补码 FFFFFFC0H 按小端放 → C0 FF FF FF。
边界对齐:为了一次访存就能取到一个数据,编译器让每个变量的地址是它自身大小的整数倍(short 放在 2 的倍数地址,int 放在 4 的倍数地址,double 放 8 的倍数),中间不够就空着(填充);结构体的总大小还要是其最大成员大小的整数倍(这样结构体数组的每个元素也对齐)。
为什么要对齐:不对齐会多访存一次
主存和 CPU 之间一次传一个存储字(32 位机器一次 4 字节,而且只能从 4 的倍数地址开始取一整行)。一个 int 如果从 4 的倍数地址开始放,一次就能取到;如果“骑”在两行之间,就要取两次再拼起来:
| 数据 | 按边界对齐的要求(按字节编址) | 地址的特征 |
|---|---|---|
| 字节(char) | 任意地址 | —— |
| 半字(short,2B) | 地址是 2 的倍数 | 最低 1 位为 0 |
| 字(int、float,4B) | 地址是 4 的倍数 | 最低 2 位为 00 |
| 双字(double,8B) | 地址是 8 的倍数 | 最低 3 位为 000 |
| 结构体 | 每个成员按自己的大小对齐;整个结构体的大小是最大成员大小的整数倍(末尾补齐),这样结构体数组的每个元素也对齐 |
成员顺序会影响结构体大小(gcc 实测):struct { char a; int b; char c; } 占 12 字节(a 占 0,1~3 填充,b 占 4~7,c 占 8,9~11 填充凑成 4 的倍数);把两个 char 放在一起 struct { char a; char c; int b; } 只占 8 字节(a、c 占 0、1,2~3 填充,b 占 4~7)。2020-14 的 struct { short x1; int x2; } 也是 8 字节:x1 占 0~1,2~3 填充,x2 从 +4 开始。
struct record { int id; char name[10]; int salary; } employee[200]; 分配存储空间。数组 employee 的起始地址为 0000A0B0H,employee[1].id 的机器数为 12345678H,则 56H 的地址是( )。- A. 0000A0C3H
- B. 0000A0C4H
- C. 0000A0C5H
- D. 0000A0C6H
展开解析
C两步:① 边界对齐算出结构体大小,找到 employee[1].id 的地址;② 小端方式下低位字节放低地址。
- 一个 record:id 占偏移 0 ~ 3;name 占 4 ~ 13;salary 是 int,要从 4 的倍数开始 → 16 ~ 19(14、15 填充);大小 = 20 字节(也是 4 的倍数 ✓)。
- employee[1] 起始 = A0B0H + 20 = A0B0H + 14H = A0C4H,id 就在这里。
- 小端:A0C4H 放 78H,A0C5H 放 56H,A0C6H 放 34H,A0C7H 放 12H。
- A 错A0C3H 是忘了填充、把结构体按 18 字节算:employee[1] 从 A0B0H + 18 = A0C2H 开始,56H 落在 A0C3H。salary 必须 4 字节对齐,要填 2 个字节。
- B 错A0C4H 是 id 的首地址,小端方式下这里放的是最低字节 78H,不是 56H。
- C 对employee[1].id 从 A0C4H 开始,56H 是第 2 个字节,在 A0C5H。
- D 错A0C6H 是大端方式下 56H 的位置(12H、34H、56H、78H 依次放),本题是小端。
结构体大小:每个成员按自身大小对齐,总大小再补成最大成员大小的倍数。小端:低字节在低地址。
- A. F000 FF12H
- B. F000 FF15H
- C. EFFF FF12H
- D. EFFF FF15H
展开解析
D两步:① 基址寻址算出操作数首地址(形式地址是 16 位补码,要先符号扩展成 32 位再加);② 大端方式下,最低有效字节放在地址最大的那个字节。
- FF12H 最高位为 1,是负数,符号扩展成 FFFF FF12H。
- EA = F000 0000H + FFFF FF12H = 1 EFFF FF12H,丢掉进位 → EFFF FF12H(操作数首地址)。
- 1234FF00H 占 EFFF FF12H ~ EFFF FF15H;大端:12H 在最低地址,LSB = 00H 在最高地址 EFFF FF15H。
- A 错两处错:形式地址没做符号扩展(当成正数 0000 FF12H 加),得到的又是首地址——大端方式下首地址放的是最高有效字节 12H。
- B 错+3 找 LSB 的思路对,但形式地址没做符号扩展:FF12H 是负数,F000 0000H 加上它应该变小,不会变成 F000 FF15H。
- C 错有效地址算对了,但 EFFF FF12H 放的是最高有效字节 12H;大端方式下 LSB 在最大地址,还要 +3。
- D 对EA = EFFF FF12H,LSB 在 EFFF FF15H。
补码形式地址要符号扩展;大端“高位在低地址”,LSB 在首地址 + (字节数 − 1);小端 LSB 就在首地址。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
struct { char a; int b; short c; char d; } s; 首地址为 1000H,s.b = 12345678H,则 34H 所在的地址和 sizeof(s) 分别为( )。- A. 1005H、12
- B. 1006H、12
- C. 1006H、11
- D. 1002H、8
展开解析
Ba 在 1000H;b 要 4 字节对齐,从 1004H 开始,小端存放 78、56、34、12,所以 34H 在 1006H。c 在 1008H~1009H,d 在 100AH。已用到 100AH,共 11 字节,结构体的总大小还要是最大成员(4)的整数倍,取 12。
变化点:真题只问一个字节的地址。这里多问了 sizeof:结构体末尾也要补齐到最大成员大小的整数倍,所以是 12,不是 11。
int i = 0; 对应的机器码为 C7 45 FC 00 00 00 00,则 int i = 1000; 对应的机器码是( )。- A. C7 45 FC 00 00 03 E8
- B. C7 45 FC E8 03 00 00
- C. C7 45 FC 03 E8 00 00
- D. C7 45 FC 00 00 E8 03
展开解析
B1000 = 0000 03E8H,小端方式低字节在前:E8 03 00 00。
变化点:2018-15 是负数 −64(FFFF FFC0H → C0 FF FF FF)。正数、负数都一样:先写出 32 位机器数,再从低字节到高字节依次排。
- A. ABH
- B. CDH
- C. EFH
- D. 89H
展开解析
B大端方式高字节在前(低地址放高字节):2000H 放 89H,2001H 放 ABH,2002H 放 CDH,2003H 放 EFH。
变化点:2016-14 是小端。同一个数,大端和小端恰好倒过来:小端时 2002H 放的是 ABH。
2.8BCD 码、字符编码与校验码(了解即可)
BCD 码:用 4 位二进制表示 1 位十进制
有些场合(计算器、财务、数码管显示)希望机器里直接存“十进制的每一位”,于是用 4 位二进制表示一个十进制数字 0~9,叫 BCD 码(二进制编码的十进制数)。4 位有 16 种组合,只用其中 10 种,剩下 6 种是非法码。
| 十进制 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 特点 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 8421 码 | 0000 | 0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 | 1000 | 1001 | 有权码,各位权重 8、4、2、1;1010~1111 非法 |
| 余 3 码 | 0011 | 0100 | 0101 | 0110 | 0111 | 1000 | 1001 | 1010 | 1011 | 1100 | = 8421 码 + 3(0011),无权码 |
| 2421 码 | 0000 | 0001 | 0010 | 0011 | 0100 | 1011 | 1100 | 1101 | 1110 | 1111 | 有权码,权重 2、4、2、1;5 及以上最高位为 1 |
8421 码加法要“加 6 修正”:两个 BCD 数字按二进制相加,如果和 > 9(出现非法码)或者向高位产生了进位,就再加 6(0110),跳过那 6 个非法码。例:8 + 7 → 1000 + 0111 = 1111(> 9,非法)→ 1111 + 0110 = 1 0101,即十位 1、个位 5,得 15 ✓。
字符编码:ASCII 与汉字
- ASCII 码用 7 位表示 128 个字符(存储时占 1 字节,最高位为 0)。要记住三个起点:'0' = 30H(48),'A' = 41H(65),'a' = 61H(97)。数字字符减 30H 就是它的数值;小写字母 = 大写字母 + 20H(第 5 位 0 → 1)。2016-44 里设备传送的就是 7 位 ASCII 字符。
- 汉字 拓展:输入时用输入码(拼音、五笔);机器内部存储、处理用机内码(每个汉字 2 字节,每字节最高位为 1,以区别于 ASCII;机内码 = 国标码 + 8080H);显示、打印时用字形码(点阵或轮廓)。今天更常用 Unicode(UTF-8 中一个汉字一般 3 字节)。
校验码
- 奇偶校验:加 1 位让“1 的个数”为奇数(奇校验)或偶数(偶校验),只能发现奇数个位出错,不能纠错。2016-44 里异步串行通信每个字符 = 1 起始位 + 7 数据位 + 1 奇校验位 + 1 停止位 = 10 位。
- 海明码:能发现并纠正 1 位错。n 位数据需要 k 位校验位,满足 2k ≥ n + k + 1。8 位数据:k=3 时 8 ≥ 12 不成立,k=4 时 16 ≥ 13 成立 → 4 位(2013-15 选 C)。
- CRC 循环冗余校验:网络里用得多(计网课),只要知道能检错、按生成多项式做模 2 除法。
海明码怎么排、怎么纠错(以 4 位数据为例,23 ≥ 4 + 3 + 1,要 3 个校验位):
- 排位置:7 个位从 1 编号到 7,编号是 2 的幂的位置(1、2、4)放校验位 P1、P2、P3,其余位置依次放数据位:H7 H6 H5 H4 H3 H2 H1 = D4 D3 D2 P3 D1 P2 P1。
- 分组:把位置编号写成二进制,编号第 1 位为 1 的(1、3、5、7)归 P1 管,第 2 位为 1 的(2、3、6、7)归 P2 管,第 3 位为 1 的(4、5、6、7)归 P3 管。偶校验:P1 = D1⊕D2⊕D4,P2 = D1⊕D3⊕D4,P3 = D2⊕D3⊕D4。
- 编码:数据 D4D3D2D1 = 1010 → P1 = 0⊕1⊕1 = 0,P2 = 0⊕0⊕1 = 1,P3 = 1⊕0⊕1 = 0 → 海明码 H7~H1 = 1010010。
- 纠错:假设 H5 在传输中出错,收到 1000010。每组重新异或:S1 = H1⊕H3⊕H5⊕H7 = 1,S2 = H2⊕H3⊕H6⊕H7 = 0,S3 = H4⊕H5⊕H6⊕H7 = 1。S3S2S1 = 101 = 5,指出第 5 位错,把它取反就纠正了;三个 S 全 0 表示没有(一位)错。
CRC 拓展:数据 101001、生成多项式 1101(x3+x2+1),在数据后面补 3 个 0 得 101001000,按模 2 除法(异或,不借位)除以 1101,余数 001,发送 101001001;接收端用同一个生成多项式去除,余数为 0 就认为没错。(已用程序验算。)
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 4
- B. 5
- C. 6
- D. 16
展开解析
Bk 个校验位要满足 2k ≥ n + k + 1。n = 16:k = 4 时 16 < 21,不够;k = 5 时 32 ≥ 22,满足。至少 5 位。
变化点:2013-15 是 8 位数据(4 位)。若还要能发现两位错,再加 1 位总校验位,即 6 位。
- 32 位 int 变量 x 的机器数为 FFFFFF9EH,x 等于多少?
答案
100000000H − FFFFFF9EH = 62H = 98 → x = −98。(2018-13 中 x−y 的结果。) short si = −32767; unsigned short usi = si;usi 是多少?unsigned int ui = si;呢?答案
8001H,usi = 32769;ui 先符号扩展成 FFFF8001H = 4294934529 = 232−215+1。- 8 位数 x = 90H、y = 80H,做 x + y,CF、OF、ZF、SF 各是多少?
答案
90H + 80H = 110H,结果 10H。无符号 144+128 = 272 ≥ 256 → CF=1;有符号 (−112)+(−128) = −240 超范围 → OF=1;结果非 0 → ZF=0;最高位 0 → SF=0。 - 把 0.75 写成 float 机器数。
答案
0.75 = 0.11₂ = 1.1₂ × 2−1,E = 126 = 0111 1110,M = 100…0 → 0 01111110 1000…0 = 3F400000H。(可用上面的拆解器验证。) - 8 位定点小数用补码表示,范围是多少?−1 的补码是什么?有没有原码?
答案
−1 ~ 1 − 2−7(即 −1 ~ 127/128);[−1]补 = 1.0000000;−1 没有原码和反码(原码、反码小数的范围只到 −(1 − 2−7))。 - 用双符号位计算 4 位补码的 (−6) + (−3),判断是否溢出。
答案
11 010 + 11 101 = (1) 10 111,丢掉最高进位后符号位是 10 → 负溢出(−9 < −8)。 - 尾数用补码表示时,下面哪些是规格化尾数:0.0110、1.1010、1.0110、0.1100、1.1000?
答案
符号位与最高数值位不同才是规格化:1.0110、0.1100。1.1000 是 −1/2,不是规格化数。 - 8421 码计算 5 + 6。
答案
0101 + 0110 = 1011(> 9,非法)→ 加 0110 修正 → 1 0001,即 11 ✓。 struct { char c; double d; short s; }按边界对齐占多少字节?答案
c 占 0,1~7 填充,d 占 8~15,s 占 16~17,总大小要是 8 的倍数 → 24 字节(gcc 实测)。调成 double、short、char 的顺序则只要 16 字节。- 为什么 f2(24) = 33554432.0 而 f1(24) = 33554431(2017-43 第 3 问)?
答案
f(24) = 225−1,是 25 个 1,而 float 只有 24 位有效位,存不下最后一位 1,舍入进位后变成 225 = 33554432。int 能精确存。
2.✎本篇真题闯关(47 题)
本篇各小节挂的真题全部在这里,按小节排好。可以展开后一道道做,也可以用弹窗模式连续做。做题记录(对/错)只保存在你这台电脑的浏览器里,小节标题下的题号会随之变绿或变红。
指令系统与程序的一生:从 C 代码到进程
上一篇讲“数据”怎么变成 0 和 1,这一篇讲“程序”怎么变成 0 和 1:一条机器指令长什么样、怎么找到操作数(寻址方式)、C 语言的 if/for/函数调用对应什么汇编;最后跟着一个程序走完它的一生——编译、链接、装入内存变成进程,在内存里怎么摆放。后半部分就是操作系统“内存管理”的起点。
3.1一条指令长什么样
一条机器指令 = 操作码(做什么:加、减、跳转……)+ 地址码(对谁做:哪个寄存器、哪个内存地址、或者直接给出的数)。
2010-43 的 16 位指令格式(位 15~12 是 OP,11~6 是源操作数,5~0 是目的操作数)
这类题全是第 0 篇那句话:k 位字段能表示 2k 种。
OP 4 位 → 最多 16 条指令;寄存器编号 3 位 → 最多 8 个通用寄存器;偏移量 16 位补码 → −32768 ~ +32767;直接寻址的地址字段 8 位 → 只能访问 0~255(地址无负数)。
指令字长:定长还是变长,“几字长”指令
- 指令字长 = 一条指令包含的二进制位数。它和机器字长比较,分为半字长指令、单字长指令、双字长指令(多字长指令)。指令字长大于存储字长时,取一条指令要访存多次,取指时间变长。
- 定长指令字结构:所有指令长度相同(RISC,如 32 位 MIPS/RISC-V)——取指、译码简单,利于流水线;变长指令字结构:不同指令长度不同(CISC,如 x86 的 1~15 字节)——功能强、代码紧凑,但取指、译码复杂。
- 定长操作码:操作码位数固定(n 位最多 2n 条指令),译码简单;可变长操作码:不同指令的操作码长度不同,典型做法是下面的“扩展操作码”。注意这两对概念是独立的:定长指令字也可以用可变长(扩展)操作码。
按“地址码有几个”分类
做一次加法要说清楚三件事:两个操作数在哪、结果放哪。把这三个位置都写进指令就是三地址指令;为了让指令短一些,可以让结果覆盖掉一个操作数(二地址),甚至让一个操作数默认就在某个寄存器里(一地址):
| 格式 | 含义 | 访存次数(A 都是主存地址) | 32 位指令、8 位 OP 时每个地址的寻址范围 |
|---|---|---|---|
| 零地址 | ① 不需要操作数:空操作、停机、关中断;② 堆栈计算机:操作数隐含在栈顶,如 ADD = 弹出两个数,相加后压回 | 取指 1 次(堆栈型还要访问栈) | —— |
| 一地址 | ① 单操作数:OP(A1) → A1,如加 1、取反;② 隐含约定另一个操作数在 ACC:(ACC) OP (A1) → ACC | ① 3 次(取指、读、写) ② 2 次(取指、读) | 24 位 → 224 |
| 二地址 | (A1) OP (A2) → A1,结果覆盖第一个操作数 | 4 次(取指、读两个、写一个) | 12 位 → 212 |
| 三地址 | (A1) OP (A2) → A3 | 4 次 | 8 位 → 28 |
| 四地址 | (A1) OP (A2) → A3,A4 = 下一条指令的地址(不用 PC) | 4 次 | 6 位 → 26 |
规律:在指令字长固定时,地址码越多,每个地址码越短,能直接访问的范围越小。所以现代机器让地址码大多是寄存器编号(几位就够),按操作数在哪分为 RR 型(寄存器—寄存器,不访存,最快)、RS 型(寄存器—存储器)、SS 型(存储器—存储器,最慢)。
指令有哪些种类:重点看“会不会改 PC”
| 类别 | 例子 | 对 PC(下一条指令地址)的影响 |
|---|---|---|
| 数据传送 | mov、load/lw、store/sw、push、pop | 顺序执行:PC = 本条地址 + 本条长度 |
| 算术逻辑、移位 | add、sub、and、shl、cmp(只设标志) | 顺序执行 |
| 条件转移 | je、jle、bne | 条件满足才跳;不满足就顺序执行 → 可能不改变执行顺序 |
| 无条件转移 | jmp | 一定跳到目标地址 |
| 过程调用 | call f | 先把返回地址(call 的下一条)保存起来(压栈),再跳到 f 的入口 |
| 返回 | ret | 把保存的返回地址放回 PC,回到调用者“原本的下一条” |
| 陷入(自陷、访管) | int、syscall、trap | 一定转去操作系统的处理程序,CPU 切到内核态(第 5 篇) |
| 输入输出 | in、out(I/O 端口独立编址时才有,第 9 篇) | 顺序执行 |
| 特权指令(系统控制) | 开 / 关中断、修改 PSW 的模式位、设置页表基址寄存器、停机;多数系统的 I/O 指令也是特权指令 | 顺序执行;但只能在内核态执行,用户态执行会引起异常(第 5 篇,2022-27、2026-21) |
教材按功能把指令分成:数据传送类、算术和逻辑运算类、移位类、转移类(无条件转移、条件转移、调用与返回、陷入)、输入输出类,以及只有操作系统能用的特权指令。上表把它们和“会不会改 PC”放在一起,是因为条件转移、调用、返回、陷入这几类正是第 4 篇流水线“控制冒险”和第 5 篇中断的来源。
- A. −32768 ~ +32767
- B. −32767 ~ +32768
- C. −65536 ~ +65535
- D. −65535 ~ +65536
展开解析
A先画出 32 位指令格式,求出偏移量占几位,再用 n 位补码的范围 −2n−1 ~ 2n−1 − 1。
- 16 个通用寄存器 → 寄存器编号要 4 位。
- 格式:OP 8 位 | 源寄存器 4 位 | 基址寄存器 4 位 | 偏移量 32 − 8 − 4 − 4 = 16 位。
- 16 位补码范围:−215 ~ 215 − 1 = −32768 ~ +32767。
- A 对偏移量 16 位,补码范围 −32768 ~ +32767。
- B 错把补码范围的两端对调了:补码是“负数比正数多一个”,+32768 = 215 已超出 16 位补码的最大值 32767。
- C 错−65536 ~ +65535 是 17 位补码的范围,相当于偏移量有 17 位;但 32 − 8 − 4 − 4 只剩 16 位。
- D 错上界 +65536 = 216,16 位补码最大只到 32767,连 16 位无符号数最大也只有 65535,不可能达到。
算字段宽度:总长减去所有已知字段;n 位补码 −2n−1 ~ 2n−1 − 1。同类:2020-16(48 条指令 → OP 6 位,4 种寻址 → 2 位,剩 8 位地址,直接寻址范围 0 ~ 255)。
条件码(标志位):比较指令和条件转移指令怎么“接力”
问题引入:C 语言的 if (a < b) 编译成两条指令:cmp a, b 和 jl 目标。cmp 只做减法、不保存结果;jl 根本不看 a、b 是多少——它凭什么知道 a < b?答案是两条指令之间靠条件码传话。
cmp 像裁判:比完一场只在记分牌上写下几个结论——“差是不是 0”“差是不是负的”“有没有借位”“有没有溢出”。后面的跳转指令只看记分牌,不再重新比赛。不像的地方:记分牌会被下一条算术指令改写,所以 cmp 和条件转移之间一般不能夹着别的会改标志的指令。
教材定义:条件码(又叫标志位、状态标志)存放在程序状态字寄存器 PSW(x86 叫 EFLAGS)中,由运算类指令根据结果自动设置,供条件转移等指令测试。常用四个:ZF 零标志(结果为 0)、SF(也写作 NF)符号标志(结果最高位)、CF 进位 / 借位标志(看无符号数是否越界)、OF 溢出标志(看有符号数是否越界)。它们怎么由加法器电路产生,见第 2 篇 2.4。
跟我做一遍(8 位,已用 verify/08/p03_calc.py 验算):cmp a, b,a = 80H,b = 01H。
- ALU 算 80H − 01H = 7FH(实际做 80H + FFH,丢掉最高位进位)。
- ZF = 0(结果不为 0);SF = 0(7FH 最高位是 0);CF = 0(无符号 128 − 1 不需要借位);OF = 1(负数减正数却得到正数,有符号溢出)。
- 有符号比较:a = −128 < b = 1 成立。jl 的条件是 SF ≠ OF:0 ≠ 1 → 跳。注意这里不能只看 SF——溢出时 SF 给出的符号是反的,所以要用 SF ⊕ OF。
- 无符号比较:a = 128 < b = 1 不成立。jb 的条件是 CF = 1:CF = 0 → 不跳。
再换一组:cmp 05H, FBH → 结果 0AH,CF = 1、OF = 0、SF = 0。有符号 5 < −5 不成立(jl 不跳),无符号 5 < 251 成立(jb 跳)。完整的条件转移表在 3.3 节 常用指令。
真题怎么考:2011-17(无符号“大于”转移:CF = 0 且 ZF = 0)、2018-19 和 2023-16(给出操作数求 CF、OF)、2013-44(指令里的 C、Z、N 三位决定检测哪些标志,“无符号小于等于”取 C = 1、Z = 1)、2019-45(5)(imul 后用 OF 判乘法溢出)。判别法:CF 管无符号、OF 管有符号;“小于”有符号看 SF ≠ OF、无符号看 CF = 1。失效的情况:并不是每条指令都改标志(x86 的 mov 不改标志、inc 不改 CF);题目自定义指令系统时,要按题目说的“哪些指令设置哪些标志”来。
- 8 位 cmp FFH, 01H 之后,ZF、SF、CF、OF 各是多少?jl、jb 跳不跳?
答案
FFH − 01H = FEH:ZF = 0,SF = 1,CF = 0,OF = 0。有符号 −1 < 1,jl(SF ≠ OF:1 ≠ 0)跳;无符号 255 < 1 不成立,jb(CF = 1?)不跳。 - 为什么说 cmp 和 sub 的区别只有一点?
答案
两者都做减法、都设置标志;sub 把差写回目的操作数,cmp 不写回(只要标志)。
扩展操作码:常用的指令给短操作码
定长指令字里,地址码多的指令留给操作码的位少,地址码少的指令能用更长的操作码。扩展操作码的规则:短操作码不能是长操作码的前缀(否则译码分不清)。做法是在短操作码里留几个编码不用,拿它们当“前缀”扩展出下一级。
和定长操作码比一比:同样是 16 位指令、每个地址码 4 位,若操作码定长 4 位,最多只有 24 = 16 条指令,而且每条都得按三地址格式编码(地址码少的指令也浪费着那几位);用扩展操作码,同样 16 位能编出 15 + 15 + 15 + 16 = 61 条。定长操作码的好处是译码简单、速度快,RISC 多用它(3.4)。
① 短操作码不能是长操作码的前缀,否则译码时分不清(所以上一级必须“留”编码当前缀);② 各条指令的操作码一定不能重复;③ 通常让使用频率高的指令用短操作码,减少平均指令长度(可变长指令字时效果明显)。
万能式(定长指令字 L 位,各级地址码总长分别为 a1 > a2 > …):把每条 k 级指令看成“占掉”2ak 个完整编码,则 Σ(该级指令条数 × 2该级地址码总位数)≤ 2L。求“最多还能有多少条某级指令”,就用 2L 减去已占的,再除以 2该级地址位数。2022-19 的一步式就是它。
- A. 0
- B. 2
- C. 64
- D. 128
展开解析
D扩展操作码:长操作码的指令用剩下的“前缀”往后扩展。每少一个地址码,操作码就多 6 位。
- 二地址:OP = 16 − 6 × 2 = 4 位,共 24 = 16 个编码,用掉 12,剩 4 个作扩展前缀。
- 一地址:每个前缀后再拼 6 位 → 4 × 26 = 256 个编码,用掉 254,剩 2 个前缀。
- 零地址:2 × 26 = 128 条。一步式验算:216 − 12 × 212 − 254 × 26 = 128 ✓。
- A 错0 条是以为一地址 254 条已经把编码用完;其实一地址最多可有 256 个编码,还剩 2 个可以继续扩展。
- B 错2 只是一地址剩下的前缀个数,忘了每个前缀后面还要再拼 6 位(原来的地址码位)当操作码,要乘 26。
- C 错64 = 26,只算了 1 个前缀的扩展;一地址剩下的是 2 个前缀,应为 2 × 64。
- D 对2 × 26 = 128。
扩展操作码一步式:零地址最多条数 = 216 − Σ(各类条数 × 2该类地址码总位数)。
- A. 24 位
- B. 26 位
- C. 28 位
- D. 32 位
展开解析
A三地址指令地址码最多,所以它的操作码最短;剩下没用的操作码编码当作前缀,扩展出二地址指令。
- 三地址指令 29 条:操作码至少 5 位(25 = 32 ≥ 29),用掉 29 个,剩 3 个编码作前缀。
- 二地址指令:每个前缀后面多出一个地址字段的 6 位可作操作码,共 3 × 26 = 192 ≥ 107,够用。
- 指令字长 = 5 + 3 × 6 = 23 位;按字节编址,指令长度应为字节的整数倍 → 24 位。
- A 对24 位。
- B 错26 位不是 8 的整数倍,按字节编址时不合适,而且也不是“至少”。
- C 错28 位同样不是字节的整数倍。
- D 错32 位当然够用,但 24 位已能放下 5 位操作码 + 3 个 6 位地址(共 23 位),32 不是最短。
扩展操作码:某一级剩 r 个编码,下一级可用 r × 2让出的位数 个。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 8
- B. 32
- C. 256
- D. 512
展开解析
C二地址指令的操作码有 16 − 10 = 6 位,共 64 个编码,用去 60 个,剩 4 个可扩展。一地址指令:4 × 25 = 128 个编码,用去 120 个,剩 8 个。零地址指令最多 8 × 25 = 256 条。
变化点:2022-19 的地址码 6 位。每一级都是“剩下的编码 × 2地址码位数”,一级一级往下乘。
- A. −16384 ~ +16383
- B. −32768 ~ +32767
- C. −8192 ~ +8191
- D. 0 ~ 32767
展开解析
A32 个寄存器,每个寄存器号 5 位。偏移量 = 32 − 7 − 5(目的寄存器)− 5(基址寄存器)= 15 位,补码范围 −214 ~ 214 − 1。
变化点:2014-17 是 16 个寄存器(4 位)、操作码 8 位,偏移 16 位。寄存器数和操作码位数一变,偏移的位数跟着变。
- A. 0 ~ 127
- B. −64 ~ +63
- C. −128 ~ +127
- D. 0 ~ 511
展开解析
B操作码 ⌈log260⌉ = 6 位,寻址方式 ⌈log28⌉ = 3 位,地址字段 16 − 6 − 3 = 7 位。相对寻址的位移是补码:−64 ~ +63。
变化点:2020-16 问直接寻址(地址是无符号数,0 ~ 2n − 1)。同样 n 位,相对寻址是补码,范围是 −2n−1 ~ 2n−1 − 1。
cmp al, bl(计算 al − bl,只设置标志),al = 90H,bl = 10H。之后分别执行有符号小于转移 jl 和无符号小于转移 jb,结果是( )。- A. jl 转移,jb 不转移
- B. jl 不转移,jb 转移
- C. 两条都转移
- D. 两条都不转移
展开解析
A90H − 10H = 80H:ZF = 0,SF = 1,CF = 0(无符号 144 ≥ 16,不借位),OF = 0(−112 − 16 = −128,在范围内)。jl 的条件 SF ≠ OF:1 ≠ 0,转移(有符号 −112 < 16 ✓);jb 的条件 CF = 1:不满足,不转移(无符号 144 > 16 ✓)。
变化点:真题问“某条件转移看哪几个标志”或“CF、OF 是多少”,这里把两步连起来:先算标志,再判断跳不跳(条件码的接力)。
3.2寻址方式:怎么找到操作数
先分清:指令寻址和数据寻址
“寻址”要找两样东西:下一条指令在哪(指令寻址),这条指令的操作数在哪(数据寻址)。前者只有两种:
顺序寻址
(PC) + “1” → PC,按顺序取下一条。“1”是一条指令的长度:按字节编址、指令 4 字节时 PC + 4;变长指令时加本条指令的字节数(2023-44 第 19 条指令长 11 字节)。跳跃寻址
下一条指令的地址由本条指令给出——转移、调用、返回、陷入指令。给法有两种:直接给出目标地址(绝对转移),或给出相对 PC 的偏移(相对转移,见下面相对寻址)。跳跃寻址是通过修改 PC 的内容实现的。下面讲的都是数据寻址。一条指令里可能有好几个操作数,每个操作数的寻址方式由指令中的寻址特征位(或操作码隐含)指明——CPU 正是靠寻址方式来区分寄存器里放的是“操作数”还是“操作数的地址”(2023-17)。
指令里的地址字段叫形式地址 A,它不一定就是操作数所在的地址。根据寻址方式,CPU 用 A 算出真正的地址——有效地址 EA,再去拿操作数。先看一个例子,同一组数据、六种寻址方式能拿到六个不同的结果:
| 设:形式地址 A = 2000H,寄存器 R = 1000H;内存 [1000H] = 2000H,[2000H] = 3000H,[3000H] = 4000H | |||||
|---|---|---|---|---|---|
| 寻址方式 | 记法 | 有效地址 EA | 取到的操作数 | 取操作数要访存几次 | 用途 |
| 立即寻址 | #A | (没有,A 本身就是数) | 2000H | 0 | 常数 |
| 直接寻址 | A | A = 2000H | 3000H | 1 | 全局变量 |
| 间接寻址 | (A) | (A) = 3000H | 4000H | 2 | 指针 |
| 寄存器寻址 | R | (在寄存器里) | 1000H | 0 | 最快 |
| 寄存器间接 | (R) | (R) = 1000H | 2000H | 1 | 指针在寄存器里 |
| 变址 / 基址 | A(R) | (R) + A = 3000H | 4000H | 1 | 数组、局部变量 |
| 相对寻址 | A(PC) | (PC) + A | 一般用于跳转目标地址 | 转移指令 | |
“访存次数”只算取操作数,不算取指令本身。2013-17 考的就是变址寻址这一行:EA = 1000H + 2000H = 3000H,操作数 4000H(选 D)。
把表里的几种方式画成“寻宝路线”:从指令出发,箭头走几步才找到操作数,就访存几次。
十种数据寻址方式全表(考研要逐个会)
| 寻址方式 | 有效地址 EA | 取数访存 | 优点 | 缺点 / 注意 |
|---|---|---|---|---|
| 隐含寻址 | 不写地址,操作数的位置由操作码隐含规定(如一地址指令的另一个操作数在 ACC) | 0 | 缩短指令字长 | 需要专门硬件存放隐含的操作数 |
| 立即寻址 | 无,形式地址 A 本身就是操作数(#A,常用补码) | 0 | 取指时操作数就到手,最快 | A 的位数限制了立即数的范围 |
| 直接寻址 | EA = A | 1 | 简单,不需要计算 | A 的位数限制寻址范围(2A 位数);程序中地址固定,不便修改 |
| 一次间接寻址 | EA = (A) | 2 | 扩大寻址范围(地址宽度 = 存储字长);便于编指针、子程序返回 | 多访存一次,慢 |
| 多次间接 拓展 | 主存字的最高位是标志:1 表示“还是地址,继续间接”,0 表示找到了 EA | ≥ 3 | —— | 更慢;地址少 1 位 |
| 寄存器寻址 | 操作数在寄存器 Ri 中(不是地址) | 0 | 指令短(只写寄存器编号),不访存,快 | 寄存器数量有限 |
| 寄存器间接 | EA = (Ri) | 1 | 比一次间接少访存一次;范围大 | —— |
| 相对寻址 | EA = (PC) + A,A 为补码 | 1 | 程序在内存里整体搬家也不用改,便于编写浮动程序;用于转移 | PC 是“取指后”的值(下一条的地址) |
| 基址寻址 | EA = (BR) + A | 1 | 扩大寻址范围;便于多道程序重定位 | BR 面向操作系统,程序执行中一般不变 |
| 变址寻址 | EA = (IX) + A | 1 | 处理数组、循环最方便 | IX 面向用户,程序执行中不断修改 |
| 堆栈寻址 | EA = (SP),操作数在栈顶,由 SP 隐含指出 | 硬堆栈 0 软堆栈 1 | 指令极短(零地址) | 硬堆栈用一组寄存器,快但小;软堆栈在主存里 |
上面“取数访存”都不含取指令本身那 1 次。速度大致是:立即 > 寄存器 > 直接 ≈ 寄存器间接 ≈ 偏移(相对/基址/变址)> 间接。复合方式按字面顺序算:先变址后间址 EA = ((IX) + A)(2016-17);先间址后变址 EA = (A) + (IX)。变址或寄存器间接还常带自动增减:(R)+ 表示先用 R 的内容作地址取数、再把 R 加 1(后增),−(R) 表示先把 R 减 1 再用(先减);“加 1”指加一个操作数的长度:按字编址时就是 +1(2010-43 按字编址,(R5)+ 执行后 R5 从 5678H 变成 5679H),按字节编址、操作数 2 字节时是 +2,看题目规定。
基址寻址 vs 变址寻址:公式一样,用法不同
基址寻址 EA = (BR) + A
基址寄存器 BR 放一块区域的起点(由操作系统或程序设置,程序执行中一般不变),A 是区域内的偏移。用来访问“以某处为起点”的数据,比如栈帧里的局部变量 [ebp−8]。变址寻址 EA = (IX) + A
A 放数组首地址(固定),变址寄存器 IX 放下标×元素大小,每次循环改它。最适合按下标顺序访问数组(2017-15 选 D)。| 偏移寻址 | 公式 | 寄存器由谁设置、变不变 | A 是什么 | 典型用途 |
|---|---|---|---|---|
| 基址 | (BR) + A | 操作系统或管理程序设置,程序执行中不变;也可以用通用寄存器当基址(显式) | 相对基址的偏移 | 多道程序的重定位;局部变量 [ebp−8] |
| 变址 | (IX) + A | 用户程序设置,程序执行中不断改变(每次循环 +1 或 + 元素大小) | 数组首地址(固定) | 数组、循环 |
| 相对 | (PC) + A | PC 由硬件自动修改 | 相对当前指令的偏移(补码) | 转移指令、浮动程序 |
三者的共同点是“某个寄存器 + 形式地址”,统称偏移寻址;间接寻址 EA = (A) 不用寄存器,不属于偏移寻址(2011-16)。
堆栈寻址:push 和 pop 时 SP 怎么变
堆栈是按“后进先出”存取的一块区域,栈顶的地址放在堆栈指针 SP 里。主存里的软堆栈通常从高地址向低地址长(x86 就是这样),这时 push/pop 的动作是:
- A. 25
- B. 32
- C. 64
- D. 100
展开解析
考点3.2 变址寻址
B变址寻址 EA = 首地址 + 变址值 × 元素大小;反过来,变址值 = (EA − 首地址) ÷ 元素大小。注意 H 是十六进制。
- 偏移 = 2100H − 2000H = 100H = 256 字节。
- double 占 8 字节 → 下标 = 256 ÷ 8 = 32,就是进入该次循环时变址寄存器的值(取完才加 1)。
- A 错25 = 100 ÷ 4:把 100H 当成了十进制 100,又把 double 当成 4 字节,两处都错。
- B 对100H = 256,256 ÷ 8 = 32。
- C 错64 = 256 ÷ 4:十六进制换算对了,但 sizeof(double) 是 8,不是 4。
- D 错100 是把偏移 100H 直接当成十进制的下标,既没换算进制,也没除以元素大小 8。
数组下标 = (元素地址 − 首地址) ÷ sizeof(元素);带 H 的数先换成十进制。同类:2016-17(先变址后间址 EA = ((I) + D),选 C);2011-16(间接寻址 EA = (A) 不用寄存器,不属于偏移寻址)。
寄存器、隐含、堆栈寻址:三种“几乎不写地址”的寻址,各跟做一例
问题引入:全表里这三种寻址的“取数访存”都是 0 次或很少,指令也特别短。它们是怎么省掉地址的?代价又是什么?
点外卖时说地址有三种偷懒法:“送到 3 号柜”(寄存器寻址:只写一个很短的编号);“老地方”(隐含寻址:什么都不写,双方事先约好);“放在最上面那一格”(堆栈寻址:位置由“栈顶”决定)。不像的地方:硬件必须专门准备好“老地方”和“栈顶指针”(ACC、SP),这就是“需要专门硬件”的代价。
- 寄存器寻址:
ADD R1, R2表示 (R1) + (R2) → R1。两个操作数都在寄存器里,指令里只写两个寄存器号(8 个寄存器各 3 位)。访存:取指 1 次,取操作数 0 次。 - 隐含寻址:单累加器机器的
ADD X表示 (ACC) + (X) → ACC。X 用直接寻址,另一个操作数和结果的位置都隐含在 ACC 里,指令里一个字也没写。访存:取指 1 次 + 读 X 1 次 = 2 次。 - 堆栈寻址:在“堆栈计算机”上算 x = (a + b) × c,用 6 条指令,栈的变化如下图。
verify/08/p03_calc.py 验算)。真题怎么考:2017-15(哪种寻址方式适合按下标访问数组:变址;寄存器寻址这类“速度最快”的结论也常作干扰项)、2022-19(零地址指令条数)、3.1 的地址码分类表。判别法:看操作数“写在指令里的是什么”——写的是数(立即)、寄存器号(寄存器)、什么都没写(隐含 / 堆栈)。失效的情况:“堆栈寻址不访存”只对硬堆栈成立;软堆栈的栈在主存,取栈顶要访存 1 次。
- 用栈机指令写出 x = a × b + c × d,共几条?
答案
push a;push b;mul;push c;push d;mul;add;pop x,共 8 条(后缀式 a b × c d × +)。 - 立即寻址和寄存器寻址取操作数都不访存,哪个更快?
答案
立即寻址:操作数随指令一起取进来,不用再读寄存器堆;寄存器寻址还要按编号读一次寄存器(很快,但多一步)。
相对寻址:最容易错在“PC 已经加过了”
相对寻址 EA = (PC) + A 里的 PC,是取完这条指令之后的 PC,已经指向下一条指令了。所以跳转目标 = 本指令地址 + 本指令长度 + 偏移量。偏移量是补码,可正可负;有的机器偏移量以“字/指令”为单位,还要先乘指令长度(看题目怎么说,或者从代码里反推)。
- A. 2006H
- B. 2007H
- C. 2008H
- D. 2009H
展开解析
考点3.2 相对寻址
C相对寻址的基准是取完这条指令之后的 PC。题目说每取一个字节 PC 加 1,这条转移指令 2 字节。
- 取完指令后 PC = 2000H + 2 = 2002H。
- 目标地址 = 2002H + 06H = 2008H。
- A 错2006H:用了本条指令的地址 2000H 作基准,忘了 PC 已经自增。
- B 错2007H:只加了 1(只算了取操作码那一个字节)。
- C 对2002H + 6 = 2008H。
- D 错2009H = 2002H + 06H + 1,比正确值多加了 1:PC 只在取指令的两个字节时各加 1,转移时不再加。
相对转移目标 = 本条地址 + 本条长度 + 位移量。
展开解析
- 编址:指令 16 位 = 2 字节,顺序执行 PC+2 → 每个地址是 1 字节,按字节编址。OFFSET 8 位补码 −128~127,目标 − 本指令地址 = 2 + 2×OFFSET,最负是 2 − 256 = −254 字节 = 127 条指令。
- 第一种情况:Z=1、N=1 要检测 ZF 和 NF,NF=1 → 转移。OFFSET = E3H = −29,目标 = 200CH + 2 + 2×(−29) = 200EH − 3AH = 1FD4H。
- 第二种情况:只检测 ZF、NF,它们都是 0 → 不转移,PC = 200CH + 2 = 200EH(CF=1 不在检测范围内)。
- (3)无符号 A ≤ B ⇔ A−B 借位(CF=1)或为零(ZF=1),不看符号 → C=1、Z=1、N=0。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 1FF0H
- B. 1FF3H
- C. 2003H
- D. 2013H
展开解析
B取完这条指令后 PC = 2000H + 3 = 2003H。FFF0H 是 −16 的补码,目标地址 = 2003H − 10H = 1FF3H。
变化点:2009-16 的指令 2 字节、位移为正数(2002H + 6 = 2008H)。这里有两处变化:指令长 3 字节,PC 要加 3;位移是负数,要先把补码换成真值。A 是用指令自己的地址去加。
- A. 3000H
- B. 4000H
- C. 5000H
- D. 2000H
展开解析
C先间址:取 (A) = (2000H) = 3000H;再变址:EA = 3000H + (R) = 4000H;操作数 = (4000H) = 5000H。
变化点:2016-17 是“先变址后间址”:EA = ((R) + A) = (3000H) = 4000H,操作数也是 5000H,但计算路线不同,要分清。B 是把有效地址当成了操作数。
- A. 32
- B. 64
- C. 100
- D. 256
展开解析
D偏移地址 = 3100H − 3000H = 100H = 256。变址寄存器里放的是字节偏移,它就等于 256。
变化点:2018-18 中变址寄存器放的是下标,偏移 = 下标 × 8,所以要除以 8。这里寄存器直接放字节偏移,不用再除。若按下标算,会得到 B(256 ÷ 4 = 64)。
- A. −7
- B. 13
- C. 3
- D. 7
展开解析
D栈依次为 [8] → [8, 2] → add → [10] → [10, 3] → sub:y = 3、x = 10,压入 10 − 3 = 7 → pop x 得 7。A 是把减数和被减数弄反(3 − 10);B 是当成加法。add、sub 是零地址指令,操作数都隐含在栈顶。
考点:堆栈寻址与零地址指令。
3.3看懂汇编:C 代码的机器级表示
x86 的寄存器:先认识“演员”
同一条指令的两种写法:Intel 格式与 AT&T 格式
| Intel 格式(Windows、408 大题最常见) | AT&T 格式(Linux 下 gcc 默认) | |
|---|---|---|
| 操作数顺序 | 目的在前:mov eax, ebx(ebx → eax) | 源在前:movl %ebx, %eax |
| 寄存器 / 立即数 | eax、8 | %eax、$8 |
| 内存操作数 | [ebp+8]、[ebx+ecx*4+8] | 8(%ebp)、8(%ebx,%ecx,4) |
| 操作数长度 | dword ptr(32 位)、word ptr(16 位)、byte ptr(8 位) | 指令后缀 l、w、b:movl、movw、movb |
两种写法翻译出的机器码完全一样。看到 %、$ 就是 AT&T,记得把源和目的倒过来读。内存操作数的一般形式是 [基址寄存器 + 变址寄存器 × 比例因子 + 偏移],比例因子只能是 1、2、4、8,正好对应 char、short、int、double 数组。
常用指令
408 大题里的汇编大多是 x86(Intel 格式),少数是 MIPS/RISC-V 风格。不需要会写,只要看得懂下面这些:
| 指令 | 意思 | 备注 |
|---|---|---|
| mov eax, [ebp+8] | eax ← 内存[ebp+8](方括号 = 取内存) | Intel 格式:目的在左 |
| mov [ebp-4], 0 | 内存[ebp−4] ← 0 | 立即数写内存 |
| add / sub / imul eax, ecx | eax ← eax ± × ecx | 会设置标志位 |
| shl edx, 1 | edx 左移 1 位(×2) | 整数才能这样乘 2(2017-44) |
| cmp a, b | 算 a − b,只设标志不存结果 | 后面跟条件跳转 |
| jmp / je / jne / jle / jge / jb … | 无条件跳 / 按标志跳 | jl/jg 有符号,jb/ja 无符号 |
| push x / pop x | 入栈 / 出栈(esp 减 4 / 加 4) | 栈向低地址增长 |
| call f / ret | call:把下一条指令地址压栈,再跳到 f;ret:弹出返回地址到 PC | 过程调用 |
| lw r5, 0(r4) / sw | RISC:r5 ← M[r4+0],只有 load/store 能访存 | 2014-44、2024-43 |
再补几条大题代码里常出现、又容易看错的:
| 指令 | 意思 | 易错点 |
|---|---|---|
| lea eax, [ebx+ecx*4] | eax ← ebx + ecx×4(只算地址,不访存) | 和 mov eax, [ebx+ecx*4] 不同:mov 会去内存取数。编译器也常用 lea 做“乘常数 + 加法” |
| inc / dec x | x ← x ± 1 | 循环变量 i++ 常编译成它 |
| neg x / not x | x ← −x(求补)/ x ← 按位取反 | neg = 取反加 1 |
| and / or / xor a, b | 按位与 / 或 / 异或 | xor eax, eax 是把 eax 清零的惯用写法 |
| test a, b | 算 a AND b,只设标志不存结果 | 和 cmp 的关系:cmp 做减法、test 做与;test eax, eax 用来判断 eax 是否为 0 / 负 |
| shl/sal、shr、sar | 左移、逻辑右移、算术右移 | 无符号数右移用 shr,有符号数右移用 sar(2.3 节) |
| imul ecx / idiv ecx | 单操作数形式:edx:eax ← eax × ecx;eax ← edx:eax ÷ ecx 的商,edx ← 余数 | 积是 64 位,高 32 位在 edx(2019-45 第 5 问判溢出就看它) |
| 条件转移(在 cmp a, b 之后) | 有符号数 | 无符号数 | 看哪些标志 |
|---|---|---|---|
| a = b / a ≠ b | je(jz)/ jne(jnz) | ZF | |
| a > b | jg(jnle) | ja(jnbe) | 有符号:ZF=0 且 SF=OF;无符号:CF=0 且 ZF=0 |
| a ≥ b | jge(jnl) | jae(jnb) | 有符号:SF=OF;无符号:CF=0 |
| a < b | jl(jnge) | jb(jnae) | 有符号:SF≠OF;无符号:CF=1 |
| a ≤ b | jle(jng) | jbe(jna) | 有符号:ZF=1 或 SF≠OF;无符号:CF=1 或 ZF=1 |
记法:g/l(greater/less)用于有符号,a/b(above/below)用于无符号;n 表示 not,e 表示 equal。表中标志的判断就是 2.4 节“比较 A 和 B”那张表。
// if (a > b) x = 1; → 条件相反时跳过 mov eax, [a] cmp eax, [b] ; 算 a−b 设标志 jle skip ; a ≤ b 就跳过(有符号用 jle,无符号用 jbe) mov [x], 1 skip: // for (i = 0; i < n; i++) sum += a[i]; → 循环 = 条件跳转往回跳 mov ecx, 0 ; i = 0 loop: cmp ecx, [n] jge done add eax, [ebx+ecx*4] ; a[i]:首地址 ebx + i×4(变址) inc ecx jmp loop done:
if-else:cmp + 条件相反的跳转(条件不成立就跳过 then 部分);then 部分末尾一条 jmp 跳过 else 部分。
while / for:开头(或末尾)一条 cmp + 条件跳转,循环体末尾一条往回跳(目标地址比自己小)的 jmp——看到“往回跳”就是循环。
do-while:先执行循环体,末尾 cmp + 条件跳转往回跳,所以循环体至少执行一次。
数组 a[i]:[首地址 + i × 元素大小],常见 [ebx+ecx*4] 或先 shl/sll 左移 2 位再加首地址(2014-44)。
函数调用:一串 push(参数)+ call;函数开头 push ebp; mov ebp, esp,结尾 leave; ret(或 mov esp, ebp; pop ebp; ret)。
过程调用:一步一步看栈怎么变
图 3-8 是一个栈帧的“静态照片”。下面把 r = add(1, 2); 的整个调用过程逐条执行一遍(32 位 x86 常见约定,每格 4 字节):
; 调用者 main 中 push 2 ; 参数从右往左压栈 push 1 call add ; 压返回地址,跳转 add esp, 8 ; 调用者清掉两个参数 mov [ebp-4], eax ; r = 返回值
; 被调用者 add(a, b) add: push ebp ; 保存调用者的 ebp mov ebp, esp ; 建立自己的栈帧 sub esp, 4 ; 给局部变量 s 留位置 mov eax, [ebp+8] ; a add eax, [ebp+12] ; a + b mov [ebp-4], eax ; s = a + b mov eax, [ebp-4] ; 返回值放 eax leave ; = mov esp,ebp; pop ebp ret ; 弹出返回地址 → PC
| 执行完这一条 | esp | ebp | 栈里新增 / 发生了什么 |
|---|---|---|---|
| (调用前) | 1000H | 1010H | main 的栈帧 |
| push 2 | 0FFCH | 1010H | [0FFCH] = 2(参数 b) |
| push 1 | 0FF8H | 1010H | [0FF8H] = 1(参数 a) |
| call add | 0FF4H | 1010H | [0FF4H] = 返回地址(call 下一条 add esp, 8 的地址),PC ← add 的入口 |
| push ebp | 0FF0H | 1010H | [0FF0H] = 1010H(旧 ebp) |
| mov ebp, esp | 0FF0H | 0FF0H | 新栈帧的底:[ebp+4] = 返回地址,[ebp+8] = a,[ebp+12] = b |
| sub esp, 4 | 0FECH | 0FF0H | [ebp−4] = [0FECH] 是局部变量 s |
| ……函数体…… | 0FECH | 0FF0H | eax = 1 + 2 = 3,存入 s,再放回 eax 作返回值 |
| leave | 0FF4H | 1010H | esp ← ebp(丢掉局部变量),pop ebp(恢复调用者的帧) |
| ret | 0FF8H | 1010H | PC ← [0FF4H],回到 main 的 add esp, 8 |
| add esp, 8 | 1000H | 1010H | 两个参数被清掉,栈恢复到调用前的样子 |
① 参数由调用者从右往左压栈,被调用者用 [ebp+8]、[ebp+12]……取([ebp+4] 是返回地址,[ebp] 是旧 ebp);局部变量在 [ebp−4]、[ebp−8]……。
② 返回值放在 eax。
③ 寄存器保存:eax、ecx、edx 由调用者保存(调用者若调用后还要用,得自己先存);ebx、esi、edi、ebp 由被调用者保存(被调用者要用就先 push、返回前 pop)。
④ 每调用一次就在栈上叠一个新栈帧,所以递归 n 层就有 n 个栈帧,递归太深会栈溢出;返回时按相反顺序一层层拆掉(2019-45)。
⑤ call 的返回地址是 call 的下一条指令的地址,也就是执行 call 时 PC 的值——和相对寻址“PC 已经指向下一条”是同一件事。
大题精讲:2019-45 + 2019-46(递归、call、大小端、分页、Cache 一题串起来)
1 00401000 55 push ebp 11 00401018 83 7D 08 01 cmp dword ptr [ebp+8], 1 ; if (n > 1) 12 0040101C 7E 17 jle f1+35h (00401035) 13 0040101E 8B 45 08 mov eax, dword ptr [ebp+8] 14 00401021 83 E8 01 sub eax, 1 15 00401024 50 push eax 16 00401025 E8 D6 FF FF FF call f1 (00401000) ; return n*f1(n-1) 19 00401030 0F AF C1 imul eax, ecx 20 00401033 EB 05 jmp f1+3Ah (0040103a) 21 00401035 B8 01 00 00 00 mov eax, 1 ; else return 1 26 00401040 3B EC cmp ebp, esp 30 0040104A C3 ret
(2019-46)主存地址 32 位,页 4KB:第 1 行 push 和第 30 行 ret 在同一页吗?指令 Cache 64 行、4 路组相联、块 64B:32 位地址哪几位是块内地址、组号、标记?读 call 指令只可能在哪一组命中?
展开解析
- (1)f1(10) → f1(9) → … → f1(1),共 10 次;第 16 行 call 递归调用。
- (2)第 12 行 jle 是条件转移;call、jmp、ret 一定跳转。
- (3)call 在 00401025H,机器码 E8 D6 FF FF FF 共 5 字节 → 第 17 行地址 = 0040102AH。call 用相对寻址:偏移 = 目标 − (PC) = 00401000H − 0040102AH = −2AH = FFFFFFD6H。机器码里写成 D6 FF FF FF,最低字节在前 → 小端。(顺手验算 jle:0040101CH + 2 + 17H = 00401035H ✓)
- (4)6227020800 > 231−1,超出 int 表示范围,乘法溢出,只留下了低 32 位。把返回值类型改成 long long 或 double。
- (5)有符号乘法,64 位乘积的高 33 位不全为 0 也不全为 1时 OF=1(结果不能用 32 位补码表示)。在 imul 后加一条溢出自陷指令(如 INTO),OF=1 时自动进入操作系统的溢出异常处理程序——这就是第 5 篇的“自陷”。
- (2019-46 分页)4KB = 212,页号是地址高 20 位 = 去掉最后 3 个十六进制位。00401000H 和 0040104AH 的页号都是 00401H → 同一页。
- (Cache)64 行 ÷ 4 路 = 16 组 → 组号 4 位;块 64B → 块内地址 6 位;标记 32−4−6 = 22 位。即 A5~A0 块内,A9~A6 组号,A31~A10 标记。
- (哪一组)页内偏移(低 12 位)在虚→实转换中不变,而 Cache 用的低 10 位都落在页内偏移里,所以直接用虚拟地址的低 12 位 025H = 0000 0010 0101B 就能算组号:A9~A6 = 0000 → 第 0 组。这一步是第 8 篇的“页内偏移原样传递”。
08048100H 00022080H loop: sll R4, R2, 2 ; (R2)<<2 → R4 08048104H 00083020H add R4, R4, R3 ; (R4)+(R3) → R4 08048108H 8C850000H load R5, 0(R4) ; M[(R4)+0] → R5 0804810CH 00250820H add R1, R1, R5 08048110H 20420001H add R2, R2, 1 08048114H 1446FFFAH bne R2, R6, loop ; if (R2)≠(R6) goto loop
展开解析
- 32 位指令 = 4 字节,相邻指令地址差 4 → 按字节编址。
- i 左移 2 位 = i×4 作为字节偏移 → 元素 4 字节 = 32 位。
- 机器码 1446FFFAH 低 16 位 OFFSET = FFFAH = −6。bne 在 08048114H,要跳回 08048100H,差 −18H = −24 字节 = −6 × 4。所以偏移以“指令(4 字节)”为单位,并且从下一条(+4)算起:目标 = (PC) + 4 + OFFSET × 4。验算:08048114H + 4 − 24 = 08048100H ✓。第(4)问流水线见第 4 篇。
64 位程序怎么传参数:IA-32 与 x86-64 的调用约定 拓展
问题引入:408 真题里的汇编都是 32 位 x86(IA-32),参数在栈上,用 [ebp+8] 取。可如果你在自己的 64 位电脑上编译、反汇编一个函数,往往找不到 [ebp+8],参数却出现在 rdi、rsi 或 rcx、rdx 里——因为 64 位换了一套“调用约定”。
大白话:调用约定是调用者和被调用者之间的“交接规矩”:参数放哪、返回值放哪、哪些寄存器谁负责保存。32 位 x86 只有 8 个通用寄存器,参数只好全放栈上;x86-64 有 16 个,就把前几个参数直接放进寄存器——少访存、更快,和 RISC“多用寄存器传参数”(3.4)是同一个思路。
| IA-32(cdecl,408 真题用) | x86-64 System V | x86-64 Windows | |
|---|---|---|---|
| 整数参数 | 全部从右往左压栈,被调用者用 [ebp+8]、[ebp+12]… 取 | 前 6 个:rdi、rsi、rdx、rcx、r8、r9;其余压栈 | 前 4 个:rcx、rdx、r8、r9;其余压栈 |
| 返回值 | eax | rax | rax |
| 指针宽度 | 32 位 | 64 位 | 64 位 |
跟我做一遍(verify/08/p03_abi.c,gcc 13.2 生成的汇编,摘关键行):
; IA-32,-m32 -O0 push ebp mov ebp, esp mov edx, [ebp+8] ; a mov eax, [ebp+12] ; b add edx, eax mov eax, [ebp+16] ; c add eax, edx
; x86-64 System V,-O1 add rdi, rsi ; a+b add rdi, rdx ; +c add rdi, rcx ; +d add rdi, r8 ; +e lea rax, [rdi+r9] ; +f add rax, [rsp+8] ; +g
; x86-64 Windows,-O1 add rcx, rdx ; a+b add rcx, r8 ; +c lea rax, [rcx+r9] ; +d add rax, [rsp+40] ; +e add rax, [rsp+48] ; +f add rax, [rsp+56] ; +g
考试怎么办:408 的 2017-44、2019-45、2023-44 都是 IA-32 代码,按题目给的代码读即可,不需要背 64 位约定;这一段只是让你自己做实验时不困惑。
3.4CISC 与 RISC
RISC 的出发点是一个统计规律——80-20 规律:典型程序中 80% 的运行时间只用到了指令系统里 20% 的简单指令,复杂指令既少用又拖慢整台机器。于是 RISC 只保留常用的简单指令,把它们做得很快;复杂功能交给编译器用多条简单指令组合。
| CISC 复杂指令集(x86) | RISC 精简指令集(ARM、MIPS、RISC-V) | |
|---|---|---|
| 指令数目 | 多(几百条),有很多功能复杂的专用指令 | 少,只选使用频率高的简单指令 |
| 指令长度与格式 | 变长,格式多 | 定长(一般 32 位),格式少、字段位置固定 |
| 寻址方式 | 多 | 少 |
| 访存 | 很多指令都能直接访存(如 add 的一个操作数可以在内存) | 只有 load/store 访存,其余指令只在寄存器之间操作 |
| 通用寄存器 | 较少 | 多 |
| 各指令执行时间 | 相差很大,多数要多个时钟周期 | 大多数指令一个时钟周期完成 |
| 控制器 | 多用微程序控制 | 多用硬布线控制(快) |
| 流水线 | 难以实现高效流水线 | 容易,几乎必然采用 |
| 过程调用传参数 | 多用栈(访存) | 寄存器多,多用寄存器传参数,少访存 |
| 编译 | 难以用优化编译生成高效代码 | 重视编译优化,靠编译器合理安排指令顺序、分配寄存器 |
| 目标代码 | 短(一条指令干得多) | 较长(要多条简单指令) |
两者不是非此即彼:RISC 并不是“指令越少越好”,CISC 为了兼容老程序一直保留复杂指令,而现代 x86 处理器内部会先把 CISC 指令拆成类似 RISC 的微操作再送进流水线 拓展。
为什么 RISC 容易做流水线?流水线就像工厂的装配线,要求每件“产品”走同样的工序、每道工序时间差不多(第 4 篇 4.4)。RISC 的三个特点正好满足(2011-18):① 指令定长、格式规整——取指一步就能取完一条,译码时各字段位置固定;② 数据按边界对齐——一次访存就能取到一个数据,不会一个数据跨两个存储字要访存两次;③ 只有 load/store 访存——其他指令都在寄存器之间操作,“访存”这道工序只有少数指令用,时间好控制。CISC 指令长短不一、有的指令一条就要访存好几次,各条指令走的工序不一样,装配线就排不齐。
- A. RISC 普遍采用微程序控制器
- B. RISC 大多数指令在一个时钟周期内完成
- C. RISC 的内部通用寄存器数量相对 CISC 多
- D. RISC 的指令数、寻址方式和指令格式种类相对 CISC 少
展开解析
ARISC 的设计目标是“简单、快”:指令少而规整,控制信号容易直接用组合逻辑产生,所以多用硬布线控制器;微程序控制器灵活但慢,适合指令多而复杂的 CISC。
- A 错“RISC 普遍采用微程序控制器”错误——本题选它。
- B 对RISC 以流水线方式执行,大多数指令平均一个时钟周期完成一条。
- C 对RISC 只让 load/store 访存,运算都在寄存器间进行,所以通用寄存器多。
- D 对RISC 指令数、寻址方式、格式种类都比 CISC 少。
RISC:定长、规整、load/store、寄存器多、硬布线、易流水;CISC:变长、复杂、指令可直接访存、多用微程序。
RISC 到底多整齐:RISC-V 的三种基本格式 拓展
问题引入:上表说 RISC“定长、格式少、字段位置固定”,到底有多整齐?看一眼 RISC-V(一种开源的 RISC 指令集;2014-44、2024-43 那种 lw / sw 风格的代码就是这一类)。
| 指令 | 格式 | 字段 | 机器码 |
|---|---|---|---|
| add x5, x6, x7 | R | funct7 = 0,rs2 = 7,rs1 = 6,funct3 = 000,rd = 5,opcode = 0110011 | 007302B3H |
| addi x5, x6, -1 | I | imm = FFFH(−1 的 12 位补码),rs1 = 6,funct3 = 000,rd = 5,opcode = 0010011 | FFF30293H |
| lw x5, 8(x6) | I | imm = 8,rs1 = 6,funct3 = 010,rd = 5,opcode = 0000011 | 00832283H |
| sw x5, 8(x6) | S | imm[11:5] = 0,rs2 = 5,rs1 = 6,funct3 = 010,imm[4:0] = 01000,opcode = 0100011 | 00532423H |
机器码已用 verify/08/p03_calc.py 按字段拼接验算(并用已知编码 add a0, a1, a2 = 00C58533H 校对)。字段位置固定带来的好处:译码器还没认出“这是哪条指令”,就可以先按 rs1、rs2 的固定位置去读寄存器——第 4 篇流水线的“译码 / 读寄存器”能放在同一段里做,靠的就是这一点(2011-18 的“指令格式规整”)。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 指令长度固定
- B. 只有 Load/Store 指令能访问存储器
- C. 寻址方式多,控制器常用微程序实现
- D. 大多数指令在一个时钟周期内完成
展开解析
CCISC 指令多、格式多、寻址方式多,用微程序控制器实现比较方便。A、B、D 都是 RISC 的特点。
变化点:两道真题都问 RISC 的错误说法,这里换成问 CISC 的正确说法:把 RISC 的特点反过来就是 CISC。
- A. 指令长度一致
- B. 只有 Load/Store 指令能访问存储器
- C. 指令中寄存器字段的位置固定
- D. 指令长度可变,且运算指令可以直接对存储器操作数运算
展开解析
D指令长度可变,取指和译码的时间就不一样;运算指令直接访存,一条指令要多次访存,各段的时间就难以统一。这两点都让流水线难以均衡。
变化点:2011-18 问“有利于”的特点,这里反过来问“不利于”的。
3.5融合程序的一生(上):编译、链接、装入
现在换成操作系统的视角。你写的 hello.c 要经过下面这条流水线,才会变成内存里一个正在运行的进程:
| 步骤 | 谁来做 | 输入 → 输出 | 干了什么 |
|---|---|---|---|
| 预处理 | 预处理器(cpp) | hello.c → hello.i | 处理 # 开头的命令:把 #include 的头文件原样插进来,展开 #define 宏,删掉注释。输出仍是 C 源程序 |
| 编译 | 编译器(cc1) | hello.i → hello.s | 词法、语法、语义分析和优化,翻译成汇编语言源程序(文本) |
| 汇编 | 汇编器(as) | hello.s → hello.o | 把汇编指令逐条翻译成机器指令,得到可重定位目标文件(二进制,但地址从 0 开始,外部函数的地址还空着) |
| 链接 | 链接器(ld) | hello.o + printf.o … → hello | 把各目标模块和库函数合并成一个可执行目标文件,填好模块之间的地址引用 |
用 gcc 可以分别停在每一步:gcc -E 只预处理,gcc -S 编译到 .s,gcc -c 汇编到 .o,不加选项一路做完链接。
逻辑地址、物理地址,链接到底要做什么
- 逻辑地址(相对地址、虚拟地址):编译、链接后程序里使用的地址,每个目标模块都从 0 开始编址,链接后整个程序形成一个统一的、从 0 开始的逻辑地址空间。
- 物理地址(绝对地址):内存中真实存储单元的地址,是地址转换的最终结果。进程运行时执行指令、访问数据,最后都要落到物理地址上。
- 地址重定位(地址变换):把逻辑地址转换成物理地址。什么时候转,就是下面“三种装入方式”的区别。
- 链接器的两件主要工作 拓展:① 符号解析——把每个模块里引用的外部符号(如 printf)和它的定义对应起来;② 重定位——把各模块合并后,修改指令和数据里涉及地址的部分(各模块原来都从 0 开始,合并后要统一加上自己在整个程序里的起始位置)。所以“形成完整的逻辑地址”发生在链接阶段(2011-30)。
链接的三种时机(和装入方式画在同一条时间线上)
问题引入:printf 的代码不是你写的,它什么时候进到你的程序里?有的程序拷到别的电脑上提示“找不到 xxx.dll”,有的却能直接运行——区别就在链接发生的时机。同一条时间线上,还有另一件事也分三个时机:逻辑地址什么时候变成物理地址(下面的三种装入方式)。两组“三种”最容易记混,先画在一起:
| 链接方式 | 库模块什么时候拼进来 | 优点 | 缺点 |
|---|---|---|---|
| 静态链接 | 程序运行前,链接器把所有目标模块和库函数拼成一个完整的可执行文件,以后不再拆开 | 运行时不依赖外部库;装入后直接执行 | 每个程序各带一份库代码,文件大、占内存;库更新后要重新链接 |
| 装入时动态链接 | 装入内存时,边装入边链接(可执行文件里只记“需要哪些模块”) | 便于修改和更新各目标模块;便于多个程序共享同一模块 | 装入时要把可能用到的模块全部链接进来,哪怕这次运行根本用不上 |
| 运行时动态链接 | 执行过程中第一次用到某模块时,才把它装入内存并链接 | 用不到的模块不装入:加快装入、节省内存;便于共享和更新 | 第一次调用有额外开销;运行时可能找不到库(“缺少 xxx.dll”) |
Windows 的 .dll、Linux 的 .so 是“动态链接库”;程序用 LoadLibrary / dlopen 在运行中按需加载,就是典型的运行时动态链接。gcc -static 生成的是静态链接程序。
真题怎么考:2011-30(形成完整逻辑地址发生在链接)、2022-20(预处理 → 编译 → 汇编 → 链接)。链接和重定位的三种方式目前多出现在王道习题里,选择题的判别法就是看上图:问“库什么时候拼进来”看上一行,问“物理地址什么时候确定 / 程序能不能移动”看下一行。
- “运行时动态链接要求在装入时把所有模块都链接好”对吗?
答案
不对,那是装入时动态链接。运行时动态链接是用到哪个模块才装入、链接哪个。 - 某程序采用静态链接,它装入内存后还能被操作系统整体移到另一处吗?
答案
能不能移动取决于装入方式而不是链接方式:若用动态重定位(运行时才转换地址)就能移动,只要改重定位寄存器;若用静态重定位就不能。
装入的三种方式:物理地址是什么时候确定的
| 装入方式 | 物理地址何时确定 | 特点 |
|---|---|---|
| 绝对装入 | 编译时就写死 | 只适合单道程序 |
| 可重定位装入(静态重定位) | 装入时一次性把所有地址 + 起始地址 | 装入后不能移动;要一次分配全部内存 |
| 动态运行时装入(动态重定位) | 运行时每次访存由硬件转换 | 需要重定位寄存器;可以移动;现代系统都是这种 |
用同一个例子对比三种方式:程序里逻辑地址 100 处有一条指令 LOAD 1, 2500(把逻辑地址 2500 处的数据读进 1 号寄存器),操作系统把程序装到从物理地址 10000 开始的内存区:
| 装入方式 | 内存里 10100 处的指令长什么样 | 执行时访问的物理地址怎么来 |
|---|---|---|
| 绝对装入 | LOAD 1, 12500 | 编译时就知道程序要放在 10000,直接生成了绝对地址 12500。换个地方装就错了 |
| 静态重定位 | LOAD 1, 12500 | 编译出来是 2500,装入时由装入程序把所有地址统一加上 10000 改写进去。装好以后程序不能再搬家 |
| 动态重定位 | LOAD 1, 2500 | 内存里保持逻辑地址不变,执行这条指令时,硬件用 2500 + 重定位寄存器(10000)= 12500 去访存,同时检查 2500 < 界地址寄存器(程序长度),否则越界 |
动态重定位时,操作系统若把程序整体移到 20000 开始,只需把重定位寄存器改成 20000,程序本身一个字节都不用改。这也是为什么“进程切换时要保存/恢复重定位寄存器(或页表基址寄存器)”(第 6、8 篇)。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 编译得到的各目标模块中,地址都已是最终的逻辑地址
- B. 链接时要合并各模块并修改相对地址,形成完整的逻辑地址空间
- C. 动态运行时装入在装入时就把逻辑地址全部转换为物理地址
- D. 汇编在编译之前进行
- A. 静态链接的程序运行时还要另外装入库函数
- B. 装入时动态链接是在程序执行中用到某模块时才去链接
- C. 运行时动态链接可以加快程序的装入并节省内存
- D. 采用静态链接的程序装入后一定不能在内存中移动
展开解析
C运行时动态链接只在用到某模块时才装入并链接,用不到的不装,所以装入快、省内存。A 错:静态链接已把库拼进可执行文件。B 说的是运行时动态链接。D 错:能不能移动取决于装入方式(动态重定位就能移动),与链接方式无关。
考点:链接的三种时机。
3.6融合程序的一生(下):进程在内存里长什么样
每个进程都有一个自己独立的虚拟地址空间(比如 32 位系统是 0 ~ 4GB),它以为整台机器的内存都是它的(第 8 篇讲操作系统怎么变这个“魔术”)。这个空间按用途分成几段:
程序里的每样东西住在哪一段
| 程序里的东西 | 放在 | 说明 |
|---|---|---|
| 函数的机器指令 | 只读代码段(.text) | 只读、可执行;同一程序的多个进程可以共享 |
| 字符串常量 "hello"、const 全局常量 | 只读数据(.rodata) | 和代码段一起装在只读区域,写它会出错 |
| 已初始化的全局变量、static 变量 | 读/写数据段(.data) | 初值存在可执行文件里,装入时读进来 |
| 未初始化(或初值为 0)的全局变量、static 变量 | .bss(紧跟在 .data 后,属读写数据区) | 可执行文件里只记“要多大”,不占文件空间,装入时统一清 0 |
| malloc / new 得到的空间 | 堆 | 运行时向高地址增长;要程序员 free,否则内存泄漏 |
| 函数的局部变量(非 static)、参数、返回地址 | 用户栈 | 每次调用建一个栈帧,函数返回就失效;向低地址增长 |
| 动态链接库(如 libc 的 printf 代码) | 共享库映射区 | 多个进程共享同一份物理页 |
| PCB、页表、内核栈、内核代码 | 内核区 | 所有进程映射同一个内核区,用户态不能访问 |
32 位 Linux 的具体数字 拓展:用户空间是 0 ~ BFFF FFFFH(低 3GB),内核空间是 C000 0000H ~ FFFF FFFFH(高 1GB);可执行文件的代码段通常从 0804 8000H 开始——2014-44 里那段循环代码的地址 08048100H 就落在这里。(2017-44、2019-45 的 00401000H 是 Windows 程序常见的起始地址。)
展开解析
- (1)PCB 是操作系统管理进程的数据结构,属于内核,位于内核区。scanf 要等键盘输入(I/O),进程让出 CPU,处于阻塞态(第 6 篇)。
- (2)main 是机器指令,在只读代码段。scanf 和 printf 要读键盘、写显示器,最终通过系统调用进入内核执行设备驱动程序(第 9 篇);malloc、strlen、free 不操作设备。
- (3)ptr 是全局变量 → 读/写数据段;length 是局部变量 → 用户栈;malloc 分配的空间 → 堆。
- A. 每个进程都有自己独立的虚拟地址空间
- B. C 语言中 malloc() 函数返回的是虚拟地址
- C. 进程对数据段和代码段可以有不同的访问权限
- D. 虚拟地址的大小由主存和硬盘的大小决定
展开解析
D虚拟地址空间有多大,只取决于虚拟地址有几位(32 位 → 4GB),与实际装了多大的内存和硬盘无关。
- A 对每个进程都有自己独立的虚拟地址空间,靠各自的页表映射。
- B 对用户程序看到、用到的地址都是虚拟地址,malloc() 返回的也是。
- C 对代码段只读可执行、数据段可读写,靠页表项中的权限位区分。
- D 错“虚拟地址的大小由主存和硬盘的大小决定”错误——本题选它。
虚拟地址空间大小 = 2虚拟地址位数;实际能用多少还受内存+外存限制,但这是“虚存的实际容量”,不是地址空间大小(同 2012-25)。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 全局变量存放在栈区
- B. 函数的局部变量存放在堆区
- C. malloc 分配的空间在堆区,函数的局部变量在栈区
- D. 代码段一般可读可写
- 2010-43:指令 OP 4 位、Ms 3、Rs 3、Md 3、Rd 3;add 的 OP = 0010,寄存器间接 = 001,寄存器间接自增 = 010;R4 = 100、R5 = 101。写出 add (R4), (R5)+ 的机器码。若 (R4)=1234H、(R5)=5678H、[1234H]=5678H、[5678H]=1234H,执行后哪些内容变了?
答案
0010 001 100 010 101 = 0010 0011 0001 0101 = 2315H。源操作数 ((R4)) = [1234H] = 5678H,目的 ((R5)) = [5678H] = 1234H,和 = 68ACH 写回 [5678H];然后 R5 自增 → 5679H。变化:[5678H] = 68ACH,R5 = 5679H(PC 也会顺序加 1)。 - 2023-44:第 2 条 jmp 在 00401079H,机器码 EB 09(2 字节),跳到哪里?第 19 条指令 C7 84 82 00 20 42 00 0A 00 00 00 在 004010AEH,第 20 条地址是多少?
答案
00401079H + 2 + 09H = 00401084H(相对寻址);第 19 条长 11 字节 → 004010AEH + 0BH = 004010B9H。机器码里的 00 20 42 00 表示 00422000H,也说明是小端。 - 静态重定位和动态重定位,哪个允许程序装入后在内存里移动?为什么?
答案
动态重定位。它在每次访存时才加重定位寄存器,程序移动后只要改寄存器的值;静态重定位装入时已把地址改死。 - 全局变量、局部变量、malloc 出来的空间、函数的机器指令分别在进程地址空间哪一段?
答案
读写数据段;栈;堆;只读代码段。 - 32 位定长指令、操作码 8 位的二地址指令,每个地址码都是主存地址,直接寻址时每个操作数能访问多大范围?执行一条这样的加法指令要访存几次?
答案
每个地址码 (32 − 8)/2 = 12 位 → 212 = 4K 个单元;访存 4 次(取指 1、取两个操作数 2、写回结果 1)。 - 堆栈向低地址增长,SP 指向栈顶元素,SP = 2000H。连续 push 两个 4 字节数据后 SP = ?再 pop 一次呢?
答案
push 先减后写:1FFCH、1FF8H → SP = 1FF8H;pop 先读后加 → SP = 1FFCH。 - AT&T 格式
movl 8(%ebp), %eax写成 Intel 格式是什么?它和lea eax, [ebp+8]有什么不同?答案
mov eax, dword ptr [ebp+8]:到内存 ebp+8 处取 4 字节(第一个参数)送 eax。lea 只把地址 ebp+8 本身送 eax,不访存。 - 全局变量
char *s = "abc";和static int n;(未初始化):s、"abc"、n 分别在哪一段?答案
s 是已初始化的全局指针 → 读写数据段(.data);字符串 "abc" → 只读数据(.rodata);n → .bss(装入时清 0)。
3.✎本篇真题闯关(29 题)
本篇各小节挂的真题全部在这里,按小节排好。可以展开后一道道做,也可以用弹窗模式连续做。做题记录(对/错)只保存在你这台电脑的浏览器里,小节标题下的题号会随之变绿或变红。
CPU 内部:数据通路、控制器与流水线
第 1 篇说 CPU“取指—译码—执行”,这一篇打开 CPU 看它具体怎么做到:数据在寄存器之间走哪条路(数据通路)、每一步谁发出什么命令(控制信号、控制器)、怎么让多条指令像流水线一样重叠执行。中断机制放到第 5 篇和操作系统一起讲。
4.1几种“周期”和几种 CPU
先看全貌:CPU 的功能和基本组成
CPU = 运算器 + 控制器。它要完成五件事:
| 功能 | 意思 |
|---|---|
| 指令控制 | 按程序规定的顺序取指令、分析指令(程序的顺序控制,靠 PC) |
| 操作控制 | 一条指令要让很多部件动作,CPU 产生相应的控制信号送到各部件 |
| 时间控制 | 各种操作按严格的时间顺序进行(时序系统、节拍) |
| 数据加工 | 对数据做算术、逻辑运算 |
| 中断处理 | 对运行中出现的异常情况和外部请求作出处理(第 5 篇) |
运算器(数据加工)
ALU;暂存寄存器(给 ALU 暂存从总线来的操作数,程序员不可见);累加器 ACC;通用寄存器组;程序状态字寄存器 PSW(标志位);移位器;计数器(控制乘除运算的步数)。控制器(指挥协调)
PC;IR;指令译码器(对操作码译码);MAR、MDR;时序系统(产生时钟、节拍等时序信号);微操作信号发生器(根据 IR、PSW、时序信号产生控制信号,就是 4.3 节的硬布线电路或微程序部件)。哪些寄存器程序员可见,见第 1 篇 1.2 节的表。考试中把 MAR、MDR 算在控制器里(王道的分法),也有教材把它们单独列出,不影响做题。
时钟周期、机器周期、指令周期
时钟周期像节拍器的“嘀嗒”一下,是 CPU 做一个最小动作(比如把一个寄存器的内容送上总线)的时间。一条指令要若干个“嘀嗒”才能完成,这些嘀嗒合起来叫指令周期。
- 时钟周期 = 1/主频,CPU 最小时间单位,长度取决于相邻寄存器之间组合逻辑电路的最大延迟(2019-16)。
- 指令周期:取出并执行一条指令的全部时间,可分为取指周期(取指令)、间址周期(取有效地址,间接寻址才有)、执行周期(取操作数、运算)、中断周期(有中断请求时,保存断点等,第 5 篇)。不同指令的指令周期长短不同;“处理器每来一个时钟脉冲就开始一条新指令”是错的。
- 机器周期(CPU 周期):有的教材在两者之间再分一层——完成一次“基本操作”(如一次访存)的时间,一个机器周期含若干时钟周期。机器周期通常以访问一次主存的时间(存取周期)为基准,因为访存是最慢的基本操作。
三者的关系:一个指令周期 = 若干个机器周期,一个机器周期 = 若干个时钟周期(节拍)。不同指令的指令周期所含的机器周期数可以不同;同一台机器里各机器周期所含的时钟周期数也可以不同(见 4.3 节“控制方式”里的“不定长机器周期”)。
四个机器周期里,数据各怎么流(教材的标准写法)
CPU 用四个标志触发器 FE、IND、EX、INT 记住“现在处在哪个机器周期”(哪个为 1 就在哪个周期),据此决定这一拍发出哪些控制信号。四个周期的数据流是固定套路,大题会直接让你写(记号见第 1 篇 1.3 节;1→R 表示发读命令,1→W 表示发写命令):
| 周期 | 目的 | 数据流(按先后顺序) |
|---|---|---|
| 取指周期 FE | 按 PC 取出指令,PC 指向下一条 | (PC)→MAR;1→R;M(MAR)→MDR;(MDR)→IR;OP(IR)→CU;(PC)+1→PC |
| 间址周期 IND | 取出操作数的有效地址(只有间接寻址的指令才有) | Ad(IR)→MAR;1→R;M(MAR)→MDR;(MDR)→Ad(IR) 把读出的有效地址替换 IR 里的形式地址(也可以直接送 MAR),执行周期再按它取操作数 |
| 执行周期 EX | 完成指令规定的操作 | 因指令而异:LOAD/STORE/ADD 见第 1 篇 1.3;无条件转移 JMP X 只需 Ad(IR)→PC;条件转移根据标志决定是否 Ad(IR)→PC |
| 中断周期 INT | 保存断点,转去中断服务程序 | (SP)−1→SP,(SP)→MAR(断点存入栈;或把约定的固定地址送 MAR);1→W;(PC)→MDR;(MDR)→M(MAR);中断服务程序入口地址→PC;0→EINT(关中断) 这些都是硬件自动完成的,称为“中断隐指令”(第 5 篇 5.4) |
① 取指周期和执行周期每条指令都有;间址周期只有间接寻址的指令才有;中断周期只在“一条指令执行完、有中断请求且允许响应”时才有。② 中断周期里保存的断点是 PC 的当前值——此时 PC 已经指向下一条指令,所以中断返回后接着执行的正是被打断指令的下一条。③ 不同指令的指令周期长度不同:有的教材指出,像 JMP X 这种执行阶段只需 Ad(IR)→PC 的指令,可以在取指周期末尾就完成,指令周期只含一个取指周期;乘法指令的执行周期比加法长;间接寻址的指令多一个间址周期。④ CPU 区分取出的是指令还是数据,靠的就是“当前处在哪个周期”(2009-11)。
三种指令执行方案(三种 CPU)
| CPU 类型 | 做法 | CPI | 时钟周期 |
|---|---|---|---|
| 单周期 CPU | 每条指令一个(很长的)时钟周期做完 | = 1 | 以最慢的指令为准,所以很长 → 主频低;控制信号在整个指令周期内不变;不能用单总线(一个周期里要同时传多个数据)(2016-20) |
| 多周期 CPU | 一条指令拆成几步,每步一个时钟周期 | > 1 | 以最慢的一步为准 |
| 基本流水线 CPU | 多条指令的不同步骤重叠 | 理想 = 1 | 以最慢的流水段为准 |
| 超标量流水线 | 每个周期发射多条指令 | 理想 < 1 |
2020-17:理想情况下 CPI 为 1 的是单周期 CPU 和基本流水线 CPU(选 B);2026-20:单周期 CPI = 1、多周期 CPI > 1、流水线 CPI = 1 三句都对(选 D);2025-18:程序的 CPI 与 Cache 缺失率有关(缺失要等主存,访存指令变慢)。
再补两个对比:① 单周期 CPU 的控制信号只由操作码决定,整条指令期间不变,控制器是纯组合逻辑;多周期 CPU 的控制器还要记住“现在走到第几步”(相当于一个状态机)。所以单周期 CPU 的控制器更简单(2025-19 C 是对的)。② 单周期 CPU 的每个部件在一条指令里只能用一次(例如取指和读数据要用两个存储器、PC+4 和 ALU 运算要用两个加法器),多周期 CPU 可以在不同步骤复用同一个部件。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 每条指令都需要相同的时钟周期数
- B. 同一个功能部件可以在一条指令的不同时钟周期中重复使用
- C. 所有指令的 CPI 都等于 1
- D. 时钟周期由最慢的一条指令决定
展开解析
B多周期 CPU 把一条指令分成几个时钟周期完成,不同周期可以复用同一个部件(例如 ALU 既算 PC + 4 又做运算),所以可以用单总线。A、C 错:不同指令的周期数不同,CPI > 1。D 是单周期 CPU 的特点。
变化点:2016-20 考单周期 CPU(不能用单总线)。这里换成多周期 CPU,两者的特点正好相反。
- A. 单周期 CPU
- B. 多周期 CPU
- C. 5 段基本流水线 CPU
- D. 超标量流水线 CPU
展开解析
D超标量 CPU 每个时钟周期能发射、完成多条指令,CPI 可以小于 1。单周期 CPU 和理想的基本流水线的 CPI 为 1,多周期 CPU 的 CPI 大于 1。
变化点:2020-17 问 CPI 为 1 的(单周期、基本流水线),这里问小于 1 的。
4.2数据通路:数据在 CPU 里走的路
数据通路是指令执行时数据流经的部件和路径:ALU、寄存器、多路选择器、总线,也包括 Cache、MMU、异常检测逻辑等(2021-18:说数据通路“不包含异常检测电路”是错的);不包括产生控制信号的控制部件(2017-19)。数据通路里的元件分两类:
组合逻辑元件(操作元件)
输出只取决于当前输入,没有记忆:ALU、多路选择器 MUX、译码器、加法器、三态门。时序逻辑元件(状态元件)
能存东西,在时钟控制下改变:PC、IR、通用寄存器组、MAR、MDR、暂存器。2023-18 选“ALU、MUX”。408 最常考的是单总线结构:CPU 内部只有一条总线,所有寄存器挂在上面,同一时刻总线上只能有一个数据。每个寄存器有两个开关:Xin(总线上的数打进 X)和 Xout(X 的内容送上总线,用三态门控制,不送的时候断开,避免多个部件同时往总线上送数)。
Xin 为 1 时总线上的数打入 X,Xout 为 1 时 X 送上总线。MDR 有两套开关:MDRin/MDRout 连内总线,MDRinE/MDRoutE 连外部数据总线。ALU 一端固定接暂存器 A,另一端接内总线;结果先放进 AC。MAR 的输出一直连在地址总线上。写微操作序列的方法
数据要从 X 到 Y,就在同一个时钟周期令 Xout=1, Yin=1。读内存 = “地址送 MAR → 读 → 数据进 MDR”;写内存 = “地址送 MAR,数据送 MDR → 写”。单总线每个周期只能传一个数,这是排节拍的唯一约束。取指令阶段是所有指令共用的:
| 节拍 | 功能 | 有效控制信号 |
|---|---|---|
| C1 | MAR ← (PC) | PCout, MARin |
| C2 | MDR ← M(MAR),PC ← (PC)+1 | MemR, MDRinE, PC+1 |
| C3 | IR ← (MDR) | MDRout, IRin |
| C4 | 指令译码 | 无 |
单总线里四类基本操作的写法
任何指令的执行阶段,都是下面四类动作的组合。以一个 ALU 输入端接暂存器 Y、输出端接暂存器 Z 的单总线 CPU 为例(2022-43 就是这种结构):
| 操作类型 | 例子 | 节拍与控制信号 |
|---|---|---|
| 寄存器 → 寄存器 | (PC) → MAR | 1 拍:PCout, MARin |
| 主存 → CPU(读) | M((R1)) → R2 | ① R1out, MARin ② MemR, MDRinE(等主存读出) ③ MDRout, R2in |
| CPU → 主存(写) | (R2) → M((R1)) | ① R1out, MARin ② R2out, MDRin ③ MDRoutE, MemW |
| 算术逻辑运算 | (R1) + (R2) → R3 | ① R1out, Yin(一个操作数先进 Y) ② R2out, ADD, Zin(另一个走总线,结果进 Z) ③ Zout, R3in |
① 同一拍里只能有一个 Xout——总线上同时只能有一个数;② 同一拍里可以有多个 Yin——同一个数可以同时打进好几个寄存器(例如 PCout 时 MARin 和 Yin 可以同时有效);③ 不走内部总线的动作可以和总线传送并行:读写主存走的是外部数据总线,PC+1 是 PC 自己的计数功能,它们都可以和另一个经内部总线的传送安排在同一拍。2009-44 的解析里“省一个节拍”的做法、取指 C2 同时做 PC+1,都是用的第 ③ 条。
再用图 4-2 的数据通路练两条(先自己写,再对答案):
| 指令(执行阶段) | 节拍:功能 / 有效控制信号 |
|---|---|
| LOAD R0, (R1) ((R1)) → R0 | C5:MAR ← (R1) / R1out, MARin C6:MDR ← M(MAR) / MemR, MDRinE C7:R0 ← (MDR) / MDRout, R0in |
| STORE (R1), R0 (R0) → M((R1)) | C5:MAR ← (R1) / R1out, MARin C6:MDR ← (R0) / R0out, MDRin C7:M(MAR) ← (MDR) / MDRoutE, MemW |
ADD (R1), R0(功能:(R0) + ((R1)) → (R1),即 R0 的数与 R1 所指内存单元的数相加,结果存回 R1 所指的内存单元)执行阶段每个节拍的功能和有效控制信号。展开解析
先想清楚要做哪几件事:① 按 R1 的内容读内存得到操作数;② 和 R0 相加;③ 结果写回同一个内存地址。再按“单总线每拍一个数”排节拍:
| 节拍 | 功能 | 有效控制信号 | 说明 |
|---|---|---|---|
| C5 | MAR ← (R1) | R1out, MARin | 要读的地址送 MAR |
| C6 | MDR ← M(MAR) | MemR, MDRinE | 读出操作数 |
| C7 | A ← (MDR) | MDRout, Ain | ALU 一个输入必须先放进 A |
| C8 | AC ← (A) + (R0) | R0out, ADD, ACin | R0 经总线进 ALU 另一端 |
| C9 | MDR ← (AC) | ACout, MDRin | 结果送 MDR 准备写 |
| C10 | M(MAR) ← (MDR) | MDRoutE, MemW | MAR 里还是 (R1),直接写 |
答案不唯一:C6 读内存时内总线是空闲的,可以同时做 A ← (R0)(R0out, Ain),C7 再 AC ← (MDR)+(A),省一个节拍。
展开解析
- Y、Z:单总线每时刻只有一个数据,而 ALU 有两个输入、一个输出。先把一个操作数放进 Y,下一拍另一个操作数走总线;ALU 的结果此时不能马上上总线(总线正被占用),先存进 Z。
- 寄存器数:4 位编号 → 24 = 16 个;rs、rd 来自指令寄存器 IR(指令里的寄存器字段);rd 选择“写哪个寄存器”,要把编号变成一根选中信号 → 接地址译码器(rs 选“读哪个”,接多路选择器)。
- 取指:① PCout, MARin(1 周期)② Read(读主存到 MDR,5 周期)③ MDRout, IRin(1 周期),共 7 个时钟周期。
- 控制部件 CU 产生控制信号;IR(操作码)和标志寄存器 FR(条件转移要看标志)的输出送到 CU。
寄存器堆:两个读口、一个写口(组合读、时序写)
问题引入:2022-43(3)问“GPRs 内有一个地址译码器和一个多路选择器,rd 连哪个?”;2023-18 问“通用寄存器组属于操作元件还是状态元件”。要答好这两问,得先把寄存器堆(也叫寄存器文件、通用寄存器组 GPRs)拆开看一看。
寄存器堆像一排带玻璃门的储物柜:看柜子里有什么(读)不用开锁,报个柜号就能隔着玻璃看到,而且两个人可以同时看两个柜子;往里放东西(写)必须等管理员摇铃(时钟边沿)、而且你手里有“写入许可”(写使能)才行。不像的地方:真实电路里“看”也要经过多路选择器,有一点点延迟,只是不需要等时钟。
教材说法:寄存器堆由若干个寄存器组成,常见的是两个读端口、一个写端口:读端口给出寄存器编号(rs、rt),经多路选择器直接输出对应寄存器的内容——这是组合逻辑,不需要时钟;写端口给出编号 rd,经译码器选中一个寄存器,在写使能 RegWr 有效时、于时钟边沿把写入数据打进去——这是时序逻辑。整个寄存器堆能“存东西”,所以归类为状态元件(时序逻辑元件,2023-18)。
跟我做一遍:
- 数一数部件:16 个寄存器 → 编号 4 位;两个读口各要一个 16 选 1 的 MUX;一个写口要一个 4-16 译码器。若寄存器有 32 个,就是 5 位编号、32 选 1 MUX × 2、5-32 译码器 × 1。
- 单周期 CPU 执行
add R1, R1, R2:一个时钟周期内,读口立即读出旧的 R1 和 R2 → ALU 算出和 → 写口在周期结束的时钟边沿才把和写回 R1。 - 为什么写入必须等时钟边沿:如果写入也像读一样“随时生效”,新 R1 会立刻又从读口出来进 ALU,再加一次 R2、再写回……一个周期里反复相加,结果完全不可控。状态元件“只在时钟边沿更新”,正是为了把每个周期隔开(这也是 2019-16 C“时钟周期按组合逻辑最大延迟定”的原因)。
| 组合逻辑元件(操作元件) | 时序逻辑元件(状态元件) | |
|---|---|---|
| 输出由什么决定 | 只由当前输入决定,没有记忆 | 由存储的状态决定,状态只在时钟边沿(且写使能有效)时更新 |
| 要不要时钟 | 不要 | 要 |
| 例子 | ALU、加法器、多路选择器 MUX、译码器、符号扩展部件、三态门 | PC、IR、通用寄存器组、MAR、MDR、暂存器、标志寄存器、存储器 |
真题怎么考:2023-18(ALU、MUX 是操作元件;PC、GPRs 是状态元件)、2021-18 和 2017-19(数据通路由组合逻辑和时序逻辑组成,不包含控制部件,但包含异常检测电路)、2022-43(3)(rd 接译码器、rs 接多路选择器)、2025-19 A(PC 一般单独设置,说通用寄存器组“应该包含”PC 是错的)。判别法:问“它能不能存住一个值”——能存就是状态元件。易错:寄存器堆的“读”是组合逻辑,但寄存器堆整体仍算状态元件。
- 32 个通用寄存器、两读一写的寄存器堆,需要几个多路选择器、几选一?写口用几线-几线译码器?
答案
两个 32 选 1 的多路选择器(每个读口一个);一个 5-32 译码器(写口)。 - 符号扩展部件、标志寄存器分别属于操作元件还是状态元件?
答案
符号扩展部件只是把位“复制”出来,没有记忆 → 操作元件;标志寄存器保存运算结果的特征 → 状态元件。
单总线之外:专用数据通路
单总线便宜,但一个周期只能传一个数,一条指令要好几拍。另一种做法是在部件之间拉专用连线(用多路选择器 MUX 选择数据来源),一个周期里多个数据可以同时各走各的路——单周期 CPU 和流水线 CPU 都是这样的(所以 2016-20“单周期处理器可以采用单总线”是错的)。2024-43 的图就是专用通路的一小块:ALU 的 B 端前面放一个 MUX,由 ALUBsrc 选“寄存器 rs2”还是“扩展后的立即数”。
| CPU 内部单总线 | CPU 内部多总线(双总线、三总线) | 专用数据通路 | |
|---|---|---|---|
| 连接方式 | 所有寄存器挂在一条总线上,Xin/Xout 控制 | 寄存器同时连到两三条总线上。例如三总线:两条源总线分别送 ALU 的两个输入,一条结果总线送回寄存器 | 部件之间直接连线,MUX 选择来源 |
| 一个周期能传几个数 | 1 个 | 2~3 个(三总线一拍就能做完 (R1)+(R2)→R3) | 多个同时传 |
| 需要暂存器 | 要(ALU 一端的 A/Y/T,输出端的 AC/Z) | 基本不要 | 不需要 |
| 优缺点 | 结构简单、成本低;总线冲突多、效率低 | 折中 | 没有冲突、性能高;连线多、成本高 |
| 用在 | 多周期 CPU、教学模型机(2009、2015、2022 大题) | —— | 单周期、流水线 CPU(2024-43、2026-44) |
下面这张图是一个简化的单周期专用数据通路(只画了运算、取数、存数三类指令要用的部分,转移指令的通路略去)。2024-43、2026-44 的图都是它的一部分,认清每个多路选择器(MUX)和控制信号在选什么,这类题就不难:
| 指令 | ALUBsrc | ExtOp | ALUctr | MemWr | MemtoReg | RegWr |
|---|---|---|---|---|---|---|
| add rd, rs1, rs2 | 0(rs2) | × | 加 | 0 | 0(ALU 结果) | 1 |
| addi rd, rs1, imm | 1(imm) | 符号扩展 | 加 | 0 | 0 | 1 |
| lw rd, imm(rs1) | 1(imm) | 符号扩展 | 加(算地址) | 0 | 1(读出的数据) | 1 |
| sw rs2, imm(rs1) | 1(imm) | 符号扩展 | 加(算地址) | 1 | × | 0 |
“×”表示取什么都行(don't care):add 用不到立即数,所以扩展器输出什么都不影响结果;sw 不写寄存器,所以 MemtoReg 选哪路都无所谓。2024-43 第 3 问“slli 的 Ext 取 0 或 1 都行”也是这个道理——slli 的 12 位立即数字段是“7 个 0 + 5 位 shamt”,最高位一定是 0,零扩展和符号扩展都在高位补 0,结果一样。真题里信号名可能不同(如 RegWsrc、MemtoReg),看图认准“这个 MUX 在选什么”即可。
另外两个常考判断:① PC 是专门的寄存器,不在通用寄存器组里(x86、MIPS 都如此;2025-19 A 错)。② 看一条指令在“取数和执行阶段”用到哪些部件(2019-17):R[r2] ← R[r1] + M[R[r0]] 要读寄存器(GPRs)、按 R[r0] 读存储器、用 ALU 相加;指令译码器只在译码阶段用,不算。
同类大题:2015-43(单总线加暂存器 T 的原因同上;ALU 7 种操作 → ALUop 至少 3 位,移位器 3 种 → SRop 2 位;SRout 控制的是三态门;MUX 一端接常数 2 是为了 PC+2,因为 16 位指令、按字节编址);2015-44(写出 inc R1、shl R2,R1、sub R3,(R1),R2 的机器码 0240H、0488H、06EAH);2024-43、2026-44(多路选择器、扩展器 Ext 的控制信号取值)。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
Ⅰ. ALU Ⅱ. 程序计数器 PC Ⅲ. 通用寄存器组 Ⅳ. 多路选择器 Ⅴ. 指令寄存器 IR
- A. 仅Ⅰ、Ⅳ
- B. 仅Ⅱ、Ⅲ
- C. 仅Ⅱ、Ⅲ、Ⅴ
- D. 仅Ⅰ、Ⅱ、Ⅲ
展开解析
C能保存信息的是状态元件:PC、通用寄存器组、IR(还有存储器)。ALU、多路选择器、译码器、加法器的输出只取决于当前输入,是操作元件。
变化点:2023-18 问操作元件(答 Ⅰ、Ⅳ),这里问状态元件,还多了一个 IR。判断标准:有没有存储功能,需不需要时钟打入。
- A. 控制器属于数据通路的一部分
- B. 程序计数器 PC 是数据通路中的状态元件
- C. 数据通路中只包含组合逻辑元件
- D. 单周期 CPU 中,同一个功能部件在一条指令内可以使用多次
- A. 每个读端口用一个 5-32 译码器选择要读的寄存器
- B. 写端口用 5-32 译码器选中被写的寄存器,写入在写使能有效时于时钟边沿完成
- C. 寄存器堆属于组合逻辑元件
- D. 读出寄存器的内容必须等到时钟边沿
展开解析
B写口:编号经译码器选中一个寄存器,在 RegWr 有效时于时钟边沿写入(时序逻辑)。A 错:读口用 32 选 1 的多路选择器“选一个输出”。C 错:寄存器堆能保存数据,是状态(时序)元件。D 错:读是组合逻辑,编号一给数据就出来,不用等时钟。
考点:寄存器堆:组合读、时序写。
4.3控制器:谁来发出这些控制信号
上一节的表格里,每个节拍要让哪些信号为 1(PCout、MARin、MemR……),是我们手写出来的。CPU 里负责“在正确的时刻让正确的信号为 1”的部件就是控制器(CU)。它要根据三样东西决定输出:
- 现在是什么指令——IR 里的操作码(ADD 和 LOAD 要的信号不一样);
- 现在是第几拍——节拍发生器给出的时序(同一条指令,C5 和 C6 要的信号不一样);
- 运算结果的状态——标志寄存器(条件转移指令要看 ZF 等才知道改不改 PC)。
所以 控制信号 = f(操作码,节拍,状态)。实现这个 f 有两种思路:一种是用门电路把 f 直接“焊”出来(硬布线);另一种是把每一拍该发哪些信号事先写成一张表存起来,执行时一行一行地读(微程序)。
时序系统和控制方式
控制信号不仅要“对”,还要在“对的时刻”发出。CPU 用三级时序来划分时间:机器周期(取指、间址、执行、中断,由 FE/IND/EX/INT 标出)→ 每个机器周期分成若干节拍(T0、T1、T2……,每拍一个时钟周期)→ 每拍里再用时钟脉冲的边沿把结果打入寄存器。时序怎么安排,有三种控制方式:
| 控制方式 | 做法 | 特点 |
|---|---|---|
| 同步控制 | 所有操作都由统一的时钟控制,每个机器周期的节拍数事先定好。又分:定长机器周期(按最复杂的操作定节拍数,简单操作也得等够)、不定长机器周期(不同机器周期节拍数不同)、中央控制与局部控制结合(大多数操作用固定节拍,少数复杂操作如乘除临时延长) | 控制简单;但时间按最坏情况安排,有浪费 |
| 异步控制 | 没有统一的节拍,每个操作需要多少时间就占多少时间,部件之间靠“请求—应答”信号联系 | 时间利用充分;控制复杂 |
| 联合控制 | 同步与异步结合:CPU 内部大部分用同步,与速度差别大的主存、I/O 交换数据时用异步应答 | 现代计算机多用 |
硬布线控制器:把控制信号“焊”出来
硬布线控制器(组合逻辑控制器)的每个控制信号,都是一个组合逻辑表达式的输出,输入是:指令译码器的输出(现在是哪条指令)、机器周期标志和节拍(现在是哪一步)、标志位(运算结果状态)以及外部请求(中断、DMA)。设计步骤是:
- 分析每个阶段的微操作序列:把每条指令在取指、间址、执行、中断各周期里要做的动作列出来(就是上一节写的那些表)。
- 选择控制方式:同步、异步还是联合,定长还是不定长机器周期。
- 安排微操作时序:把微操作排进节拍。原则:有先后依赖的必须按顺序;被控对象不同、互不冲突的微操作尽量放在同一拍;占用时间很短的几个操作可以放在同一拍的前后(如 MDR 读出后立即送 IR)。
- 电路设计:列出操作时间表(每个微操作在哪个周期、哪一拍、被哪些指令用到)→ 写出每个微命令的最简逻辑表达式 → 画出逻辑图。
举个表达式的例子:“M(MAR) → MDR”(读主存)这个微命令,在取指周期的 T1 所有指令都要用;在间址周期的 T1 只有带间接寻址的访存/转移类指令要用;在执行周期的 T1 只有 ADD、LDA(取数)等需要读操作数的指令要用。于是它的表达式形如:
式中的指令名是教材模型机里的:ADD 加法、STA 存数、LDA 取数、JMP 无条件转移、BAN 结果为负则转移。间址周期里这五条指令只要用了间接寻址,都要先读主存取有效地址;执行周期里只有 ADD、LDA 要读操作数(STA 是写,JMP、BAN 只改 PC)。每一项读作“在哪个周期 · 哪一拍 · 哪些指令”。所有微命令的表达式合起来就是整个控制器的电路。可见指令越多、越复杂,表达式就越乱——这就是硬布线控制器设计复杂、难以修改扩充的原因;但它用门电路直接产生信号,速度快,所以 RISC 多用它(2009-19)。
微程序控制器的几个名词(按从小到大)
| 名词 | 意思 | 对应上一节的例子 |
|---|---|---|
| 微命令 / 微操作 | 一个控制信号(微命令)让一个部件做的一个最小动作(微操作) | PCout = 1 → “PC 送总线” |
| 微指令 | 一个节拍里要同时发出的那一组微命令,再加上“下一条微指令在哪”的信息 | C1 那一行:{PCout, MARin} |
| 微周期 | 读出并执行一条微指令的时间,一般 = 一个时钟周期 | 一个节拍 |
| 微程序 | 一条机器指令对应的一串微指令 | ADD (R1),R0 的 C5~C10 |
| 控制存储器 CS | 放所有微程序的 ROM,在 CPU 里;用微地址(CMAR)去读,读出放进 CMDR |
取指令那几拍(C1~C3)所有指令都一样,所以写成一段公共的取指微程序;取指完成后,由操作码找到这条指令自己的微程序入口,接着执行。
微程序控制器的组成和工作过程
- 开机或一条指令执行完,CMAR 指向公共取指微程序的入口,逐条执行它,把机器指令从主存取到 IR。
- IR 的操作码送微地址形成部件,得到这条指令对应微程序的入口地址,送入 CMAR。
- 从 CM 逐条读出这段微程序的微指令到 CMDR:操作控制字段发出这一拍的微命令;顺序控制字段(配合标志位)决定下一条微指令的地址。
- 这段微程序的最后一条微指令让 CMAR 回到取指微程序入口,开始取下一条机器指令(有中断请求时先转去执行中断周期的微程序)。
① 微程序个数:每条机器指令对应一段微程序,再加上公共的取指微程序;若有间址周期、中断周期,它们也各对应一段公共微程序。所以 n 条机器指令 → 控存里至少有 n + 1 段微程序。
② 微指令总条数 = 各指令微程序的微指令数之和 + 公共微程序的微指令数(2014-18:32×4 + 2 = 130)。
③ 控存容量 = 微指令条数 × 微指令字长;用断定法时,下地址字段 k 位,控存按 2k 个单元设计。
④ 对应关系:程序 ↔ 机器指令序列,微程序 ↔ 微指令序列;一条机器指令对应一段微程序,一条微指令包含若干微命令。
| 微指令格式考点 | 方法 | 真题 |
|---|---|---|
| 操作控制字段:直接编码 | 每个微命令占 1 位,不用译码,最快但最长 | |
| 字段直接编码 | 互斥的微命令放同一字段,字段内编码;每个字段要多留一个编码表示“什么都不发”:含 k 个微命令的字段需要 ⌈log2(k+1)⌉ 位 | 2012-18:7、3、12、5、6 → 3+2+4+3+3 = 15 位 |
| 下地址字段(断定法) | 位数 = ⌈log2(微指令总条数)⌉ | 2014-18:32 条指令×4 + 公共取指 2 = 130 条 → 8 位 |
操作控制字段的第三种编码:字段间接编码——一个字段里某些微命令的含义,还要由另一个字段来“解释”(比如字段 A 的某个编码到底选哪个寄存器,要看字段 B 的值)。它能进一步缩短微指令,但会多一层译码、变慢,只作辅助手段。三种编码对比:直接编码最快、最长;字段直接编码折中(最常用、真题考的就是它);字段间接编码最短、最慢。
| 水平型微指令 | 垂直型微指令 | |
|---|---|---|
| 一条微指令 | 定义并执行多个并行的微命令 | 类似机器指令,有“微操作码”字段,一条只能执行一两个微操作 |
| 编码 | 直接编码、字段直接编码 | 对微操作码译码 |
| 并行能力 / 速度 | 强;执行一条机器指令所需的微指令少,速度快 | 弱;要的微指令多,速度慢 |
| 微指令字长 / 微程序 | 字长长,微程序短,控存容量大 | 字长短,微程序长 |
| 编写 | 难,用户不易掌握 | 和写机器指令类似,容易 |
还有介于两者之间的混合型微指令。拓展 动态微程序设计:控存用可改写的存储器(如 EPROM),通过改写微程序来改变机器的指令系统;毫微程序:用更低一级的毫微程序去解释执行微指令。
下一条微指令的地址怎么来:① 取指微程序结束后,由机器指令的操作码经微地址形成部件得到入口地址(一般用硬件映射);② 断定法:微指令的下地址字段直接给出;③ 增量计数器法:(CMAR) + 1 → CMAR,顺序执行时用;④ 根据标志或外部条件在几个地址中选一个(微程序分支,要用微指令里的判别测试字段);⑤ 中断周期等由硬件直接产生固定的微程序入口地址。
| 硬布线控制器 | 微程序控制器 | |
|---|---|---|
| 工作原理 | 控制信号由组合逻辑电路即时产生 | 控制信号事先存在控存里,执行时读出 |
| 执行速度 | 快 | 较慢(每拍都要访问一次控存) |
| 规整性 | 繁琐、不规整 | 较规整 |
| 修改与扩充 | 困难(要改电路) | 容易(改微程序即可) |
| 适用场合 | RISC CPU | CISC CPU |
展开解析
- 操作控制字段:每个字段多留一个“不发任何微命令”的编码 → ⌈log26⌉ + ⌈log29⌉ + ⌈log214⌉ + ⌈log24⌉ = 3 + 4 + 4 + 2 = 13 位。
- 微指令总数 = 60 × 4 + 2 + 1 + 2 = 245 条 → 下地址字段 ⌈log2245⌉ = 8 位。
- 微指令字长 = 13 + 3 + 8 = 24 位。
- 控存容量:8 位微地址可寻址 28 = 256 个单元 → 按 256 × 24 位设计(实际用到 245 个单元)。
2017-18:主存 MM 在 CPU 外、存指令和数据、用 RAM+ROM;控制存储器 CS 在 CPU 内、存微指令、用 ROM;两者都按地址访问(说 CS“按内容访问”是错的)。
字段间接编码:让一个字段去“解释”另一个字段(例)
问题引入:上面一句话带过的“字段间接编码”到底怎么省位?省了位又丢了什么?用一个自己设计的小例子算一遍就清楚了。
同一个手势“竖大拇指”,在饭桌上是“好吃”,在路边是“搭车”——手势本身(字段 B)不变,场合(字段 S)决定它的意思。不像的地方:人可以同时在饭桌上又在路边吗?不行——微指令也一样,被“解释”的两组微命令不能在同一拍里同时发出,这就是代价。
设计要求(示意):微命令里有 4 个 ALU 运算(加、减、与、或,互斥)和 4 个移位操作(左移 1 位、右移 1 位、左移 2 位、右移 2 位,互斥)。
- 直接编码:8 个微命令各占 1 位 → 8 位,任意组合都能同拍发出。
- 字段直接编码:ALU 运算一个字段(4 + 1 种 → 3 位),移位一个字段(4 + 1 种 → 3 位)→ 6 位;ALU 运算和移位可以同拍(各在自己的字段里)。
- 字段间接编码:规定“移位和 ALU 运算不在同一拍做”,用 1 位 S 选择 B 的含义 → 1 + 3 = 4 位。省了 2 位,代价是加法和移位不能同拍(并行能力下降),而且多了一级与门,信号出得更慢。
| 直接编码 | 字段直接编码 | 字段间接编码 | |
|---|---|---|---|
| 本例位数 | 8 | 6 | 4 |
| 译码 | 不要 | 每个字段一个译码器 | 译码之后还要由另一字段“解释” |
| 速度 | 最快 | 较快 | 最慢 |
| 并行能力 | 最强 | 不同字段可同拍 | 被解释的几组不能同拍 |
| 地位 | 微命令多时太长 | 最常用(2012-18 考它) | 只作字段直接编码的辅助手段 |
数字已用 verify/08/p04_calc.py 验算。真题目前只考过字段直接编码的位数计算;字段间接编码考概念:“可以进一步缩短微指令字长,但削弱了并行控制能力,通常作为辅助手段”。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 6
- B. 15
- C. 19
- D. 40
展开解析
C每类要留一个“不发出任何微命令”的编码,n 个微命令要 ⌈log2(n + 1)⌉ 位:8 → 4 位,3 → 2 位,15 → 4 位,6 → 3 位,4 → 3 位,4 → 3 位,共 19 位。
变化点:2012-18 的各类大小为 7、3、12、5、6。注意 15 个微命令要 4 位(16 个编码),3 个要 2 位:看的是 n + 1。D 是直接编码(每个微命令 1 位)的位数。
- A. 7
- B. 8
- C. 9
- D. 10
展开解析
C微指令总数 = 64 × 5 + 3 + 2 + 2 = 327 条,下址字段至少 ⌈log2327⌉ = 9 位。
变化点:2014-18 是 32 × 4 + 2 = 130 条(8 位)。公共部分(取指、间址、中断)要单独加上,只算一次。
- A. 字段间接编码的并行控制能力最强
- B. 直接编码需要的译码电路最多
- C. 字段直接编码时,同一字段内的几个微命令可以在同一拍发出
- D. 字段间接编码可进一步缩短微指令字长,但会削弱并行控制能力
展开解析
D字段间接编码让一个字段去“解释”另一个字段,能省位,但被解释的几组微命令不能同拍发出,并且多一级译码、更慢,所以只作辅助手段。A 错:并行能力最强的是直接编码。B 错:直接编码不需要译码。C 错:同一字段内的微命令互斥,一拍只能发其中一个。
考点:字段间接编码与三种编码对比。
4.4指令流水线:让指令像洗衣服一样重叠
洗一筐衣服要“洗→烘→叠”三步,各 30 分钟。一个人等上一筐全弄完才洗下一筐,3 筐要 270 分钟;聪明的做法是第 1 筐进烘干机时第 2 筐就进洗衣机,3 筐只要 30×(3+2) = 150 分钟。每一筐的时间没变短,但单位时间完成的筐数(吞吐率)变大了。
408 用的是五段流水线:IF 取指 → ID 译码/读寄存器 → EX 执行/算地址 → MEM 访存 → WB 写回寄存器。不同类型的指令在各段做的事不同,有的段是“空”的(但照样要占一拍,保证每条指令流过的段数一样):
| 指令类型 | IF | ID | EX | MEM | WB |
|---|---|---|---|---|---|
| 运算类 add rd, rs, rt | 按 PC 取指令,PC + 4 | 译码,读 rs、rt | ALU 运算 | 空 | 结果写 rd |
| 取数 lw rt, imm(rs) | 读 rs,imm 符号扩展 | 算地址 rs + imm | 读数据存储器 | 数据写 rt | |
| 存数 sw rt, imm(rs) | 读 rs、rt | 算地址 rs + imm | 写数据存储器 | 空 | |
| 条件转移 beq rs, rt, L | 读 rs、rt | 比较(相减看 ZF),算转移目标地址 | 根据比较结果决定是否把目标地址送 PC(在哪一段改 PC 因设计而异,题目会说明) | ||
| 无条件转移 j L | 译码 | 把目标地址送 PC(通常越早越好) | |||
相邻两段之间有流水段寄存器(IF/ID、ID/EX、EX/MEM、MEM/WB),保存上一段算出的结果和后面要用的控制信号,让五条指令在五个段里互不干扰。所以流水线的时钟周期 = 最慢一段的时间 + 流水段寄存器的延迟(下面 2018-20);每条指令从头到尾的时间不但没缩短,还因为段寄存器延迟、各段向最慢段看齐而变长了——流水线提高的是吞吐率。
第三个指标:效率(时空图里“格子”的利用率)
- A. 60ps
- B. 70ps
- C. 80ps
- D. 100ps
展开解析
D流水线所有段共用一个时钟,每段都要在一个周期内做完,还要把结果打入流水段寄存器:时钟周期 = 最慢段 + 段寄存器延时 = 80 + 20 = 100ps。
- A 错60ps 比最慢段 80ps 还短,最慢的那一段做不完。
- B 错70ps 比最慢的 A 段(80ps)还短,A 段在一个周期内做不完;而且还要再加段寄存器延时 20ps。
- C 错80ps:只取了最慢段,忘了加流水段寄存器的 20ps(2009-18 是题目说明“忽略缓存时间”才不加)。
- D 对100ps。
流水线时钟周期 = max(各段时间) + 段间寄存器延迟。
- A. 0.25×109 条/秒
- B. 0.97×109 条/秒
- C. 1.0×109 条/秒
- D. 1.03×109 条/秒
展开解析
C吞吐率 = 完成的指令数 ÷ 所用时间;有“装满流水线”的时间,所以不是简单的“主频 ÷ 1”。
- 4 级流水、100 条指令、无阻塞:共 4 + (100 − 1) = 103 个时钟周期。
- 时间 = 103 ÷ 1.03GHz = 100ns。
- 吞吐率 = 100 条 ÷ 100ns = 1.0 × 109 条/秒。
- A 错0.25 × 109 大约是“不用流水线、每条指令 4 个周期”的速度,没有利用流水线的重叠。
- B 错0.97 × 109:计算有误(100 条 ÷ 103 个周期 ≈ 0.97 条/周期,但还要乘主频 1.03GHz)。
- C 对1.0 × 109 条/秒。
- D 错1.03 × 109 是“每个周期完成一条”的极限,忽略了前 3 个周期流水线还没装满。
n 条指令、k 段流水线:周期数 = k + n − 1;吞吐率 = n ÷ 总时间。
冒险:流水线为什么会“卡住”
| 冒险(相关) | 原因 | 解决办法 |
|---|---|---|
| 结构冒险 资源冲突 | 两条指令同一时刻要用同一个部件。典型:I1 在 MEM 访问数据时,I4 在 IF 取指令,都要用存储器 | 让后一条等(阻塞);或者指令 Cache 和数据 Cache 分离(2014-16:分离的主要目的是减少流水线资源冲突) |
| 数据冒险 数据相关 | 后一条要用前一条的结果,结果还没写回。最常考“写后读 RAW” | ① 插入气泡(阻塞);② 数据旁路/转发:把 ALU 的结果直接送给下一条的 ALU 输入;③ 编译器调整指令顺序、插 nop |
| 控制冒险 | 遇到转移指令,要到后面某段才知道下一条从哪取 | 阻塞(等分支结果);分支预测;延迟转移 |
三种冒险的对策(完整版)
| 冒险 | 对策 |
|---|---|
| 结构冒险 | ① 让后一条指令暂停一个周期再用该部件(插入气泡);② 资源重复配置:指令存储器与数据存储器分开(分离的指令 Cache、数据 Cache,2014-16),多设几个功能部件 |
| 数据冒险 | ① 硬件阻塞:插入气泡,等前一条写回再读;② 数据旁路(转发):把结果从流水段寄存器直接送到需要它的部件(2010-19:转发本身不会引起阻塞);③ 软件插空:编译器调整指令顺序,把不相关的指令插进去,或者插 nop;④ 寄存器前半周期写、后半周期读,使写回与读寄存器可以在同一拍完成,少等一拍 |
| 控制冒险 | ① 硬件阻塞:遇到转移指令就暂停取指,直到知道转不转、转到哪;② 分支预测:静态预测(事先固定“总是预测不转移”或“总是预测转移”,或由编译器按统计决定)、动态预测(根据这条分支过去的执行历史来猜,如用 1 位、2 位预测位);猜错了要把错取的指令作废;③ 尽早判断:把比较和目标地址计算提前到 ID 段,缩短阻塞;④ 两个方向的指令都预取;⑤ 延迟转移:转移指令后面的“延迟槽”由编译器放一条无论转不转都要执行的指令,让等待时间不浪费 |
数据相关的三种类型(按“两条指令对同一个寄存器先后做了什么”分):
| 类型 | 例子(按顺序执行) | 会不会出问题 |
|---|---|---|
| 写后读 RAW(真相关) | I1: add r1, r2, r3 I2: sub r4, r1, r5 | I2 要读 I1 写的 r1。按序流水线里只有它会造成数据冒险,408 考的都是它 |
| 读后写 WAR(反相关) | I1: add r1, r2, r3 I2: add r2, r6, r7 | I2 写的 r2 是 I1 要读的。按序执行时 I1 早就在 ID 读完了,没问题;乱序执行时若 I2 抢先写,I1 读错 拓展 |
| 写后写 WAW(输出相关) | I1: add r1, r2, r3 I2: mul r1, r8, r9 | 两条都写 r1,最后应当是 I2 的值。按序完成时没问题;乱序完成时可能被 I1 覆盖 拓展 |
- 对每条指令写出:写哪个寄存器、读哪些寄存器(store 指令也“读”它要存的那个寄存器;load 的地址寄存器也是“读”)。
- 后面的指令读的寄存器,恰好是前面某条(还没写回的)指令要写的 → 写后读 RAW,有数据冒险。只写同一个寄存器(WAW)或先读后写(WAR)在按序流水线里不造成问题。
- 有转发时:一般的 ALU 结果能及时转发,不阻塞;但 load 的结果要到 MEM 段末才有,紧跟着用它的指令仍要阻塞 1 拍(load-use,2024-19:“所有数据冒险都能靠转发解决”是错的)。
动手数一次:同一段代码,三种约定各要几拍
I1: add r1, r2, r3 I2: sub r4, r1, r5 ; 读 I1 写的 r1 I3: and r6, r1, r7 ; 读 r1 I4: or r8, r1, r9 ; 读 r1
“阻”表示这一拍被阻塞:按序发射时,被阻塞的指令停在原地(I2 停在 ID 等数据),后面的指令也只能停在自己的段里(I3 停在 IF)。
| ① 不转发,后一条的 ID 要在前一条 WB 的下一拍(408 常见约定,图 4-11 用的就是它) | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| 指令 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| I1 | IF | ID | EX | M | WB | ||||||
| I2 | IF | 阻 | 阻 | 阻 | ID | EX | M | WB | |||
| I3 | IF | 阻 | 阻 | 阻 | ID | EX | M | WB | |||
| I4 | IF | ID | EX | M | WB | ||||||
| ② 不转发,但寄存器“前半周期写、后半周期读”:ID 可以和前一条的 WB 在同一拍 | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| 指令 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| I1 | IF | ID | EX | M | WB | |||||
| I2 | IF | 阻 | 阻 | ID | EX | M | WB | |||
| I3 | IF | 阻 | 阻 | ID | EX | M | WB | |||
| I4 | IF | ID | EX | M | WB | |||||
③ 有转发:I1 的结果在第 3 拍 EX 结束时就有了,直接转发给 I2 第 4 拍的 EX;I3 第 5 拍 EX 时,I1 的结果正在 MEM/WB 段寄存器里,同样可以转发;I4 第 6 拍 EX 时,I1 已在第 5 拍写回,I4 在第 5 拍 ID 读寄存器,若“前半周期写、后半周期读”就能直接读到,否则也从旁路取。所以一拍都不用等,4 条指令 5 + 3 = 8 拍完成。三种约定分别是 11、10、8 拍(已用程序模拟验算)。若 I1 换成 lw r1, 0(r2),数据要到第 4 拍 MEM 结束才有,即使转发 I2 也得等 1 拍(load-use),共 9 拍。
- A. I1 和 I3
- B. I2 和 I3
- C. I2 和 I4
- D. I3 和 I4
展开解析
考点4.4 数据冒险
C列出每条指令“写哪个、读哪些”寄存器;后面的指令读前面指令要写的寄存器,就是 RAW 数据冒险。
- I1 写 s2;I2 写 s3、读 t2;I3 写 s2、读 s2 和 s3;I4 读 s2 和 t2(store 不写寄存器)。
- A 错I3 要读 s2,而 I1 刚写 s2,是 RAW 数据冒险。
- B 错I3 要读 s3,而 I2(load)刚写 s3,是 RAW 数据冒险,而且是 load-use,转发也要阻塞 1 拍。
- C 对I2 写 s3,I4 读的是 s2 和 t2,没有读 s3;两条都只读 t2,读读不冲突,无数据冒险。
- D 错I4(store)要读 s2 写进内存,而 I3 刚写 s2,是 RAW 数据冒险。
store 只读寄存器(要存的数 + 基址),load 写目的寄存器。只有“写后读”同一个寄存器才算 RAW。同类:2016-19(选“I2 和 I3”:I2 写 R5,I3 读 R5)。
- A. 仅 I3
- B. 仅 I2、I4
- C. 仅 I3、I4
- D. 仅 I2、I3、I4
展开解析
C逐对看:转发能解决“ALU 结果给下一条用”,解决不了“load 刚取的数给紧接着的下一条用”(load-use);分支用硬件阻塞,则分支后面那条要等。
- I1 → I2(s2):add 的结果在 EX 段末就有,可转发给 I2 的 EX,I2 不阻塞。
- I2 → I3(s3):load 在 MEM 段末才取到数,I3 紧跟其后要用 → load-use,转发也来不及,I3 阻塞 1 拍。
- I3 是条件分支 beq:在它算出是否转移之前,不知道下一条该取 I4 还是 L1 处的指令 → 控制冒险用硬件阻塞,I4 阻塞。
- A 错漏了 I4:题目规定控制冒险用硬件阻塞,beq 后面的 I4 必须等分支结果出来才能继续,会被阻塞。
- B 错I2 不会阻塞:它要的 s2 是 add 在 EX 段算出来的,可以直接转发;而真正要阻塞的 I3(load-use)被漏掉了。
- C 对I3(load-use 数据冒险)和 I4(控制冒险)阻塞。
- D 错多了 I2:add → load 的数据相关通过转发就能解决,不需要阻塞。
有转发时只剩 load-use 要阻塞 1 拍;控制冒险按题目说的处理方式(阻塞 / 预测 / 延迟槽)判。
展开解析
数据相关:2 读 1 写的 R4;3 读 2 写的 R4;4 读 3 写的 R5;6 读 5 写的 R2 → 2、3、4、6 阻塞。6(bne)发生控制冒险。下一轮的指令 1 读 R2 虽然依赖指令 5,但中间隔着 bne 的 3 个周期阻塞,等它开始译码时指令 5 早已写回。
展开解析
(2)5 + (4−1) = 8。(4)指令序列 I1 LOAD R1,[x];I2 LOAD R2,[a];I3 SHL R1(或 ADD R1,R1);I4 ADD R1,R2;I5 STORE R2,[x]。按图 4-11 的规则:I3 等 I1 第 5 拍写回 → 第 6 拍 ID,第 9 拍 WB;I4 等 I3 → 第 10 拍 ID,第 13 拍 WB;I5 等 I4 → 第 14 拍 ID,第 17 拍 WB。至少 17 个时钟周期。
其他选择题:2010-19 不会引起阻塞的是“数据旁路(转发)”;2011-18 指令定长规整、按边界对齐、只有 Load/Store 访存都有利于流水线;2017-17 超标量流水线能在一个周期内发射多条指令、能结合动态调度,但不能缩短每段的处理时间(选 C)。
有阻塞时,流水线的实际 CPI
理想流水线每拍完成一条指令(CPI = 1);每发生一次阻塞,就相当于多花了几拍。所以:
展开解析
- load-use 带来的阻塞:0.25 × 0.4 × 1 = 0.1 拍/条。
- (1)转移阻塞:0.2 × 2 = 0.4 拍/条 → CPI = 1 + 0.1 + 0.4 = 1.5。
- (2)只有转移成功(占转移指令的 40%)才阻塞:0.2 × 0.4 × 2 = 0.16 → CPI = 1 + 0.1 + 0.16 = 1.26。
- 主频、指令数都没变,执行时间之比 = CPI 之比:1.5 / 1.26 ≈ 1.19,即性能提高约 19%。
高级流水线技术:超标量、超流水线、超长指令字
基本流水线的 CPI 最好也只能到 1。想更快,有两条路:让每拍能同时处理多条指令(多发射),或者把流水线切得更细、让时钟更快(超流水线)。
| 技术 | 做法 | 要点 |
|---|---|---|
| 超标量 动态多发射 | CPU 里配置多套功能部件(多条流水线),每个时钟周期同时发射多条相互独立的指令;由硬件在运行时检查相关性、决定哪些指令能一起发射,可结合动态调度(乱序执行) | 理想 CPI < 1;不能缩短每段的时间(2017-17);硬件复杂 |
| 超流水线 | 把每个流水段再细分成更多、更短的段,时钟周期更短、主频更高;相当于在一个“原来的时钟周期”里流水线推进了好几次 | 段数多了,冒险造成的阻塞代价(拍数)也更大;“是否采用超流水线”属于组成,不由 ISA 规定(2026-16) |
| 超长指令字 VLIW 静态多发射 | 由编译器在编译时找出可以并行的操作,把它们拼成一条很长的指令,一次发射给多个功能部件 | 硬件简单;并行性靠编译器挖掘,代码膨胀 |
区分超标量和 VLIW 的关键:谁来决定哪些指令一起执行——超标量是硬件在运行时决定(动态),VLIW 是编译器在编译时决定(静态)。
动态流水线:指令可以“插队”执行,但必须按原顺序“交卷”
问题:上面讲的流水线都是按序的——前一条卡住,后面的全得等。设想 lw r1, 0(r2) 没命中 Cache,要等好几拍才拿到数据;紧跟着的 add r3, r1, r4 要用 r1,只能等,这没办法。可再往后的 sub r5, r6, r7 跟 r1 毫无关系,也被堵在后面白等——功能部件明明空着。能不能让它先做?
教材说法(袁春风、王道口径):
- 静态调度:硬件按程序顺序发射、执行,遇到相关就阻塞;能做的优化(调整指令顺序、插 nop)都由编译器在编译时完成。前面的五段流水线、VLIW 都属于这一类。
- 动态调度(动态流水线调度):由硬件在运行时检查指令之间的相关性,按序取指、译码,然后让操作数已经准备好的指令先执行——这叫乱序执行(out-of-order execution);执行完的结果先放进重排序缓冲(ROB)里,再按程序顺序提交(写回寄存器或存储器)。
- 为什么一定要按序提交:保证精确异常——某条指令出了异常(如缺页),它后面的指令即使已经算完,结果也还没提交,可以整体作废,中断返回后从这条指令重新执行,程序员看到的效果和按序执行一模一样(第 5 篇“故障”的返回地址就是这么来的)。分支预测猜错时,作废已执行的错误路径指令也靠它。
- 代价:乱序执行会让读后写 WAR、写后写 WAW 也变成真问题(上面的表格里标了拓展),硬件要用寄存器重命名来消除它们;控制逻辑比按序流水线复杂得多 拓展。
- 和超标量的关系:超标量说的是“每拍能发射几条”(宽度),动态调度说的是“能不能不按顺序执行”(顺序)。两者常常一起用(2017-17 Ⅲ“超标量能结合动态调度技术提高指令执行并行性”),但超标量不一定乱序,简单的超标量处理器仍按序发射。
verify/08/p13/a_dyn.py 模拟得到。跟我做一遍(按图中的模型逐拍看动态流水线):
- 第 1 拍:I1(lw)开始执行,要到第 5 拍末才拿到 r1。
- 第 2 拍:I2 已译码,但要读 r1 → 不能开始(这是真正的 RAW 相关,谁也救不了)。
- 第 3 拍:I3 译码完毕,只读 r6、r7,操作数都就绪 → 越过 I2 先执行,第 3 拍末算出 r5。
- 第 4 拍:I4 要读 r5,r5 在第 3 拍末已算出 → 执行。
- 第 6 拍:r1 到了,I2 执行。
- 提交:I1 第 6 拍提交;I2 第 7 拍;I3 虽然第 3 拍就算完,也只能排在 I2 后面,第 7 拍和 I2 一起提交;I4 第 8 拍提交。执行是乱的,提交是按序的。
- 2017-17:超标量“能在一个时钟周期内同时发射多条指令”“能结合动态调度技术提高指令执行并行性”都对;“能缩短流水线功能段的处理时间”错——每段的时间由电路决定,多发射、乱序都不改变它。
- Ⅰ/Ⅱ/Ⅲ 判断的常见说法:“动态调度由编译器完成”✗(编译器做的是静态调度);“乱序执行的处理器结果也按乱序写回寄存器”✗(按序提交);“乱序执行可以消除所有数据冒险”✗(真正的 RAW 只能等,I2 就是例子);“超标量处理器一定乱序执行”✗。
- 另一种口径(唐朔飞等教材讲“流水线分类”时用):按“同一时间内各段的连接方式”分,多功能流水线在同一时间只能按一种功能连接的叫静态流水线,不同段可以同时按不同功能工作的叫动态流水线。两种“动态”说的不是一件事;408 真题(2017-17)用的是“动态调度”这个意思。若题目出现“多功能流水线”“连接方式”字样,按后一种口径答。
- 动态流水线为什么要“按序提交”,而不是谁先算完谁先写回?
答案
为了精确异常和分支猜错后能作废:前面的指令若发生异常或分支预测错误,后面已经算完的指令结果还没提交,可以整体丢弃,机器状态和按序执行完全一样。 - 把图中 I3 改成
sub r5, r1, r7(也读 r1),动态流水线还能让 I3 提前执行吗?答案
不能。I3 也要等 I1 的 r1(第 6 拍才可用),它和 I2 一样只能等;I4 又依赖 I3 的 r5,也被拖后。乱序执行只能让不相关的指令提前。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 1800ps
- B. 2000ps
- C. 2400ps
- D. 7000ps
展开解析
C时钟周期 = 最长段 + 段寄存器延迟 = 90 + 10 = 100ps。T = (5 + 20 − 1) × 100 = 2400ps。
变化点:两道真题只问时钟周期,这里接着问总时间。D 是不用流水线、每条指令用 350ps 的结果;B 是 20 × 100,漏了装入流水线的 4 拍。
- A. 0.4 × 109 条/秒
- B. 1.99 × 109 条/秒
- C. 2.0 × 109 条/秒
- D. 2.5 × 109 条/秒
展开解析
BT = (5 + 1000 − 1) × 0.5ns = 502ns,吞吐率 = 1000 ÷ 502ns ≈ 1.99 × 109 条/秒。
变化点:2013-18 只有 100 条指令,装入流水线的 3 拍影响较大。指令越多,吞吐率越接近主频(每拍 1 条),但永远达不到。
I1: load R1, 0(R2) // R1 ← M[R2]I2: add R3, R1, R4 // R3 ← R1 + R4I3: sub R1, R5, R6 // R1 ← R5 − R6I4: store R3, 4(R1) // M[R1 + 4] ← R3- A. I1 和 I2
- B. I2 和 I3
- C. I2 和 I4
- D. I3 和 I4
展开解析
BI2 读 R1、I3 写 R1,是读后写(WAR)。按序流水线中 I2 早在 ID 段就读完了 R1,不会出错,也不阻塞。其余三对都是写后读:I2 用 I1 取来的 R1,I4 用 I2 算出的 R3,I4 用 I3 算出的 R1 作地址。
变化点:真题的干扰项多是“没有相关”的指令对,这里放了一对读后写。另外,store 的地址寄存器 R1 也是“读”,I3 和 I4 之间是写后读。
- A. add 指令后紧跟一条使用其结果的 sub 指令
- B. load 指令后紧跟一条使用其结果的 add 指令
- C. add 指令后隔一条指令再使用其结果
- D. store 指令后紧跟一条对不同地址的 load 指令
展开解析
Bload 的数据要到 M 段末才取出来,下一条指令的 EX 段已经开始了,转发也来不及,要阻塞 1 拍(load-use 冒险)。运算指令的结果在 EX 段末就有,转发给下一条的 EX 正好赶上。
变化点:2024-19 问“所有数据冒险都能靠转发解决”对不对,答案是不能。这里直接问哪一种不能:load-use。
4.5多处理器、多核与硬件多线程
| 分类(按指令流和数据流) | 含义 | 例子 |
|---|---|---|
| SISD | 单指令流单数据流 | 传统单核处理器(可以有流水线) |
| SIMD | 一条指令同时处理多个数据(数据级并行) | 向量处理器、GPU;处理数组的 for 循环最有效 |
| MISD | 多条指令流处理同一个数据流 | 几乎没有实际产品(原因见下面 Flynn 分类细看) |
| MIMD | 多条指令流处理多个数据流(线程级并行) | 多核、多处理器;分共享存储(SMP,单一物理地址空间)和消息传递(多计算机) |
- 多核:一块芯片上多个核,各核真正并行;每个核可以有自己的 Cache,也可以共享;通常共享主存。单线程程序在多核上并不会变快。
- SMP(共享内存多处理器):所有处理器共享单一物理地址空间,用普通的存取指令就能访问任何内存;访问时间相同叫 UMA,不同叫 NUMA。
- 硬件多线程:一个核里为每个线程准备一套寄存器和 PC,切线程只要换一套寄存器,开销很小。三种:细粒度(每个时钟周期轮换线程)、粗粒度(遇到 Cache 缺失等长阻塞才换)、同时多线程 SMT(一个周期里同时发射多个线程的指令,Intel 超线程就是它)。单核也能用硬件多线程(2022-22 选 C 为错误项)。
| 细粒度多线程 | 粗粒度多线程 | 同时多线程 SMT | |
|---|---|---|---|
| 什么时候换线程 | 每个时钟周期轮流换 | 只在遇到长时间阻塞(如 Cache 缺失)时才换 | 不用“换”:同一个周期里就发射多个线程的指令 |
| 能掩盖的阻塞 | 长的、短的都能 | 只能掩盖长阻塞 | 长的、短的都能 |
| 代价 | 单个线程被拖慢(要隔几拍才轮到它一次) | 换线程时流水线要清空重新填,有开销 | 需要多发射(超标量)硬件 |
| 共同点 | 每个线程有自己的一套通用寄存器和 PC,切换线程不必保存/恢复寄存器;各线程共享功能部件和 Cache | ||
- 向量处理器(SIMD 的典型):有向量寄存器和向量指令,一条指令对一整组数据(向量)做同样的运算,适合矩阵、图像这类“同一操作重复很多次”的计算。
- MIMD 的两种组织:多处理器——多个 CPU 共享一个主存(单一物理地址空间),靠读写共享变量通信,要用锁等同步机制(第 7 篇);多计算机——每台机器有自己私有的主存,靠消息传递通信(机群、分布式系统)。
- 拓展 多核各有私有 Cache 时,同一个主存块可能在几个 Cache 里各有一份副本,一个核改了,其他核的副本就过期了——这就是 Cache 一致性问题,由硬件一致性协议(如 MESI)解决。
Flynn 分类细看:为什么 MISD 几乎没有,向量处理器快在哪
问题引入:2022-22 问“多核属于 MIMD、向量处理器属于 SIMD”对不对。Flynn 分类只看两件事:同时有几条指令流、同时有几条数据流。把它画成一张 2 × 2 的格子,四类一目了然。
| 类 | 含义 | 典型 | 要点 |
|---|---|---|---|
| SISD | 单指令流单数据流 | 传统单核处理器 | 用了流水线、超标量也还是 SISD(一个指令流) |
| SIMD | 一条指令同时处理多个数据 | 向量处理器、GPU、CPU 的向量扩展指令 | 适合“同一操作做很多遍”(数组、矩阵、图像);遇到分支多、数据不规则的程序就没优势 |
| MISD | 多条指令流处理同一个数据流 | 几乎没有实际产品 | “同一份数据同时被多条不同指令处理”的需求很少;需要对同一数据流依次做多步处理时,流水线就能满足。有教材把某些容错冗余系统勉强归入此类 |
| MIMD | 多条指令流处理多个数据流 | 多核、多处理器、机群 | 按存储组织分:共享存储的多处理器(SMP,UMA / NUMA)和消息传递的多计算机 |
跟我做一遍:向量处理器省在哪(示意,已用 verify/08/p04_calc.py 计算):计算 C[i] = A[i] + B[i],i = 0 ~ 63。标量 RISC 每个元素要 lw、lw、add、sw、改下标、条件转移约 6 条指令,共约 384 条;向量处理器设向量长度为 64,只要“向量取 A、向量取 B、向量加、向量存 C”4 条。省下的是取指、译码的次数和循环控制的开销;加法本身仍做了 64 次(由多个处理单元或向量流水线完成)。
共享存储多处理器:SMP、UMA 与 NUMA
问题引入:2022-22 D“SMP 中所有处理器共享单一物理地址空间”是对的。可同样是共享内存,服务器为什么还要分 UMA、NUMA?操作系统又为什么要尽量把进程的内存分在它运行的那颗 CPU 旁边?
教材说法:多个处理器共享存储时,使用单一物理地址空间,普通的 load / store 就能访问任何位置,靠读写共享变量通信,需要锁等同步机制(第 7 篇)——这样的系统叫共享存储多处理器(SMP)。按访存时间是否相同分为 UMA(一致存储访问:每个处理器访问任何单元的时间都一样)和 NUMA(非一致存储访问:访问本地存储器快、访问其他节点的存储器慢)。与之相对,多计算机(如机群)每台机器有私有的地址空间,靠消息传递通信。
图中平均访存时间 = 本地比例 × 80 + 远程比例 × 140:0.9 × 80 + 0.1 × 140 = 86ns;0.5 × 80 + 0.5 × 140 = 110ns(数值为示意,已验算)。
物理核与逻辑核:“8 核 16 线程”是什么意思
问题引入:买电脑时常看到“8 核 16 线程”。任务管理器里显示 16 个 CPU,可芯片上明明只有 8 个核。多出来的 8 个是什么?
大白话:每个物理核是一套完整的运算部件(ALU、流水线、私有 Cache);用了 SMT(Intel 叫超线程)以后,一个物理核里放了两套寄存器和 PC,能同时保存两个线程的现场——操作系统看来就是两个 CPU,叫逻辑核(逻辑处理器、硬件线程)。两个逻辑核共用同一套运算部件,所以性能远到不了两倍,只是把一个线程用不满的发射槽填上(上面硬件多线程图的最后一格)。
| 物理核(多核) | 逻辑核(同一物理核上的硬件线程) | |
|---|---|---|
| 独立的寄存器组和 PC | 有 | 有(每个硬件线程一套) |
| 独立的 ALU、流水线 | 有 | 没有,同一物理核上的逻辑核共用 |
| 私有 L1 / L2 Cache | 每个物理核一份 | 同一物理核上的逻辑核共用 |
| 能否真正同时执行 | 能(线程级并行,MIMD) | SMT 能在同一周期混着发射,但抢同一套部件 |
| 操作系统看到的 CPU 数 | 物理核数 × 每核硬件线程数(8 × 2 = 16) | |
真题怎么考:2022-22(多核属于 MIMD、向量处理器属于 SIMD、SMP 单一物理地址空间都对;“硬件多线程只能用于多核”错——单核也能用)。判别法:多核 = 多套运算部件;硬件多线程 = 一套运算部件 + 多套寄存器现场。
多核的 Cache 一致性:MESI 的思路 拓展
问题引入:两个核各有私有 Cache,都读了变量 x = 5。现在核 0 把 x 改成 6,只改了自己 Cache 里的副本——核 1 的 Cache 里还是 5,它再读就读到旧值了。这就是 Cache 一致性问题,必须由硬件协议解决。
大白话:MESI 给每个 Cache 行贴一个四选一的标签:M(Modified,我改过了,只有我有,主存里是旧的)、E(Exclusive,只有我有,和主存一致)、S(Shared,大家都有,都和主存一致)、I(Invalid,作废)。核心规矩只有一条:要写,先通知别人把副本作废;别人再读时,持有 M 的那个核负责把新值交出来(并写回主存)。
verify/08/p04_calc.py 的小模拟核对。E 状态的好处:核 0 若在 ① 之后直接写 x,不用通知任何人(反正只有它有),E 直接变 M。和第 8 篇的关系:单核时 Cache 的“写回 / 全写”解决的是 Cache 与主存之间的一致;MESI 解决的是多个 Cache 之间的一致。408 目前只要求知道“多核私有 Cache 会有一致性问题、由硬件协议解决”,状态转换的细节不作要求。
- 一台 4 核、每核支持 2 个硬件线程的处理器,操作系统看到几个 CPU?
答案
4 × 2 = 8 个逻辑 CPU;但只有 4 套运算部件。 - NUMA 系统中,进程从节点 0 的 CPU 被调度到节点 1 的 CPU 上运行,它的访存会变快还是变慢?
答案
一般变慢:它的数据还在节点 0 的本地内存里,现在成了远程访问,Cache 里的内容也要重新加载。所以调度器讲究亲和性,尽量让进程留在原来的 CPU 上。 - MESI 中,某行在核 0 是 M 状态,核 1 要读它,会发生什么?
答案
核 0 把新值提供出来并写回主存,两个核的该行都变成 S。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 细粒度多线程可以在每个时钟周期切换线程
- B. 硬件多线程技术只能用于多核处理器
- C. 同时多线程(SMT)在一个时钟周期内只能执行一个线程的指令
- D. 粗粒度多线程在每条指令执行后都切换线程
- A. NUMA 结构中各处理器使用各自独立的物理地址空间,只能靠消息传递通信
- B. MISD 结构几乎没有实际产品
- C. 一个物理核采用同时多线程(SMT)后,操作系统可看到多个逻辑处理器
- D. 多核处理器各核的私有 Cache 之间需要由硬件协议维持一致性
展开解析
ANUMA 仍是共享存储多处理器:所有处理器共享单一物理地址空间,用普通 load / store 就能访问其他节点的内存,只是远程访问更慢。“各自独立的地址空间、靠消息传递”说的是多计算机(机群)。B、C、D 都对。
- 单总线结构中,ALU 的两个输入为什么不能都直接接总线?
答案
单总线每个时刻只能传一个数据,两个操作数不能同时从总线送进 ALU,所以一个输入端必须接暂存器(A、Y、T),先存一个操作数;输出也常要暂存器(Z、AC),因为结果出来时总线可能被占用。 - 一条 4 段流水线,各段 60、100、80、50ns,段间寄存器延迟 10ns,执行 20 条指令(无阻塞)要多少时间?
答案
时钟周期 = 100 + 10 = 110ns;周期数 = 4 + 19 = 23;时间 = 2530 ns。 - 微程序控制器有 6 个互斥类微命令,分别含 3、7、2、15、1、4 个,采用字段直接编码,操作控制字段至少多少位?
答案
⌈log24⌉ + ⌈log28⌉ + ⌈log23⌉ + ⌈log216⌉ + ⌈log22⌉ + ⌈log25⌉ = 2+3+2+4+1+3 = 15。 - load r1,0(r2) 后面紧跟 add r3,r1,r4,有转发时会不会阻塞?
答案
会阻塞 1 拍:load 的数据在 MEM 段结束才得到,而 add 在紧接的 EX 段就要用,转发也来不及(load-use 冒险)。 - 写出间址周期的数据流。间址周期结束时,有效地址放在哪里?
答案
Ad(IR)→MAR;1→R;M(MAR)→MDR;(MDR)→Ad(IR)。有效地址替换了 IR 中的形式地址(或直接送 MAR),执行周期用它取操作数。 - 在图 4-2 的单总线数据通路中,同一个节拍里能否同时有 R0out 和 R1out?能否同时有 MARin 和 Ain?
答案
不能同时 R0out、R1out:总线上一时刻只能有一个数据。可以同时 MARin、Ain:同一个数可以同时打入多个寄存器。 - 中断周期里保存的断点是哪个寄存器的内容?由谁完成保存?
答案
PC 的内容(此时已指向被打断指令的下一条);由硬件在中断周期自动完成(中断隐指令),不是由中断服务程序保存。 - 一条 4 段流水线执行 20 条指令、无阻塞,效率是多少?指令条数趋于无穷时呢?
答案
E = 20 × 4 / (4 × (4 + 20 − 1)) = 80/92 ≈ 87%;n → ∞ 时 E → 1。 - 水平型微指令和垂直型微指令,哪种执行速度快?哪种微指令字长短?
答案
水平型并行能力强、执行快,但字长长;垂直型字长短、微程序长、执行慢。 - 超标量和超长指令字(VLIW)都能一个周期执行多条指令,区别在哪?
答案
超标量由硬件在运行时动态决定哪些指令一起发射;VLIW 由编译器在编译时静态地把可并行的操作拼成一条长指令。
4.✎本篇真题闯关(29 题)
本篇各小节挂的真题全部在这里,按小节排好。可以展开后一道道做,也可以用弹窗模式连续做。做题记录(对/错)只保存在你这台电脑的浏览器里,小节标题下的题号会随之变绿或变红。
融合一:中断、异常与系统调用 —— CPU 和操作系统握手的地方
操作系统也是一段程序,它凭什么能“管”别的程序?答案是硬件给它留了一扇只有它能开的门:用户态/内核态,以及进门的唯一途径——中断和异常。计组讲这扇门的硬件怎么做(中断响应、隐指令、屏蔽字),操作系统讲进门以后做什么(中断处理程序、系统调用)。这两部分在 408 里是同一个考点的两面,本篇一起讲。
5.1用户态与内核态:CPU 里的一个开关
如果任何程序都能直接操作磁盘、关掉中断、改别人的内存,一个写错的程序就能让整台机器崩掉。所以 CPU 设计了两种运行模式,用程序状态字 PSW 里的一位来标记:
内核态(管态、核心态)
也叫特权态、系统态、内核模式(kernel mode)。操作系统内核运行在这里。可以执行所有指令,包括特权指令。用户态(目态)
也叫用户模式(user mode)。应用程序运行在这里。只能执行非特权指令;只能访问自己的地址空间。| 例子 | |
|---|---|
| 特权指令 只能在内核态执行 | I/O 指令、关中断/开中断指令、中断返回指令、内存清零、修改 PSW、修改存储保护寄存器(如页表基址寄存器)、设置时钟、切换到用户态的指令 |
| 非特权指令 | 算术逻辑运算、数据传送、跳转、压栈、trap/陷入指令(系统调用指令)、设置断点指令 |
trap(陷入/访管)指令本身是在用户态执行的,它是非特权指令——正因为用户程序能执行它,才能用它来请求操作系统服务;执行它的结果是 CPU 切换到内核态。2014-25(不能在用户态执行的是“关中断指令”)、2021-23(只能在内核态执行的是“I/O 指令”)、2026-21(不属于特权指令的是“系统调用指令”)都是在考这个。
模式怎么切换?越权会怎样?
- 用户态 → 内核态:只有一条路——发生中断或异常(包括执行陷入指令)。CPU 在响应时由硬件自动把 PSW 里的模式位改成内核态,并转到内核里的处理程序(5.2 节图 5-1)。
- 内核态 → 用户态:操作系统执行一条特权指令(典型是“中断返回”,它把先前保存的 PSW 恢复回去,PSW 里的模式位自然回到用户态)。
- 用户程序硬要执行特权指令:CPU 译码时发现“在用户态执行特权指令”,产生一个异常(非法指令 / 保护违例),转入内核;操作系统一般会终止这个进程。所以用户程序根本没法直接关中断、直接做 I/O。
| 运行在 | 程序 |
|---|---|
| 用户态 | 应用程序;命令解释程序(shell);编译程序、链接程序;C 库函数(如 printf 里格式化字符串的那部分);trap 指令本身 |
| 内核态 | 中断处理程序(时钟中断、键盘中断……);异常处理程序(缺页处理、除 0 处理);进程调度程序、进程切换;系统调用服务例程;设备驱动程序;装入程序(要建进程、分配内存,2026-23) |
记法:凡是要动“全局资源”(CPU 分给谁、内存怎么分、设备怎么用)或要执行特权指令的,都在内核态;只是在自己的地址空间里算来算去的,都在用户态。
- A. CPU 处于用户态时只能执行特权指令
- B. CPU 处于内核态时只能执行特权指令
- C. CPU 处于用户态时只能执行非特权指令
- D. CPU 处于内核态时只能执行非特权指令
展开解析
C内核态“什么都能执行”(特权指令和非特权指令都行);用户态只能执行非特权指令。
- A 错用户态恰恰不能执行特权指令;若用户态“只能执行特权指令”,普通程序连加法都做不了。
- B 错内核态也能执行非特权指令(加法、传送等),不是“只能执行特权指令”。
- C 对用户态只能执行非特权指令。
- D 错内核态可以执行特权指令(如关中断、I/O 指令),说它“只能执行非特权指令”正好把内核态的作用说反了。
特权指令 ⊂ 内核态能执行的指令;用户态执行特权指令会引起异常。
- A. 命令解释程序
- B. 缺页处理程序
- C. 进程调度程序
- D. 时钟中断处理程序
展开解析
A判断标准:要不要直接操作硬件、改内核数据结构。要 → 内核态;只是普通应用程序 → 用户态(需要时发系统调用)。
- A 对命令解释程序(shell)就是一个普通应用程序:读你敲的命令、解析,需要建进程、读文件时再通过系统调用请内核做。
- B 错缺页处理程序是缺页异常的处理程序,要修改页表、启动磁盘 I/O,必须在内核态执行。
- C 错进程调度程序要修改 PCB、就绪队列并切换 CPU 现场,是内核的核心功能,在内核态执行。
- D 错时钟中断处理程序是中断处理程序,进入时 CPU 已由硬件切到内核态,要更新系统时间、检查时间片。
中断 / 异常处理程序、调度程序、设备驱动都在内核态;shell、编译器、链接器、普通应用在用户态。同类:2026-23(编译、链接、命令解释都在用户态,装入程序要建立进程、分配内存,在内核态)。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
Ⅰ. 陷入(trap)指令 Ⅱ. 设置时钟的指令 Ⅲ. 算术运算指令 Ⅳ. 修改页表基址寄存器的指令
- A. 仅Ⅰ、Ⅲ
- B. 仅Ⅱ、Ⅳ
- C. 仅Ⅰ、Ⅱ、Ⅲ
- D. 仅Ⅲ
展开解析
A陷入指令就是给用户程序进入内核用的,必须能在用户态执行。设置时钟、修改页表基址寄存器会影响整个系统,是特权指令,只能在内核态执行。
变化点:真题问“不能在用户态执行的”。这里反过来问能执行的,陷入指令仍是最容易错的一项:它不是特权指令。
- A. C 语言编译器
- B. shell 命令解释程序
- C. 缺页异常处理程序
- D. 文本编辑器
展开解析
C缺页异常处理程序是操作系统内核的一部分,在内核态执行。编译器、命令解释程序、编辑器都是普通的用户程序。
变化点:2011-24 问“在用户态执行的”(命令解释程序)。命令解释程序虽然属于操作系统,但它运行在用户态,这一点两道题都在考。
5.2进入内核的唯一大门:中断和异常
计组教材:中断(外部中断,来自 CPU 外部)和异常(内部异常,由 CPU 执行指令时内部产生)。操作系统教材:外中断(= 中断,狭义的中断)和内中断(= 异常,也叫例外)。“广义的中断”把两者都包括在内。另一对常用词:中断是异步的(何时发生与正在执行哪条指令无关),异常是同步的(由某条指令引起,重复执行这条指令就会重复出现)。“访管指令”“陷入指令”“trap 指令”“自陷指令”说的是同一类东西。
- A. 仅Ⅰ、Ⅱ
- B. 仅Ⅰ、Ⅲ
- C. 仅Ⅱ、Ⅲ
- D. Ⅰ、Ⅱ和Ⅲ
展开解析
B用户态 → 内核态只有一条路:中断或异常(含系统调用的陷入指令)。逐个看会不会触发。
- Ⅰ 对整数除以零触发除法错异常(故障),CPU 转去内核的异常处理程序,进入内核态。
- Ⅱ 错sin() 是数学库里的普通函数,只做浮点计算,在用户态就算完了,不需要内核服务。
- Ⅲ 对read 是系统调用,通过陷入(trap)指令进入内核,由内核去读文件。
干扰项从哪来:A 是把 sin() 当成系统调用,又漏了 read;C 是把 sin() 当成系统调用,又以为除以零只是得到一个错误结果、不会进内核;D 是把 sin() 这种库函数也当成要进内核
库函数 ≠ 系统调用:printf、read 的封装最终会发系统调用,sin、strlen 不会。同类:2015-24(“NOT R0”永远不会引起异常;DIV 可能除 0、MOV R0, addr 可能缺页、INT n 是软中断,都可能进内核)。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
Ⅰ. ADD R0, R1(寄存器相加) Ⅱ. MOV R0, [addr](读主存) Ⅲ. trap 指令 Ⅳ. DIV R0, R1(整数除法)
- A. 仅Ⅲ
- B. 仅Ⅱ、Ⅲ
- C. 仅Ⅱ、Ⅲ、Ⅳ
- D. Ⅰ、Ⅱ、Ⅲ、Ⅳ
展开解析
C读主存可能缺页;trap 就是用来进入内核的;整数除法可能除以 0。三者都会进入内核。寄存器相加即使溢出,也只是置 OF 标志,不会自动进入内核。
变化点:2015-24 找“不可能”的(NOT R0,只操作寄存器)。这里换成加法:加法溢出也不会引起异常,这一点容易错。
- A. 进程调用库函数 printf
- B. 进程执行 trap 指令
- C. 进程切换
- D. 外部设备发出中断请求
展开解析
C进程切换由操作系统内核完成,一定在内核态。printf 的库函数部分在用户态执行;trap 指令本身是在用户态执行的,执行后才进入内核;外部中断请求可能在 CPU 处于任何状态时到来。
变化点:2012-23 问“不可能在用户态发生的”(进程切换)。这里换个问法,答案不变,干扰项换成了 trap 指令和 printf。
5.3中断和异常的分类
| 类型 | 例子 | 什么时候检测 | 处理完返回到哪 |
|---|---|---|---|
| 故障 fault(可恢复) | 缺页、缺段 | 指令执行过程中 | 回到发生故障的那条指令,重新执行(缺页调入后再访存一次) |
| 故障(无法恢复) | 整数除以 0、非法操作码、地址越界、存储保护错、用户态执行特权指令 | 指令执行过程中 | 处理程序无法修复,通常终止进程,不再返回 |
| 自陷 trap | 系统调用(陷入指令)、断点指令、单步跟踪、溢出自陷指令(如 INTO,OF=1 时触发) | 执行这条陷阱指令时 | 回到陷阱指令的下一条 |
| 终止 abort | 指令执行过程中发生了使计算机无法继续的硬件故障:控制器出错、存储器校验错、总线错误 | 指令执行过程中 | 无法继续,终止程序,由异常服务程序做善后(甚至停机) |
| 可屏蔽中断 INTR | I/O 设备:键盘、磁盘、网卡、DMA 传送结束、定时器 | 每条指令执行完之后检查 | 回到下一条指令(被打断的那条已执行完) |
| 不可屏蔽中断 NMI | 掉电、严重的硬件故障 | 每条指令执行完之后 | 紧急处理(如掉电时抢在断电前保存现场),处理完能否继续看故障情况;关中断也挡不住它 |
注意两个“看起来像”:整数加法溢出本身只是把 OF 置 1,不会自动进内核,要靠程序里的“溢出自陷指令”检查 OF 才会触发自陷(2019-45 第 5 问);而整数除法的除 0、商溢出会由硬件直接触发除法异常(2025-44)。三类内部异常(故障、自陷、终止)都是在指令执行过程中由 CPU 内部检测的;但它们不一定都回到原指令(2015-22 D 错)。
- A. 系统调用
- B. 页缺失
- C. DMA 传送结束
- D. 打印机缺纸
展开解析
B处理完回到哪里,看事件的类型:故障(fault)回到当前指令重新执行;陷阱(trap,如系统调用)和外部中断回到下一条指令。
- A 错系统调用是陷阱(自陷):陷入指令本身已执行完,内核服务完回到它的下一条指令。
- B 对页缺失是故障:这条指令访存没有成功,调入页面后必须重新执行这条指令。
- C 错DMA 传送结束是外部中断,在当前指令执行完后才响应,处理完回到下一条指令。
- D 错打印机缺纸是外部设备发来的 I/O 中断,同样在当前指令执行完后才响应,返回下一条指令。
故障(如缺页)→ 处理完重新执行当前指令;陷阱 → 下一条;终止 → 不返回;外部中断 → 下一条。反面题:2019-14(“缺页处理完成后回到下一条指令”错);2015-22(“内部异常处理后都返回到发生异常的指令”错:自陷回下一条,除 0 一般直接终止进程)。
- A. “访存时缺页”属于中断
- B. “整数除以 0”属于异常
- C. “DMA 传送结束”属于中断
- D. “存储保护错”属于异常
展开解析
A判断口诀:和正在执行的这条指令有关 → 异常;从 CPU 外部来、与当前指令无关 → 中断。
- A 错缺页是正在执行的访存指令在地址转换时引起的,属于异常(故障),不是中断——本题选它。
- B 对整数除以 0 是除法指令自己出错,属于异常。
- C 对DMA 传送结束由 CPU 外部的 DMA 控制器发出请求,属于中断。
- D 对存储保护错是访存指令越权引起的,属于异常。
外部中断:键盘、定时器、网卡、DMA 结束、打印机缺纸;异常:缺页、除 0、越界、非法指令、陷入(系统调用、断点)。
① 有中断请求(设备把中断请求触发器置 1);② CPU 允许中断(开中断,中断允许位 IF = 1),而且这个中断没有被屏蔽字屏蔽;③ 当前指令刚执行完(中断在指令之间响应;异常则是在指令执行过程中检测到的)。几个请求同时来时,由硬件排队电路按响应优先级选一个;不可屏蔽中断(如掉电)不受 IF 和屏蔽字控制。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
Ⅰ. 系统调用 Ⅱ. 缺页 Ⅲ. 时钟中断 Ⅳ. 执行断点指令
- A. 仅Ⅰ、Ⅲ
- B. 仅Ⅰ、Ⅳ
- C. 仅Ⅰ、Ⅲ、Ⅳ
- D. Ⅰ、Ⅱ、Ⅲ、Ⅳ
展开解析
C系统调用、断点指令都属于自陷,返回陷阱指令的下一条;时钟中断是外部中断,在指令执行完后才响应,返回下一条。只有缺页是故障,要回到原指令重新执行。
变化点:2021-21 问哪个“必须回到当前指令重新执行”(缺页)。这里反过来问哪些回到下一条,要把每一类都判断一遍。
Ⅰ. 整数除以 0 Ⅱ. 打印机缺纸 Ⅲ. 用户态下执行特权指令 Ⅳ. 键盘按键
- A. 仅Ⅰ、Ⅱ
- B. 仅Ⅰ、Ⅲ
- C. 仅Ⅱ、Ⅳ
- D. 仅Ⅰ、Ⅲ、Ⅳ
展开解析
B内部异常由正在执行的指令引起:除以 0、在用户态执行特权指令都是。打印机缺纸、键盘按键来自外部设备,是外部中断。
变化点:判断标准只有一个:事件是不是当前指令引起的。打印机缺纸虽然是“出错”,但它来自设备,与当前指令无关。
- A. 三者都在一条指令执行结束后才被检测
- B. 缺页属于自陷
- C. 终止发生后,一般不能返回原程序继续执行
- D. 自陷是由外部设备发出的中断请求
展开解析
C终止由严重的硬件错误引起(如控制器出错、存储器校验错),处理后一般不能继续执行原程序。A 错:内部异常在指令执行过程中检测。B 错:缺页是故障。D 错:自陷由陷阱指令引起,是内部异常(2020-18)。
变化点:把三类异常的“检测时机、典型例子、返回位置”混在一道题里考。
5.4融合一次中断的完整接力:硬件做一半,操作系统做一半
这是本篇最重要的图。以“程序正在运行,键盘按了一个键”为例:
硬件(中断隐指令):关中断、保存断点 PC 和 PSW、形成服务程序入口地址。
软件(中断服务程序,属于操作系统):保存通用寄存器(现场)、保存/设置屏蔽字、开中断、服务、恢复、中断返回。
为什么 PC 必须硬件保存?因为一旦 PC 被改成服务程序入口,原来的 PC 就丢了,软件来不及保存;而通用寄存器在服务程序开头还没被动过,可以由软件自己保存。
- A. 仅Ⅰ、Ⅱ
- B. 仅Ⅰ、Ⅲ
- C. 仅Ⅱ、Ⅲ
- D. Ⅰ、Ⅱ、Ⅲ
- A. Ⅰ→Ⅴ→Ⅵ→Ⅱ→Ⅶ
- B. Ⅲ→Ⅰ→Ⅴ→Ⅶ
- C. Ⅲ→Ⅳ→Ⅴ→Ⅵ→Ⅶ
- D. Ⅳ→Ⅰ→Ⅴ→Ⅵ→Ⅶ
展开解析
A先分清谁做什么:关中断、保存断点是 CPU 响应中断时由硬件(中断隐指令)完成的,不在服务程序里;服务程序(软件)做的是保护现场 → 中断事件处理 → 恢复现场 → 开中断 → 中断返回。单级中断在服务期间一直关着中断,直到返回前才开。
- A 对Ⅰ保护现场 → Ⅴ处理 → Ⅵ恢复现场 → Ⅱ开中断 → Ⅶ中断返回。
- B 错以Ⅲ关中断开头(那是隐指令做的),而且漏了恢复现场和开中断。
- C 错Ⅲ、Ⅳ都是隐指令的工作,而且没有开中断。
- D 错Ⅳ保存断点是硬件做的,顺序也不完整。
开中断指令要到下一条指令执行完才生效,所以“开中断 → 中断返回”之间不会被打断。
- A. 仅Ⅰ、Ⅱ
- B. 仅Ⅰ、Ⅱ、Ⅳ
- C. 仅Ⅲ、Ⅳ
- D. 仅Ⅱ、Ⅲ、Ⅳ
展开解析
D中断处理是“硬件做一半、操作系统做一半”:响应阶段由硬件(中断隐指令)完成,进入服务程序后才是操作系统的事。
- Ⅰ 错保存断点(PC)由硬件的中断隐指令完成——此时还没进入任何程序,软件来不及保存。
- Ⅱ 对中断服务程序是操作系统(内核中的处理程序或驱动)提供的。
- Ⅲ 对中断向量表在操作系统初始化时建立,把各类中断的服务程序入口地址填进去。
- Ⅳ 对保存中断屏蔽字(以及通用寄存器等现场)由中断服务程序在开头用指令完成,属于操作系统。
干扰项从哪来:A 是把硬件保存断点当成操作系统做的,又漏了Ⅲ和Ⅳ;B 是把硬件保存断点当成操作系统做的,又漏了初始化中断向量表;C 是漏了Ⅱ,没意识到中断服务程序本身就由操作系统提供
硬件做:关中断、保存断点、找入口(中断隐指令);OS 做:保存现场和屏蔽字、执行服务、恢复现场。中断向量表是一个数组(2023-24),在 OS 初始化时建立(2022-24);进程切换时不用改中断向量表基址寄存器(2025-23)。
图 5-3 第 ③ 步“识别中断源、把服务程序入口地址送 PC”具体是怎么做的?现代计算机普遍用向量中断:
子程序调用(call)是程序自己安排的,只保存 PC(返回地址);中断是随机发生的,要保存 PC 和 PSW(PSW 里有标志位和模式位,被打断的程序回来后必须原样继续)。2012-24:中断处理一定会保存而子程序调用不需要保存的是“程序状态字寄存器”。
中断请求怎么“记下”、怎么“挑”、怎么找到入口
- 记下请求:每个中断源有一个中断请求触发器,设备要请求就把它置 1;所有触发器合起来叫中断请求寄存器。CPU 在每条指令执行完时采样这些请求信号来发现中断(2017-22 D 对)。
- 判优(挑一个):几个请求同时到,按响应优先级选出一个。实现方法:硬件排队(排队电路、优先编码器,快)或软件查询(程序按优先顺序逐个查请求位,先查到谁就先服务谁,慢但灵活)。中断控制器是按优先级排队,不是按请求到达的先后(2018-22 A 错)。
- 识别中断源、找到入口:软件查询法——所有中断都先进一个公共程序,由它依次查询是哪个设备,再跳到对应的服务程序;硬件向量法(向量中断)——由硬件直接给出中断类型号,据此查中断向量表得到入口(下图),快,现代计算机都用它。
| 名词 | 意思 |
|---|---|
| 中断向量 | 中断服务程序的入口地址 |
| 中断向量地址 | 存放中断向量的那个主存单元的地址(中断向量表中第 n 项的地址),可以理解为“入口地址的地址”;由硬件的向量地址形成部件根据中断源产生。有的机器在向量地址处放的是一条跳转到服务程序的指令 |
| 中断向量表 | 所有中断向量按类型号排成的数组,由操作系统初始化时建立(2022-24、2023-24) |
| 响应外部中断 | 响应异常(含陷入) | |
|---|---|---|
| 什么时候 | 一条指令执行完之后 | 指令执行过程中发现就处理 |
| 受开关中断、屏蔽字控制吗 | 可屏蔽中断受控制;NMI 不受 | 不受(异常不能屏蔽,也不用等开中断) |
| 类型号从哪来 | 由中断控制器送给 CPU | 由 CPU 内部根据异常种类确定 |
| 相同之处 | 都要保存断点和 PSW、切换到内核态、按类型号查中断向量表转入处理程序 | |
再看两个“谁先拿到”的问题:用户从键盘登录时,首先拿到键入字符的是键盘中断处理程序(2010-32)——字符先由它从键盘控制器读进内核缓冲区,登录程序、命令解释程序是之后才从缓冲区里读到的(第 9 篇 9.6)。
中断判优:硬件排队电路与软件查询
问题引入:上面一句话说“几个请求同时到,按响应优先级选一个”。可电路怎么“选”?2018-22 A 说“中断控制器按请求的先后次序排队”为什么错?把两种判优办法画出来就明白了。
链式排队像传话游戏:老师(CPU)只对第一排说“谁有问题?”,第一排有问题就直接举手、不再往后传;没问题才把话传给第二排……坐得越靠前,越先轮到。软件查询像老师拿着点名册一个个问:先问 1 号,再问 2 号……点名册上的顺序就是优先级,想改顺序只要改点名册。不像的地方:电路传话几乎不花时间,点名(执行查询程序)却要一条条执行指令,慢得多。
| 硬件判优(链式排队、优先编码器) | 软件查询判优 | |
|---|---|---|
| 谁决定先响应谁 | 电路的连接顺序 / 编码器 | 查询程序里查询的先后顺序 |
| 速度 | 快(不执行指令) | 慢(要执行查询程序) |
| 改优先级 | 难(要改电路) | 容易(改程序) |
| 决定的是 | 响应优先级(同时到达时先响应谁);处理优先级(能不能打断)另由屏蔽字决定(5.5) | |
真题怎么考:2018-22 A(按请求先后排队,错)、2024-21 A(屏蔽字决定的是处理优先级而不是响应优先级)、2011-21(响应优先级是干扰条件,屏蔽字只看处理优先级)。判别法:同时到达比“响应优先级”,正在处理时被打断比“处理优先级”。
中断类型号 → 向量地址 → 中断向量:跟我算一遍
问题引入:三个名字都带“向量”,选择题专挑它们的定义互换。它们其实是一条链上的三环:拿到类型号,算出向量地址,从那里读出中断向量(入口地址),送 PC。
跟我做一遍(已用 verify/08/p05_calc.py 验算):
- 例 1:中断向量表从主存 0 号单元开始,按字节编址,每个表项 4 字节(32 位入口地址),小端存放。类型号 n = 21H → 向量地址 = 0 + 4 × 21H = 84H。若 84H ~ 87H 依次存 00H、10H、40H、00H,则中断向量 = 0040 1000H,送 PC 后开始执行服务程序。
- 例 2:表基址 1000H,每项 8 字节,n = 12(十进制)→ 向量地址 = 1000H + 8 × 12 = 1000H + 60H = 1060H。
- 反过来:表基址 2000H、每项 4 字节,已知向量地址 2040H → n = (2040H − 2000H) ÷ 4 = 40H ÷ 4 = 16。
① 中断向量 ≠ 向量地址:前者是入口地址本身,后者是存放入口地址的单元的地址。② 中断向量表在主存里,由操作系统初始化时建立(2022-24),不是硬件生成的。③ 异常、系统调用也有类型号,同样查这张表(2023-24:用数组存放,按下标直接找)。
中断处理 10 步:每一步谁做、为什么是它做
问题引入:2012-22、2015-23、2022-31、2026-22 年年换着问“哪一步由硬件做、哪一步由操作系统做”。死记容易混,弄懂每一步为什么必须由它来做,就不会错。
| 步 | 做什么 | 谁做 | 为什么必须是它 |
|---|---|---|---|
| ① | 关中断 | 硬件(隐指令) | 接下来要保存断点和 PSW,这几步若被别的中断插进来,断点就会被覆盖、乱掉——必须先“锁门” |
| ② | 保存断点(PC)和 PSW | 硬件(隐指令) | 第 ③ 步要把入口地址写进 PC,旧 PC 一被覆盖就找不回来了;而服务程序的任何一条指令要执行,都得先改 PC——软件根本来不及保存。PSW 也一样:响应时模式位要改成内核态、中断允许位被清零,旧 PSW 必须在改之前存好 |
| ③ | 识别中断源,入口地址送 PC | 硬件(向量法) | 向量中断由硬件给出类型号、查表取入口;若用软件查询法,这一步在公共查询程序里完成 |
| ④ | 保护现场:保存通用寄存器(和旧屏蔽字) | 软件(服务程序) | 服务程序开头还没动过通用寄存器,软件来得及保存;而且不同的服务程序用到的寄存器不同,只保存自己要用的最省时间——让硬件每次把全部寄存器都存一遍反而慢 |
| ⑤ | 设置新屏蔽字,开中断 | 软件 | 只有多重中断才需要;屏蔽字体现的“处理优先级”是操作系统的策略,可以随时改 |
| ⑥ | 执行中断服务(真正干活) | 软件 | 读键盘、搬数据、唤醒等待的进程……这是设备驱动 / 操作系统的工作 |
| ⑦ | 关中断 | 软件 | 多重中断时,恢复现场要“一口气”做完,不能被打断 |
| ⑧ | 恢复现场和屏蔽字 | 软件 | 与 ④ 对称,出栈顺序与入栈相反 |
| ⑨ | 开中断 | 软件 | 开中断指令要到下一条指令执行完才生效,所以 ⑨ 和 ⑩ 之间不会被打断(2010-21) |
| ⑩ | 中断返回 | 软件执行指令 硬件恢复 | 服务程序执行中断返回指令(特权指令),由硬件把保存的 PC、PSW 恢复回去,回到被打断的程序(和用户态) |
真题怎么考:2012-22(隐指令 = 关中断 + 保存断点 + 形成入口)、2015-23(由操作系统保存的是通用寄存器)、2022-31(系统调用时由操作系统完成的是保存通用寄存器、执行服务程序;保存断点 / PSW、切内核态是硬件做的)、2026-22(必须由硬件完成的是保存断点)、2018-22(“隐指令保护通用寄存器”错)。判别法:涉及 PC、PSW、关中断、取入口 → 硬件;涉及通用寄存器、屏蔽字、服务本身 → 软件。失效的情况:题目若明确说“某机器由硬件自动保存全部寄存器”(有的处理器确实如此),就按题目来。
- 为什么中断要保存 PSW,而普通的子程序调用只保存返回地址?
答案
中断随时可能发生,被打断的程序事先不知道,回来后必须连标志位、模式位、中断允许位都原样恢复;子程序调用是程序自己安排的,调用前后的状态由程序自己负责(2012-24)。 - 中断向量表从 0 开始、每项 4 字节,某中断的向量地址是 0040H,它的类型号是多少?
答案
40H ÷ 4 = 10H,即类型号 16。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
Ⅰ. 关中断 Ⅱ. 保存断点 Ⅲ. 保存通用寄存器 Ⅳ. 识别中断源,形成中断服务程序入口地址 Ⅴ. 设置新的中断屏蔽字
- A. 仅Ⅰ、Ⅱ
- B. 仅Ⅰ、Ⅱ、Ⅳ
- C. 仅Ⅱ、Ⅲ、Ⅳ
- D. Ⅰ、Ⅱ、Ⅳ、Ⅴ
展开解析
B中断隐指令做三件事:关中断、保存断点(和 PSW)、引出中断服务程序(识别中断源、送入口地址)。保存通用寄存器(保护现场)和设置屏蔽字都在中断服务程序里用软件完成。
变化点:2012-22 只给了三项。这里多了“设置屏蔽字”:它是多重中断时服务程序做的事,不是硬件做的。
Ⅰ. 保护现场 Ⅱ. 开中断 Ⅲ. 关中断 Ⅳ. 设置新的屏蔽字 Ⅴ. 中断事件处理 Ⅵ. 恢复现场 Ⅶ. 中断返回
- A. Ⅰ→Ⅳ→Ⅱ→Ⅴ→Ⅲ→Ⅵ→Ⅱ→Ⅶ
- B. Ⅲ→Ⅰ→Ⅳ→Ⅴ→Ⅵ→Ⅱ→Ⅶ
- C. Ⅰ→Ⅴ→Ⅵ→Ⅱ→Ⅶ
- D. Ⅰ→Ⅱ→Ⅳ→Ⅴ→Ⅵ→Ⅶ
展开解析
A进入服务程序时已经是关中断(硬件关的)。先保护现场、设置新屏蔽字,再开中断,允许更高级的中断嵌套进来;处理完后先关中断,再恢复现场(和屏蔽字),然后开中断、中断返回。
变化点:2010-21 是单级中断,答案是“保护现场 → 中断事件处理 → 恢复现场 → 开中断 → 中断返回”(即本题的 C),处理期间一直关中断。多重中断多了三步:设置屏蔽字、处理前开中断、恢复前关中断。
- A. 程序计数器(PC)的内容
- B. 通用寄存器的内容
- C. 快表(TLB)中的内容
- D. Cache 中的内容
展开解析
APC(断点)和 PSW 由中断隐指令自动保存。通用寄存器由中断服务程序(操作系统)按需保存;TLB、Cache 不需要保存。
变化点:2015-23 问“由操作系统保存的”(通用寄存器)。选项完全相同,只把“操作系统”换成“硬件”,答案就换了。
- A. C,因为它的请求先到
- B. A 和 C 同时被响应
- C. A,因为它在链上离 CPU 最近
- D. 不确定,取决于两个请求到达的时间差
- A. 2028H,该中断服务程序的入口地址
- B. 200AH,该中断服务程序的入口地址
- C. 2028H,该中断的类型号
- D. 0AH,该中断服务程序的入口地址
展开解析
A向量地址 = 表基址 + n × 表项大小 = 2000H + 0AH × 4 = 2000H + 28H = 2028H;这个单元里存放的是中断向量,即服务程序的入口地址。B 忘了乘表项大小;C 把表项内容说成了类型号;D 把类型号当成了向量地址。
考点:类型号 → 向量地址 → 中断向量(已用 verify/08/p05_calc.py 验算)。
5.5多重中断与中断屏蔽字
中断服务程序正在跑的时候,又来了一个中断请求,能不能打断它?这由两个“优先级”决定:
响应优先级(硬件排队,固定)
好几个请求同时到达时,CPU 先响应谁。由硬件排队电路决定,不能改。处理优先级(由屏蔽字决定,可改)
正在处理 A 时,B 来了能不能打断 A。操作系统在每个服务程序里设置屏蔽字来控制。响应优先级的一般规律(排队电路按这个顺序设计,选择题偶尔考):硬件故障中断 > 软件中断;不可屏蔽中断 > 可屏蔽中断;DMA 请求 > I/O 设备的中断请求;高速设备 > 低速设备;输入设备 > 输出设备;实时设备 > 普通设备。
实现多重中断(中断嵌套)的两个条件:① 中断服务程序里要提前执行开中断指令(否则 CPU 一直处于关中断,谁也打不断它);② 只允许处理优先级更高的中断源打断优先级低的(靠屏蔽字实现)。
屏蔽字每一位对应一个中断源,1 = 屏蔽(不许打断),0 = 开放。Li 的服务程序里:处理优先级不比 Li 高的(包括 Li 自己)全置 1,比 Li 高的置 0。
- A. 11110
- B. 01101
- C. 00011
- D. 01010
展开解析
D屏蔽字只看处理优先级:处理某级中断时,自己和处理优先级比它低的置 1(屏蔽),比它高的置 0(放行)。响应优先级在这里是干扰信息。
- 处理优先级:L4 > L0 > L2 > L1 > L3。
- 处理 L1 时:L4、L0、L2 比它高 → 0;L1 自己和 L3 → 1。
- 按 M4 M3 M2 M1 M0 排:0 1 0 1 0 = 01010。
- A 错11110 是按响应优先级(L0 → L4)设的:屏蔽了 L1 自己和响应优先级更低的 L2、L3、L4。但改变处理顺序正是屏蔽字的作用,应该按处理优先级设。
- B 错M0 = 1 屏蔽了 L0,可 L0 的处理优先级比 L1 高,应能打断 L1;M1 = 0 又没屏蔽 L1 自己,同级中断会重复嵌套。
- C 错00011 屏蔽了 L1 和 L0、放行了 L3,正好反了:L0 比 L1 高应放行,L3 比 L1 低应屏蔽。
- D 对01010:放行 L4、L0、L2,屏蔽 L1 和 L3。
检查方法:处理优先级越高,屏蔽字里的 1 越多。本题五个屏蔽字(M4 … M0):L4 11111、L0 01111、L2 01110、L1 01010、L3 01000。
单重中断和多重中断,服务程序各做几步
| 谁做 | 单重中断 | 多重中断 |
|---|---|---|
| 硬件 中断隐指令 | 关中断 → 保存断点(PC)和 PSW → 送服务程序入口地址到 PC | |
| 软件 服务程序 | ① 保护现场(通用寄存器) ② 执行设备服务(中断事件处理) ③ 恢复现场 ④ 开中断 ⑤ 中断返回 | ① 保护现场和原屏蔽字,设置本级的新屏蔽字 ② 开中断(从这里起允许更高级的中断打断) ③ 执行设备服务 ④ 关中断(恢复现场时不能被打断) ⑤ 恢复现场和原屏蔽字 ⑥ 开中断 ⑦ 中断返回 |
为什么保护现场、恢复现场时要关中断?这几步如果执行到一半被打断,寄存器和屏蔽字只保存(或恢复)了一部分,返回后就乱了。2010-21 的单级中断顺序就是上表左列(Ⅰ→Ⅴ→Ⅵ→Ⅱ→Ⅶ)。
动手画一次:响应顺序 ≠ 处理顺序
展开解析
- 屏蔽字(顺序 M0 M1 M2,1 = 屏蔽):处理优先级不高于自己的都屏蔽。L0 最高 → 111;L2 屏蔽自己和更低的 L1 → 011;L1 最低,只屏蔽自己 → 010。
- t = 5:L1、L2 同时请求,硬件按响应优先级先响应 L1。L1 的服务程序设置屏蔽字 010 后开中断,此时还在排队的 L2 没有被屏蔽,于是立刻被响应——L1 实际一步都没做就被 L2 打断了。L2 从 t = 5 开始服务。
- t = 8:L0 请求。L2 的屏蔽字 011 不屏蔽 L0 → L0 打断 L2(L2 已做 3μs),L0 服务 8 ~ 18,完成。
- 返回 L2,继续做剩下的 7μs:18 ~ 25,完成;返回 L1,做 10μs:25 ~ 35,完成;返回用户程序。
处理完成的顺序是 L0(18μs)→ L2(25μs)→ L1(35μs),正好符合处理优先级 L0 > L2 > L1(已用程序模拟验算)。画这类图的要点:纵轴按处理优先级从高到低排,每来一个请求就问一句“当前正在运行的程序的屏蔽字挡不挡它”,不挡就往上跳,处理完就原路跳回。
- A. 中断屏蔽字用于确定中断响应的优先级
- B. 保存断点和程序状态字在中断响应阶段完成
- C. 保存通用寄存器和设置新中断屏蔽字由软件实现
- D. 单重中断方式下,中断处理时 CPU 处于关中断状态
展开解析
A选“错误”的说法。关键区分:响应优先级由硬件排队电路固定;处理优先级才由中断屏蔽字改变。
- A 错“屏蔽字确定响应优先级”错误——本题选它。响应优先级由硬件判优电路决定、不能改;屏蔽字改变的是处理优先级(谁能打断谁)。
- B 对中断响应阶段由硬件(中断隐指令)关中断、保存断点(PC)和程序状态字、找到服务程序入口,说法正确。
- C 对进入服务程序后,保存通用寄存器(保护现场)和设置新的屏蔽字都是服务程序用指令做的,即由软件实现,说法正确。
- D 对单重中断在服务程序执行期间不开中断,不允许被别的中断打断,所以 CPU 处于关中断状态,说法正确。
硬件做:关中断、保存断点和 PSW、找入口;软件做:保存现场和屏蔽字、开中断(多重)、服务、恢复。同类:2020-21(“一旦可屏蔽中断请求有效,CPU 将立即响应”错:要等当前指令结束、开中断且未被屏蔽;不可屏蔽中断 NMI 关中断时也能响应);2021-22(“仅在用户态下才能检测和响应中断”错:内核态执行服务程序时也能响应更高级的中断);2018-22(“中断控制器按请求的先后次序进行优先级排队”错)。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 1011
- B. 0100
- C. 1110
- D. 1101
展开解析
A处理 L0 时,要屏蔽它自己和处理优先级比它低的 L3、L1,只让 L2 能打断它。所以 M3 = 1、M2 = 0、M1 = 1、M0 = 1,屏蔽字 = 1011。
变化点:2011-21 是 5 级中断、问 L1。方法相同:在处理优先级序列中找到自己,自己和排在后面的都置 1,排在前面的置 0。
- A. 中断屏蔽字决定中断的响应优先级
- B. 通过设置中断屏蔽字可以改变中断的处理优先级
- C. 不可屏蔽中断也受中断屏蔽字的控制
- D. CPU 处于关中断状态时,只要屏蔽字允许,仍会响应可屏蔽中断
5.6融合系统调用:程序请操作系统办事的正规渠道
程序要读文件、创建进程、申请内存、收发网络数据——这些都涉及共享资源,必须由操作系统代办。系统调用就是操作系统提供给程序的接口,每个系统调用有一个编号(系统调用号)。按功能分:设备管理、文件管理、进程控制、进程通信、内存管理。
系统调用、库函数、普通函数调用:三者对比
| 普通函数调用(call) | 库函数(如 printf、fopen) | 系统调用(如 write、open) | |
|---|---|---|---|
| 由谁提供 | 程序自己 | 语言的运行库(C 库) | 操作系统内核 |
| 执行在 | 用户态 | 用户态 | 服务例程在内核态 |
| 怎么进入 | call 指令 | call 指令 | 陷入指令(trap / int / syscall) |
| 保存什么 | 返回地址 | 返回地址 | 断点 和 PSW,切换模式、切换到内核栈 |
| 开销 | 小 | 小(除非它内部又发了系统调用) | 大(模式切换、保存恢复现场) |
关系:库函数有的完全在用户态完成(sin、strlen、生成随机数),有的是对系统调用的包装(printf 做完格式化后调用 write,fopen 调用 open)。所以“库函数是 OS 的接口”是错的,OS 给程序的接口是系统调用(2010-23)。
参数怎么交给内核 拓展:① 放在寄存器里(最常用,快;32 位 Linux 用 eax 放系统调用号、ebx/ecx/edx… 放参数,再执行 int 80H);② 参数太多时放在主存的一个参数块里,把块的地址放进寄存器;③ 压到栈里。内核根据系统调用号查系统调用表找到对应的服务例程。因为用户态和内核态用的栈不同,陷入时 CPU 还要从用户栈切换到该进程的内核栈。
- A. 仅Ⅰ、Ⅳ
- B. 仅Ⅱ、Ⅲ
- C. 仅Ⅰ、Ⅱ、Ⅳ
- D. Ⅰ、Ⅲ、Ⅳ
展开解析
C系统调用是操作系统内核提供给应用程序的服务接口,服务程序在内核态执行。
- Ⅰ 对陷入之后 CPU 进入内核态,系统调用服务程序在内核态执行。
- Ⅱ 对外设由操作系统统一管理,用户程序只能通过系统调用请求 I/O,不能直接执行 I/O 指令(特权指令)。
- Ⅲ 错不同操作系统的系统调用各不相同(Windows 和 Linux 的系统调用号、名称、参数都不一样);统一的是编程语言的库函数(如 C 标准库),不是系统调用。
- Ⅳ 对系统调用就是操作系统的程序接口。
系统调用:用户态发起(陷入)→ 内核态执行 → 返回用户态;它因操作系统而异。
- A. 仅Ⅰ、Ⅱ
- B. 仅Ⅰ、Ⅲ
- C. 仅Ⅱ、Ⅲ
- D. Ⅰ、Ⅱ和Ⅲ
展开解析
Aread 是通过文件描述符读已打开文件的系统调用。
- Ⅰ 对数据不在内存要启动磁盘读,进程在 I/O 完成前阻塞(睡眠等待)。
- Ⅱ 对任何系统调用都要执行陷入指令,CPU 从用户态切换到内核态(核心态)。
- Ⅲ 错按文件名查找是 open 的工作;open 返回文件描述符 fd,read(fd, buf, n) 的参数是 fd、缓冲区地址和字节数,不含文件名。
open 按名查找、建立打开文件表项;read / write / close 只用文件描述符(2024-29)。
- A. 中断或异常发生时,CPU 处于内核态
- B. 每个系统调用都有对应的内核服务例程
- C. 中断处理程序开始执行时,CPU 处于内核态
- D. 系统添加新类型设备时,需注册相应的中断服务例程
展开解析
A区分“发生时”和“处理时”:中断/异常发生时 CPU 可能正在用户态运行用户程序(也可能在内核态);CPU 响应之后,处理程序才一定在内核态执行。
- A 错“中断或异常发生时 CPU 处于内核态”错误——本题选它。
- B 对每个系统调用号都对应内核中的一个服务例程(查系统调用表)。
- C 对响应中断时硬件已把 CPU 切换到内核态,中断处理程序开始执行时一定处于内核态。
- D 对新设备有新的中断,驱动程序要向内核注册相应的中断服务例程(填中断向量/处理程序表)。
用户态 → 内核态只有一条路:中断或异常(包括系统调用);内核态 → 用户态靠执行中断返回等特权指令。
展开解析
① 除数 x = 0000 0000H(被除数任意)→ 除 0;② 被除数 = 8000 0000H(−231)、除数 = FFFF FFFFH(−1)→ 商 231 超出 int 范围,商溢出。
异常响应(硬件):关中断;保存断点(PC)和程序状态字 PSW;切换到内核态;根据异常类型转到异常处理程序入口(查中断向量表,把入口地址送 PC)。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
① 执行设备驱动程序 ② 执行陷入指令 ③ 在用户态调用库函数 printf ④ 执行系统调用服务程序 ⑤ 返回用户态
- A. ③→②→④→①→⑤
- B. ②→③→④→①→⑤
- C. ③→④→②→①→⑤
- D. ③→②→①→④→⑤
展开解析
Aprintf 是库函数,先在用户态执行(格式化字符串),然后由它执行陷入指令进入内核;系统调用服务程序(设备无关软件)再调用驱动程序操作设备,完成后返回用户态。
变化点:2017-24 只有“传参 → 陷入 → 服务 → 返回”四步。这里前面加了库函数,中间加了驱动程序,要能把它们放进同一条主线。
Ⅰ. 创建子进程 Ⅱ. 从磁盘文件读数据 Ⅲ. 求字符串长度(strlen) Ⅳ. 进程调度
- A. 仅Ⅰ、Ⅱ
- B. 仅Ⅰ、Ⅳ
- C. 仅Ⅱ、Ⅲ
- D. 仅Ⅰ、Ⅱ、Ⅳ
展开解析
A创建进程、读文件都要操作系统代劳,必须通过系统调用。strlen 在用户态就能算完。进程调度是操作系统内部在需要时自己做的,不是应用程序通过系统调用请求的。
变化点:2021-32 是单选(创建新进程)。这里改成组合题,并保留两个干扰项:用户态能完成的库函数,以及操作系统内部的工作。
Ⅰ. 保存断点和程序状态字 Ⅱ. 保存通用寄存器的内容 Ⅲ. 将 CPU 模式改为内核态 Ⅳ. 执行系统调用服务程序
- A. 仅Ⅰ、Ⅲ
- B. 仅Ⅱ、Ⅳ
- C. 仅Ⅰ、Ⅱ、Ⅲ
- D. 仅Ⅲ
展开解析
A执行陷入指令时,CPU 硬件自动保存断点和 PSW,并切换到内核态。保存通用寄存器、执行服务程序都是操作系统(软件)做的。
变化点:2022-31 问由操作系统完成的(保存通用寄存器、执行服务程序)。系统调用中硬件和软件的分工与中断响应完全相同。
5.7本篇总表
| 外部中断 | 异常(故障) | 自陷 / 系统调用 | 终止 | |
|---|---|---|---|---|
| 来源 | CPU 外部(设备、时钟) | 当前指令执行中出错 | 程序主动执行陷入指令 | 硬件出错 |
| 与当前指令 | 无关(异步) | 有关(同步) | 有关(同步,故意的) | 在指令执行中检测到 |
| 何时检测/响应 | 每条指令执行完 | 指令执行过程中 | 陷入指令执行时 | 指令执行过程中 |
| 能否屏蔽 | INTR 可以,NMI 不行 | 不能 | 不能 | 不能 |
| 返回到 | 下一条 | 本条(重新执行)或终止 | 下一条 | 不返回 |
| 典型 | 键盘、I/O 完成、定时器、DMA 结束 | 缺页、除 0、越界、非法指令 | read/write/fork、断点 | 控制器出错、存储器校验错 |
- “CPU 在执行中断服务程序时一定处于关中断状态”对吗?
答案
不对。单重中断是;多重中断在保护现场、设好屏蔽字后会开中断,允许更高处理优先级的中断嵌套。 - 处理优先级 L2 > L0 > L3 > L1,屏蔽字顺序 M3M2M1M0,写出 L0 的屏蔽字。
答案
比 L0 高的只有 L2 → M2 = 0;L0 自己和 L3、L1 置 1 → M3M2M1M0 = 1011。 - 下列哪些在用户态执行:① 命令解释程序 ② 时钟中断处理程序 ③ trap 指令 ④ 进程调度程序 ⑤ C 库函数 printf 中格式化字符串的那部分代码?
答案
①③⑤。 - 缺页异常处理完,为什么要回到“本条”指令而不是下一条?
答案
缺页时这条指令需要的数据(或指令本身)还没取到,指令没执行完。页面调入后必须重新执行这条指令,才能真正完成访存。 - 用户程序里执行一条关中断指令,会发生什么?
答案
关中断是特权指令,用户态执行会被 CPU 检测到并产生异常(非法指令/保护违例),转入内核处理,通常终止该进程;中断并不会被关掉。 - “中断向量”和“中断向量地址”有什么区别?
答案
中断向量是中断服务程序的入口地址;中断向量地址是存放这个入口地址的主存单元的地址(中断向量表中那一项的地址),即“入口地址的地址”。 - 多重中断的服务程序里,为什么在“恢复现场和屏蔽字”之前要先关中断?
答案
恢复到一半若被打断,寄存器和屏蔽字只恢复了一部分,返回后程序状态就错了,所以恢复现场期间必须关中断,恢复完再开中断、中断返回。 - printf、sin、write 三者中,哪个一定会使 CPU 进入内核态?
答案
write(系统调用)。sin 完全在用户态;printf 是库函数,它自身在用户态,只是内部会调用 write 才进入内核。
5.✎本篇真题闯关(40 题)
本篇各小节挂的真题全部在这里,按小节排好。可以展开后一道道做,也可以用弹窗模式连续做。做题记录(对/错)只保存在你这台电脑的浏览器里,小节标题下的题号会随之变绿或变红。
进程、线程与 CPU 调度
第 3 篇说程序装入内存就成了“进程”。这一篇回答:操作系统怎么记录一个进程(PCB)、进程会处在哪些状态、怎么在进程之间切换(这里要用到第 5 篇的中断和第 4 篇的寄存器)、线程和进程有什么区别,以及轮到谁上 CPU——调度算法。调度算法是操作系统里出题最多的单个考点。
6.1进程是什么:程序 + 数据 + PCB
回忆第 1 篇的多道程序:内存里同时放着好几个程序,CPU 在它们之间换来换去。换走之前,A 程序算到了哪一条指令、寄存器里是什么、打开了哪些文件……都得有个地方记下来,下次换回来才能接着跑。于是操作系统给每个“正在跑的程序”建一份档案——这份档案连同它的程序和数据,就叫进程。
程序是一本菜谱,进程是“照着菜谱正在做的这一道菜”:同一本菜谱可以同时做两份(两个进程),每一份都有自己做到了第几步、锅里现在有什么(各自的状态)。菜谱是静态的,做菜是动态的。
进程是程序的一次执行过程,是系统进行资源分配和调度的独立单位(引入线程后,调度单位变成线程)。一个进程在内存里由三部分组成,其中最关键的是进程控制块 PCB——操作系统就是通过 PCB 感知和管理进程的,PCB 是进程存在的唯一标志。
系统里可能有成百上千个 PCB,操作系统按状态把它们串成若干条队列,这样调度时只要看就绪队列,某个事件发生时只要看等这个事件的阻塞队列:
选择题常考:进程的特征、进程与程序的区别
| 特征 | 意思 |
|---|---|
| 动态性 | 进程是程序的一次执行,有创建、运行、暂停、消亡的生命周期。动态性是进程最基本的特征 |
| 并发性 | 多个进程能在一段时间内同时推进;引入进程的目的就是让程序能并发执行 |
| 独立性 | 进程是能独立运行、独立获得资源、独立接受调度的基本单位(未建立 PCB 的程序不能作为独立单位运行) |
| 异步性 | 各进程按各自独立的、不可预知的速度推进,需要同步机制保证结果正确(第 7 篇) |
| 结构性 | 每个进程都有一个 PCB,由 PCB + 程序段 + 数据段组成 |
| 程序 | 进程 | |
|---|---|---|
| 本质 | 静态的指令集合,存在外存上 | 动态的执行过程,有生命周期 |
| 组成 | 代码(和初始数据) | PCB + 程序段 + 数据段 |
| 对应关系 | 同一个程序可以同时对应多个进程(同时打开两个记事本就是两个进程);进程是资源分配和调度的单位,程序不是 | |
PCB 里记了什么(选择题会问“下列哪项不在 PCB 中”):
| 类别 | 内容 |
|---|---|
| 进程描述信息 | 进程标识符 PID、用户标识符 UID |
| 进程控制和管理信息 | 进程当前状态、优先级、代码入口地址、程序的外存地址、进入内存的时间、已占用 CPU 的时间、信号量的使用情况 |
| 资源分配清单 | 代码段、数据段、堆栈段的指针(内存管理信息,如页表基址),打开的文件(文件描述符),使用的 I/O 设备 |
| 处理机相关信息(CPU 现场) | 通用寄存器、PC、PSW、栈指针等的值——进程被换下时保存在这里,再次运行时从这里恢复(6.5 节) |
不在 PCB 里的:进程要处理的数据本身(在数据段)、程序代码本身(在程序段)、文件的内容(在磁盘上,PCB 里只有打开文件的指针)。PCB 由操作系统创建和管理,存放在内核空间,用户程序不能直接访问(2025-46 第 1 问)。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 程序和进程一一对应
- B. PCB 是进程存在的唯一标志
- C. 同一个程序只能对应一个进程
- D. 进程是静态的,程序是动态的
展开解析
B创建进程就是建立 PCB,撤销进程就是回收 PCB,所以 PCB 是进程存在的唯一标志。同一个程序可以同时运行多次,对应多个进程;进程是动态的,程序是静态的。
变化点:2020-29 考父子进程(不共享虚拟地址空间)。这里考进程最基本的概念。
6.2进程的五种状态和转换
- A. 执行 P(wait) 操作
- B. 申请内存失败
- C. 启动 I/O 设备
- D. 被高优先级进程抢占
展开解析
D运行 → 就绪:只是 CPU 被拿走,其他什么都不缺;运行 → 阻塞:要等某种资源或事件。
- A 错执行 P 操作时如果信号量不够,进程要等别人 V 操作唤醒它,是运行 → 阻塞。
- B 错申请内存失败,要等别的进程释放内存才能继续,是运行 → 阻塞。
- C 错启动 I/O 后进程要等 I/O 完成才能继续,是运行 → 阻塞。
- D 对被高优先级进程抢占,进程只是暂时没有 CPU,条件都具备,回到就绪队列。
就绪只缺 CPU,阻塞在等事件。同类:2017-27(“时间片用完后变为阻塞态”错,应为就绪);2023-27(主动出让 CPU → 就绪;缺页、wait → 阻塞);2014-26(读磁盘完成后 OS 必做的是把进程改为就绪态)。
- A. 仅Ⅰ、Ⅳ
- B. 仅Ⅱ、Ⅲ
- C. 仅Ⅲ、Ⅳ
- D. 仅Ⅰ、Ⅲ、Ⅳ
展开解析
D执行 → 阻塞:进程要等某个事件(I/O 完成、资源、信号量);只是 CPU 被拿走则是执行 → 就绪。
- Ⅰ 对读文件要启动磁盘 I/O,进程等数据读入期间阻塞。
- Ⅱ 错时间片用完只是 CPU 被收回,进程什么都不缺,变为就绪态。
- Ⅲ 对申请的外设可能正被别的进程占用,申请不到就要阻塞等待。
- Ⅳ 对wait() 时信号量不够(值减 1 后小于 0),进程进入该信号量的等待队列,阻塞。
干扰项从哪来:A 是漏了Ⅲ,以为申请外设总能立即得到;B 是把时间片用完当成阻塞,又漏了读文件和 wait();C 是漏了Ⅰ,以为读文件不需要等待磁盘
题目问“可能导致”:只要存在一种情况会阻塞就算。同类:2018-27、2019-24(能唤醒进程的是“I/O 结束”“某进程退出临界区”,时间片用完不会唤醒别人)。
每一种状态转换:什么原因、谁触发
| 转换 | 原因 | 性质 |
|---|---|---|
| 创建 → 就绪 | PCB 建好、资源分配好,插入就绪队列(资源不够时停在创建态) | 由 OS 完成 |
| 就绪 → 运行 | 被进程调度程序选中,分到 CPU | 调度 |
| 运行 → 就绪 | 时间片用完;被更高优先级进程抢占;主动让出 CPU(yield) | 只是失去 CPU,别的不缺 |
| 运行 → 阻塞 | 请求 I/O、读文件、申请资源失败、wait/P 操作使信号量不够、等待某事件 | 主动行为(进程自己请求而阻塞) |
| 阻塞 → 就绪 | 等待的事件发生:I/O 完成(中断处理程序唤醒)、资源被释放、signal/V 操作 | 被动行为(由别的进程或中断处理程序唤醒) |
| 运行 → 终止 | 正常结束、出错(如除 0)、被外界杀死 |
① 不存在“阻塞 → 运行”和“就绪 → 阻塞”:阻塞的进程被唤醒只能先进就绪队列;就绪的进程没在运行,不可能发出会让自己阻塞的请求。
② 单 CPU 系统有 n 个进程时:运行态最多 1 个,就绪态最多 n − 1 个(有一个在运行时),阻塞态最多 n 个(全都在等 I/O,CPU 跑闲逛进程);最少都可以是 0 个。
③ 一个状态转换可能引起另一个:一个进程运行 → 阻塞后 CPU 空了,就会引起某个就绪进程就绪 → 运行;但“某进程由阻塞变就绪”不一定引起调度(非抢占式时要等当前进程让出 CPU)。
七状态模型:挂起与中级调度
问题引入:内存只有那么大。如果内存里塞满了阻塞着、一时半会儿用不上 CPU 的进程,新的进程就进不来,CPU 也可能没活干。能不能把这些“暂时没用的”进程先挪到磁盘上,腾出内存?可以——这就是挂起。
食堂座位有限:在座位上等菜的人是“阻塞”(人还占着座位);被请到门外长椅上等的人是“挂起”(座位让出来了)。门外的人要吃饭,得先被叫回座位(激活)。不像的地方:挂起的进程连同它的程序和数据(进程映像)都被换到外存,而“等菜的人”身上什么也没被拿走。
教材说法:把进程映像从内存换到外存叫挂起,换回内存叫激活,由中级调度(内存调度)负责,目的是提高内存利用率和系统吞吐量。五状态模型再加两个状态:就绪挂起(在外存,一旦被激活就能就绪)和阻塞挂起(在外存,还在等事件)。
| 阻塞 | 挂起 | |
|---|---|---|
| 原因 | 进程自己在等某个事件(I/O、资源、信号量) | 操作系统为了腾内存(或用户、父进程要求)把它换出 |
| 进程映像在哪 | 内存 | 外存 |
| 谁决定 | 进程主动(请求后得不到) | 中级调度(系统决定) |
| 怎么解除 | 等的事件发生 → 唤醒 | 激活(换入内存) |
真题怎么考:408 选择题对挂起考得很少,常见的是“三级调度各做什么”“中级调度的作用”(6.6 节三级调度图)。判别:问“在不在内存”就分清了阻塞和挂起;问“谁负责换入换出”就是中级调度。
- 一个阻塞挂起的进程等待的 I/O 完成了,它会立刻变成就绪态吗?
答案
不会。它只变为就绪挂起(仍在外存),要被中级调度激活、换回内存后才是就绪态,再由进程调度选中才能运行。 - 中级调度的主要目的是什么?
答案
在内存紧张时把暂时不能运行的进程换到外存、需要时再换入,提高内存利用率和系统吞吐量。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
Ⅰ. P 的时间片用完 Ⅱ. P 执行 V 操作,唤醒了一个优先级比 P 高的进程
Ⅲ. P 请求从磁盘读数据 Ⅳ. P 执行 P 操作后信号量的值小于 0
- A. 仅Ⅰ
- B. 仅Ⅰ、Ⅱ
- C. 仅Ⅲ、Ⅳ
- D. 仅Ⅰ、Ⅱ、Ⅳ
展开解析
B时间片用完、被更高优先级的进程抢占,都是运行 → 就绪。请求 I/O、P 操作得不到资源,都是运行 → 阻塞。
变化点:2022-28 问“变为阻塞态”(读文件、申请外设、wait)。这里反过来问“变为就绪态”,而且抢占不是来自新到达的进程,而是 P 自己唤醒的进程。
- A. 运行 → 阻塞
- B. 阻塞 → 就绪
- C. 就绪 → 运行
- D. 阻塞 → 运行
展开解析
D被唤醒的进程只能先进入就绪队列,等调度程序选中才能运行,所以阻塞 → 运行不可能直接发生。同理,就绪 → 阻塞也不可能:只有正在运行的进程才会因为请求资源而阻塞。
变化点:2014-26 考“I/O 完成后必做的是改为就绪态”,这里把它推广到所有转换。
- A. 被挂起的进程仍然在内存中等待事件
- B. 阻塞挂起的进程等待的事件发生后,立即变为就绪态
- C. 进程的挂起和激活由低级调度(进程调度)完成
- D. 中级调度把暂时不能运行的进程换到外存,以提高内存利用率和系统吞吐量
展开解析
D挂起 = 把进程映像换到外存,由中级调度(内存调度)负责,目的是腾出内存。A 错:挂起的进程在外存(在内存等事件的是阻塞态)。B 错:事件发生后只变成就绪挂起,仍在外存,要激活后才是就绪态。C 错:低级调度只负责从就绪队列选进程分配 CPU。
考点:七状态模型。
6.3进程控制与进程通信
创建、终止、阻塞、唤醒、切换这些操作都由操作系统内核的原语完成。原语执行时不能被打断(用关中断实现,第 5 篇),否则 PCB 可能改了一半就被切走,数据乱套。
| 操作 | 要做的事 | 真题 |
|---|---|---|
| 创建 | 申请空白 PCB → 分配资源(内存等)→ 初始化 PCB → 插入就绪队列(不是直接运行) | 2021-24 选“申请空白 PCB、初始化 PCB”;2010-24 用户登录、启动程序会创建进程,设备分配不会 |
| 终止 | 回收资源、撤销 PCB;子进程不一定跟着终止 | 2024-24 |
| 阻塞 / 唤醒 | 成对使用:谁等什么事件就阻塞在哪个队列上,事件发生时由别的进程或中断处理程序唤醒它 |
每个原语的具体步骤(选择题会拿其中一步问“是否必须”):
| 原语 | 什么时候用 | 步骤 |
|---|---|---|
| 创建 | 用户登录、作业调度把作业调入内存、系统要提供某种服务、程序请求创建子进程(fork) | ① 分配唯一的 PID,申请空白 PCB(申请不到则创建失败)② 分配运行所需的资源(内存等;资源不足时停在创建态等待,不算失败)③ 初始化 PCB(标识、状态、优先级、CPU 现场)④ 若就绪队列能接纳,插入就绪队列 |
| 终止 | 正常结束、异常结束(越界、非法指令、除 0)、外界干预(用户杀死、父进程请求、OS 干预) | ① 按 PID 找到 PCB,读出状态 ② 若正在运行,立即终止,让出 CPU ③ 若有子孙进程,视系统策略决定是否一并终止(2024-24:“终止子进程”不一定做)④ 把资源还给父进程或系统 ⑤ 把 PCB 从所在队列删除 |
| 阻塞 | 进程需要等待某事件 | ① 找到自己的 PCB ② 若在运行,保存现场,状态改为阻塞,停止运行 ③ 把 PCB 插入该事件的阻塞队列 ④ 转进程调度 |
| 唤醒 | 它等待的事件发生了 | ① 在该事件的阻塞队列里找到 PCB ② 从阻塞队列移出,状态改为就绪 ③ 插入就绪队列 |
父子进程(2020-29):子进程由父进程创建(如 fork),有自己的 PCB 和独立的虚拟地址空间(fork 时复制父进程的地址空间内容,但之后各改各的),可以继承、共享父进程打开的文件等资源;父子进程可以并发执行;父子进程访问同一临界资源时同样要互斥(不能同时使用)。
进程间通信(IPC)——进程的地址空间彼此独立,要交换数据必须借助操作系统:
- 共享存储:OS 把同一块物理内存映射进两个进程的地址空间(第 8 篇的内存映射文件就能做到),读写要自己用同步互斥保护。
- 消息传递:通过 send/receive 原语;直接发给对方,或放进“信箱”(间接通信)。
- 管道:一个固定大小的内存缓冲区,像一根水管,一端写一端读;写满时写进程阻塞,读空时读进程阻塞(2014-31 选 C);同一时刻只能单向传输(半双工)。
- 信号:通知进程发生了某个事件(如 Ctrl+C),进程可以设置处理函数(谁发、何时处理、三种处理方式见下面 信号)。
| 方式 | 细节(选择题考点) |
|---|---|
| 共享存储 | 分两种:基于数据结构的共享(只能共享 OS 规定好的固定数据结构,低级);基于存储区的共享(划一块内存区,由进程自己决定放什么,高级)。速度最快(读写不经内核),但同步互斥要进程自己用 PV 实现 |
| 消息传递 | 以格式化的消息为单位,用 send/receive 原语。直接通信:发送方把消息挂到接收方的消息缓冲队列上;间接通信:发到中间实体信箱,接收方从信箱取(计算机网络里的电子邮件就是这种思路) |
| 管道 | 本质是内核里一个固定大小的缓冲区(Linux 里是一种特殊的“管道文件”);写满时写进程阻塞,读空时读进程阻塞;数据读出后就从管道里消失;普通管道同一时刻只能单向(半双工),要双向通信就建两个管道;读写要互斥,但允许多个读进程或写进程(2014-31 D 说“只能有一个读进程或一个写进程”是错的) |
第四种通信方式:信号(signal)
问题:你在终端里运行一个死循环程序,按下 Ctrl+C,它就停了。这个程序里没有一行代码在“读键盘”,它是怎么知道你按了 Ctrl+C 的?——是操作系统给它发了一个信号。2025 年起考纲在“进程间通信”里明确写了“共享内存,消息传递,管道,信号”,这一条要单独会。
教材说法:信号是操作系统用来通知进程某个事件已经发生的一种机制,属于异步通知——进程事先不知道什么时候会收到。每种信号有一个编号和名字,例如 SIGINT(2,Ctrl+C 中断)、SIGKILL(9,强制终止)、SIGSEGV(11,非法访存)、SIGFPE(8,算术异常如除 0)、SIGCHLD(17,子进程结束)。
| 环节 | 谁做、做什么 |
|---|---|
| 发送 | 三种来源:① 内核检测到硬件异常后发给出错的进程(除 0 → SIGFPE、非法访存 → SIGSEGV);② 终端驱动程序在用户按 Ctrl+C 时发给前台进程(SIGINT);③ 另一个进程通过 kill(pid, 信号) 系统调用发送(kill 只是“发信号”,不一定杀死对方)。不管哪种,都是内核在目标进程的 PCB 里把“待处理信号”位图的对应位置 1。 |
| 阻塞(屏蔽) | 进程可以暂时屏蔽某些信号(PCB 里还有一张“屏蔽位图”,和中断屏蔽字一个思路)。被屏蔽的信号留在待处理位图里,等解除屏蔽再处理。 |
| 处理时机 | 进程从内核态返回用户态之前(系统调用返回、中断或异常处理完返回、被调度重新运行时),内核检查“待处理 且 未屏蔽”的信号,有就处理。所以信号不是“立刻”打断进程的。 |
| 处理方式 | 三选一:① 执行默认动作(多数是终止进程,有的是忽略、暂停、继续);② 忽略;③ 捕获:执行进程事先用 signal() / sigaction() 注册的信号处理函数(在用户态执行,执行完再回到原来的断点)。SIGKILL 和 SIGSTOP 不能被捕获、屏蔽或忽略,保证管理员总能终止或暂停进程。 |
| 不排队 | 位图每种信号只有 1 位:同一种信号在处理前到达多次,只记一次(WSL Ubuntu 实测:屏蔽期间发 3 次 SIGUSR1,解除屏蔽后处理函数只执行 1 次)。所以信号适合通知,不适合计数或传数据。 |
跟我做一遍:进程 P 正在用户态跑一个循环,并事先用 signal(SIGINT, handler) 注册了处理函数。用户按下 Ctrl+C,接下来依次发生:
- 键盘中断:CPU 响应中断,转到内核的键盘中断处理程序(P 被打断,断点保存好)。
- 发送:终端驱动程序识别出 Ctrl+C,内核把前台进程 P 的 PCB 里 SIGINT 那一位置 1。
- 检查:中断处理完、准备回到 P 的用户态时,内核发现 P 有“待处理且未屏蔽”的 SIGINT。
- 捕获:P 注册过处理函数,内核让 P 先回到用户态执行 handler(不是直接回原断点);handler 结束后经一个系统调用回到内核,再恢复到原断点继续循环。
- 若 P 没有注册处理函数:执行 SIGINT 的默认动作——终止 P,这就是“Ctrl+C 能停下程序”的原因。
| 信号 | 信号量(第 7 篇) | 中断(第 5 篇) | |
|---|---|---|---|
| 是什么 | 内核通知进程“发生了某事件” | 一个整数 + 等待队列,用 P、V 做同步互斥 | CPU 暂停当前程序去处理外部或内部事件 |
| 谁响应 | 目标进程(内核代为检查,处理函数在用户态运行) | 做 P、V 的进程自己 | CPU 硬件 + 内核的中断服务程序 |
| 何时生效 | 目标进程从内核态返回用户态之前 | 执行 P、V 原语时立即 | 每条指令执行完的中断周期 |
| 带数据吗 | 只有信号编号 | 只有计数值 | 中断类型号 |
记法:信号常被称为“软件层面的中断”——都是“异步事件 + 打断当前流程 + 处理完回断点”,都能屏蔽;区别是中断由 CPU 硬件在指令之间响应,信号由内核在返回用户态之前替进程检查。不要把信号和信号量混为一谈:前者是通知,后者是计数器。
- 进程 A 调用
kill(B, SIGTERM)后,B 是不是立刻停止运行?答案
不一定。kill 只是让内核在 B 的 PCB 里把 SIGTERM 置位;B 要等到下一次从内核态返回用户态时才处理,而且 B 可能捕获或忽略 SIGTERM(它不是 SIGKILL)。 - 为什么说信号“不适合用来计数”?
答案
待处理信号用位图记录,每种信号只有 1 位:处理之前同一种信号来了多次也只记一次(实测屏蔽期间发 3 次只处理 1 次)。要计数应当用信号量或共享变量。 - 下列关于信号的说法,错误的是:A. 信号可以由内核发出,也可以由进程发出;B. 信号处理函数在内核态执行;C. SIGKILL 不能被捕获;D. 信号可以被屏蔽。
答案
B 错。用户注册的处理函数是用户程序的一部分,在用户态执行;内核只负责在返回用户态时安排跳转。
fork:调用一次,返回两次 拓展
问题:上面说“程序请求创建子进程(fork)”,可 fork 到底怎么用?子进程从哪里开始执行?为什么同一段 if 能让父子进程走不同的分支?这几个问题自命题院校和面试题经常问,408 一旦考进程创建的代码题也绕不开。
要点(UNIX / Linux 的 pid_t fork(void)):
- 调用一次,返回两次:在父进程里返回子进程的 PID(大于 0),在子进程里返回 0;创建失败时只在父进程里返回 −1。
- 子进程从 fork 返回的地方接着执行,不是从 main 的开头;它得到父进程用户地址空间的副本(变量值相同、之后各改各的)、自己的 PCB 和 PID,并继承打开的文件。
- fork 之后父、子进程并发执行,谁先运行由调度决定,输出顺序不确定。
int main(void) {
int x = 1;
pid_t pid = fork(); /* 从这里起有两个进程 */
if (pid == 0) { /* 子进程拿到 0 */
x = x + 1; printf("child x=%d\n", x);
} else { /* 父进程拿到子进程的 PID(> 0) */
x = x * 10; printf("parent x=%d\n", x);
}
return 0;
}
输出两行:child x=2 和 parent x=10(顺序不定)。两个进程各有一份 x,子进程把 x 加 1 不影响父进程那份(WSL Ubuntu 22.04 实测,verify/08/p13/a_fork.c)。
跟我做一遍:for (i = 0; i < 3; i++) { fork(); printf("-\n"); } 一共有几个进程、打印几行?
- 第 0 轮:1 个进程执行 fork → 2 个进程,各打印 1 行,共 2 行。
- 第 1 轮:2 个进程各 fork → 4 个,打印 4 行。
- 第 2 轮:4 个进程各 fork → 8 个,打印 8 行。
- 合计:进程 23 = 8 个(含最初的那个),fork 被执行 1 + 2 + 4 = 7 次(= 新建的子进程数),打印 2 + 4 + 8 = 14 行。一般地,循环 n 次:进程 2n 个、fork 2n − 1 次、循环体内打印 2n+1 − 2 行(Python 模拟 n = 1 ~ 5,WSL 实测 n = 3 时 14 行)。
细节:printf 不带 \n 或输出被重定向到文件时,C 库的缓冲区会随 fork 一起被复制,打印次数会比上面多——这是 C 库的行为,不是 fork 本身的规律,考研不涉及。
fork(); fork(); printf("hi\n");打印几次?答案
4 次。第一个 fork 后 2 个进程,第二个 fork 后 4 个进程,每个都执行 printf。- 子进程里
fork()的返回值是多少?子进程从哪一句开始执行?答案
返回 0;从 fork 调用返回处(给 pid 赋值那一句)接着执行,不是从 main 的开头重新执行。
融合跟着一次 read 走一遍:阻塞原语和唤醒原语
问题引入:上表的四个原语都是一串步骤,背起来很枯燥。把它们放进一个真实场景——进程 P2 调用 read 读磁盘——看 PCB 在几条队列之间怎么“搬家”,就记住了。
设定:单 CPU,非抢占式调度。开始时 P2 在运行,就绪队列里有 P1、P3,磁盘等待队列为空。
若改成抢占式优先级调度且 P2 优先级比 P1 高,唤醒 P2 之后中断返回前会再调度一次,P2 抢占 P1。两种情况下“阻塞 → 就绪”这一步都一样,区别只在之后会不会立刻调度。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
Ⅰ. 回收进程占用的资源 Ⅱ. 撤销进程的 PCB Ⅲ. 终止它的所有子进程
- A. 仅Ⅰ
- B. 仅Ⅰ、Ⅱ
- C. 仅Ⅱ、Ⅲ
- D. Ⅰ、Ⅱ、Ⅲ
- A. 共享存储方式中,进程读写共享区时不需要同步与互斥
- B. 消息传递方式中,进程通过 send 和 receive 原语交换数据
- C. 一个管道可以同时双向传输数据
- D. 信号机制主要用于在进程之间传输大量数据
6.4线程:更轻的“执行流”
进程切换代价大(要换 PCB、换地址空间、刷 TLB)。可是一个程序里的多个任务(比如浏览器一边下载一边渲染)本来就共享数据,没必要各开一个进程。于是有了线程:
进程是资源分配的基本单位,线程是 CPU 调度的基本单位。同一进程的线程共享进程的地址空间(代码段、全局变量、堆)、打开的文件;每个线程私有的只有:线程 ID、栈(和栈指针)、寄存器(包括 PC)、线程控制块 TCB。
| 比较项 | 进程 | 线程 |
|---|---|---|
| 调度 | 引入线程前是调度的基本单位 | 引入线程后,线程是调度的基本单位;同一进程内线程切换不引起进程切换 |
| 资源 | 资源分配的基本单位,拥有地址空间、打开的文件等 | 基本不拥有资源,只有必不可少的一点(栈、寄存器、TCB),共享所属进程的资源 |
| 并发性 | 进程之间可以并发 | 同一进程的线程之间、不同进程的线程之间都可以并发,并发度更高 |
| 系统开销 | 创建、撤销、切换都要分配/回收地址空间等资源,开销大 | 开销小:同进程线程切换只保存少量寄存器,不用换地址空间 |
| 独立性 | 各有独立的地址空间,互不干扰 | 同进程的线程共享地址空间,一个线程可以改另一个线程的数据(所以要同步) |
| 通信 | 要借助 OS(6.3 的 IPC) | 同进程线程直接读写共享变量(加同步互斥) |
| 多处理机 | 单线程进程只能用一个 CPU | 一个进程的多个内核级线程可以分到多个 CPU 上并行 |
TCB 里有什么:线程标识符;一组寄存器(PC、状态寄存器、通用寄存器);线程运行状态;优先级;线程专有存储区(保存现场用);栈指针。线程也有就绪、运行、阻塞三种基本状态,转换规则与进程相同。
| 用户级线程(ULT) | 内核级线程(KLT) | |
|---|---|---|
| 谁管理 | 用户空间的线程库,内核不知道它存在 | 操作系统内核,每个线程有内核里的 TCB |
| 切换 | 不用进内核,快 | 要进内核,慢一些 |
| 缺点 | 一个线程阻塞(如系统调用),整个进程都阻塞;多核上不能真正并行 | 开销大 |
| 其他 | 可以在不支持线程的 OS 上实现 | 调度由 OS 完成 |
多线程模型(用户级线程怎么对应到内核级线程):多对一(多个 ULT 映射到一个 KLT,一个阻塞全阻塞)、一对一(每个 ULT 一个 KLT,并发好、开销大)、多对多(折中)。2019-23 “操作系统为每个用户级线程建立一个线程控制块”错(那只是一对一模型的情况;纯用户级线程内核根本看不见)。2012-31:“不管系统是否支持线程,进程都是资源分配的基本单位”正确。
| 多对一 | 一对一 | 多对多(n 个用户线程 → m 个内核线程) | |
|---|---|---|---|
| 一个线程阻塞 | 整个进程阻塞 | 只阻塞它自己 | 只阻塞它自己,其余用户线程可以映射到别的内核线程上 |
| 能否多核并行 | 不能(内核只看见一个线程) | 能 | 能,最多 m 个同时运行 |
| 线程管理开销 | 小(切换在用户态完成) | 大(每创建一个用户线程就要建一个内核线程) | 适中 |
| 一句话 | 切换快、并发差 | 并发好、开销大 | 两者折中 |
① “能不能共享”:属于进程的(代码段、全局变量、堆、打开的文件 fd、地址空间)都能共享;属于某个线程的(它的栈、栈指针、寄存器、PC)不能(2011-25、2024-28、2026-25)。
② “内核知不知道”:纯用户级线程内核看不见,所以 OS 不会为它建 TCB(2019-23)、它的切换不需要内核(2012-31 C 错)、一个线程执行阻塞系统调用会使整个进程阻塞、一个进程的多个用户级线程不能在多个 CPU 上并行。内核级线程这四点都反过来。
③ “映射关系方向”:多对一、一对一、多对多都是“用户级线程 → 内核级线程”;“多个内核级线程映射到一个用户级线程”这种说法本身不存在(2026-25 B 错)。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
Ⅰ. 程序计数器 Ⅱ. 寄存器上下文 Ⅲ. 栈 Ⅳ. 打开的文件 Ⅴ. 全局变量
- A. 仅Ⅲ
- B. 仅Ⅰ、Ⅲ
- C. 仅Ⅰ、Ⅱ、Ⅲ
- D. 仅Ⅲ、Ⅳ
展开解析
C线程是独立调度的执行流,每个线程要有自己的 PC、寄存器和栈(保存在线程控制块 TCB 和各自的栈中)。代码、全局变量、堆、打开的文件等资源属于进程,所有线程共享。
变化点:2011-25 只问“栈指针不能共享”。这里把线程私有的三样东西一起列出来,反过来考。
- A. 一个线程执行阻塞式系统调用时,整个进程都会被阻塞
- B. 多个线程可以在多个 CPU 核上并行运行
- C. 线程之间的切换必须进入内核
- D. 操作系统为每个用户级线程建立线程控制块
展开解析
A内核只看得见一个内核级线程。其中一个用户级线程阻塞在系统调用上,内核就把这个内核级线程(整个进程)阻塞了。同理,也不能利用多核并行。用户级线程的切换由线程库在用户态完成,不进内核,内核也不为它们建 TCB(2019-23)。
变化点:真题问用户级线程和内核级线程的一般特点,这里落到具体的多线程模型上:多对一模型的优点是切换快,缺点是一个阻塞全体阻塞、不能并行。
6.5融合进程切换:CPU 里的寄存器怎么“换人”
CPU 只有一套寄存器。从进程 A 换到进程 B,本质就是把 A 在 CPU 里的“现场”存进 A 的 PCB,再把 B 的现场从 B 的 PCB 装回 CPU。这件事只能在内核态做,通常由时钟中断、系统调用等进入内核后触发。
调度是“决定下一个让谁上 CPU”(选),切换是“真正把 CPU 交给它”(换现场)。调度之后如果选中的还是原进程,就不用切换。另外,切换一定发生在内核态(2012-23),所以时间片轮转需要用到:进程控制块、时钟中断处理程序(计时、判断时间片到没到)、就绪队列,但不需要阻塞队列(2021-25 选 C)。
- 保存当前进程 A 的 CPU 上下文:PC、PSW、通用寄存器、栈指针等存入 A 的 PCB。
- 更新 A 的 PCB:状态改成就绪(时间片到、被抢占)或阻塞(等事件),把 PCB 挂到相应队列。
- 选择另一个进程 B(这一步就是“调度”),更新 B 的 PCB(状态改为运行)。
- 更新内存管理的数据结构:换成 B 的页表(改页表基址寄存器 PDBR),必要时刷新 TLB。
- 恢复 B 的 CPU 上下文,从 B 上次停下的地方(它保存的 PC)继续执行。
模式切换是 CPU 在用户态和内核态之间转换(如一次系统调用:用户态 → 内核态 → 用户态),还是同一个进程在运行,开销小。进程切换是换了一个进程运行,必然先进入内核态,开销大。所以:进程切换一定伴随模式切换,但模式切换不一定引起进程切换(系统调用返回时若没有更高优先级的进程,就回到原进程)。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 通用寄存器
- B. 程序计数器
- C. 程序状态字
- D. Cache 中的内容
展开解析
D上下文是进程在 CPU 上运行的现场:通用寄存器、PC、PSW、页表基址寄存器等,要保存到 PCB(或内核栈)中。Cache 对程序透明,由硬件管理,不需要保存。
变化点:2025-23 问“不需要更新的寄存器”(中断向量表基址,所有进程共用)。这里换成“不需要保存的内容”,答案换成了 Cache。
- A. 进程切换可以在用户态完成
- B. 进程切换要保存旧进程的上下文并恢复新进程的上下文
- C. 只要发生中断,就一定会进行进程切换
- D. 采用分页存储管理时,进程切换不需要改变页表基址寄存器
6.6CPU 调度:何时调度、按什么标准
| 调度层次 | 做什么 | 频率 |
|---|---|---|
| 高级调度(作业调度) | 从外存的后备作业中挑一个,装入内存、创建进程 | 最低 |
| 中级调度(内存调度) | 把暂时不跑的进程挂起到外存 / 调回内存 | 中 |
| 低级调度(进程调度) | 从就绪队列挑一个进程,把 CPU 给它(本篇重点) | 最高 |
什么时候会发生进程调度?当前进程终止、阻塞(等 I/O、wait)、时间片用完、有更高优先级进程就绪(抢占式)、中断处理结束或系统调用返回时(2021-27 四项都可能)。什么时候不能调度?正在处理中断的过程中、在原语(原子操作)中、在操作系统内核程序的临界区中。注意:普通进程访问打印机之类的临界资源时(用户的临界区)可以被调度走(2012-30 选 C 为错误项)。
抢占式:有更重要的进程来了可以把 CPU 抢过来;非抢占式:当前进程主动放弃(结束或阻塞)才调度。
评价调度算法的指标:CPU 利用率(越高越好)、系统吞吐量(单位时间完成的作业数)、周转时间、等待时间、响应时间(交互式系统最看重)。调度算法影响的主要是“等待时间”——一个进程真正要用的 CPU 时间和 I/O 时间是它自己决定的,算法只能改变它在就绪队列里等多久。
- 周转时间是从提交(到达)到完成的全部时间,包括:作业在外存后备队列等待作业调度、进程在就绪队列等待进程调度、在 CPU 上执行、等待 I/O 完成。所以它≥ 运行时间,带权周转时间 ≥ 1,越接近 1 越好(等得越少)。带权周转时间大,说明相对于自己的运行时间,它等得太久了——短作业等 10 秒和长作业等 10 秒,感受完全不同。
- 响应时间是从用户提交请求到系统首次产生响应的时间,交互式系统最看重它;周转时间是批处理系统最看重的。
- CPU 利用率 = CPU 有效工作时间 ÷(有效工作时间 + 空闲等待时间);吞吐量 = 单位时间内完成的作业数(短作业多的系统吞吐量大)。
- 抢占式调度决定“抢不抢”的三条常见原则:优先权原则(高优先级可抢低的)、短进程优先原则(新到的更短可抢)、时间片原则(时间片到就换)。
调度程序本身、闲逛进程和多处理机
调度程序(调度器)由三部分组成
① 排队器:把就绪进程按策略排成队列;② 分派器:从就绪队列取出选中的进程,把 CPU 分给它;③ 上下文切换器:保存旧进程的现场、装入新进程的现场(6.5 节)。
闲逛进程(idle)
没有任何就绪进程时,调度程序就让 CPU 运行闲逛进程。它的优先级最低,只要有进程就绪就立刻让出 CPU;它不需要 CPU 以外的资源,不会被阻塞。有了它,“CPU 总得跑点什么”这件事就统一了。
两种线程下,调度谁
只有用户级线程时,内核看不见线程,调度的单位是进程,同一进程内的线程切换由线程库在用户态完成;有内核级线程时,调度的单位是内核级线程。同一进程内的两个线程之间切换不用换地址空间(页表基址不变),比跨进程的切换快。
多处理机调度 拓展
多个 CPU 时,就绪队列可以公共(所有 CPU 从一个队列取进程,负载均衡好,但要互斥访问队列)或私有(每个 CPU 一个队列,要定期迁移进程做负载均衡)。还要考虑“亲和性”:进程尽量回到上次运行的 CPU,那里的 Cache 里还有它的数据。
多处理机调度:负载均衡与处理器亲和性 拓展
问题引入:一台 8 核电脑,就绪进程该放在一条大队列里让 8 个核轮流来取,还是每个核一条队列?进程这次在核 3 上跑,下次最好还回核 3 吗?
- 负载均衡:让各个 CPU 的工作量大致相同,不让一个 CPU 忙死、另一个闲着。
- 处理器亲和性:进程倾向于留在上次运行的 CPU 上——那个 CPU 的 Cache 里还有它的数据,换核就要重新从主存加载(在 NUMA 机器上换节点还会让访存变慢,第 4 篇 4.5)。软亲和:操作系统尽量但不保证;硬亲和:程序可以指定只能在某些 CPU 上运行。
- 两者是矛盾的:负载均衡要迁移进程,迁移又破坏亲和性,调度器要在两者之间折中。
考纲写的是“调度准则、调度算法、典型机制”,多处理机调度目前只作了解;能说出“公共 / 私有队列、负载均衡、亲和性”三个词及其含义即可。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 进程运行结束时
- B. 正在处理中断的过程中
- C. 进程的时间片用完时
- D. 进程因等待 I/O 而阻塞时
6.7调度算法:一张表 + 画甘特图
| 算法 | 规则 | 抢占? | 饥饿? | 特点 |
|---|---|---|---|---|
| FCFS 先来先服务 | 按到达顺序 | 否 | 否 | 对长作业有利,短作业等得久 |
| SJF 短作业优先 | 选运行时间最短的 | 否(抢占版叫 SRTN 最短剩余时间优先) | 会(长作业一直等) | 平均等待/周转时间最短 |
| HRRN 高响应比优先 | 响应比 = (等待时间 + 要求服务时间)/ 要求服务时间,选最大 | 否 | 不会 | 既照顾短作业,长作业等久了响应比也会变大(2009-24、2011-23) |
| 优先级 | 选优先级最高的(注意题目说“数值大”还是“数值小”优先级高) | 都有 | 会(静态优先级) | 动态优先级可防饥饿 |
| RR 时间片轮转 | 按 FCFS 排队,每人最多跑一个时间片,没跑完排到队尾 | 是(时钟中断) | 不会 | 分时系统用;时间片太大退化为 FCFS,太小切换开销大 |
| 多级反馈队列 | 多个队列,优先级从高到低、时间片从小到大;新进程进第 1 级;一个时间片没跑完就降一级 | 是 | 可能 | 综合性最好;设计时要考虑队列数、各队列优先级和算法、迁移条件(2020-26 全选) |
- 在时间轴上标出每个进程的到达时刻。
- 从 0 开始,看当前时刻已经到达的进程里该选谁(非抢占:只在当前进程结束时选;抢占:每有新进程到达都要重新比较)。
- 画出每一段,写下每个进程的完成时刻。
- 列表算周转 = 完成 − 到达,带权 = 周转/运行,最后求平均。
每种算法的考点细节
| 算法 | 选择题常考的点 | 可用于 |
|---|---|---|
| FCFS | 最简单、公平;有利于长作业、CPU 繁忙型作业,不利于短作业、I/O 繁忙型作业(I/O 型进程每次做完 I/O 回来都要排在长作业后面) | 作业调度、进程调度 |
| SJF / SPF | 所有作业同时到达时,SJF 的平均等待时间、平均周转时间最短(不同时到达时,抢占式的 SRTN 更短);缺点:长作业可能饥饿;运行时间是用户估计的,不一定准;不考虑紧迫程度 | 作业调度、进程调度 |
| HRRN | 响应比 = 1 + 等待时间 / 要求服务时间,每次调度时重新计算。等待时间相同 → 服务时间短的先(照顾短作业,像 SJF);服务时间相同 → 等得久的先(像 FCFS);长作业等久了响应比会升高,不会饥饿(2009-24、2011-23)。非抢占 | 作业调度为主 |
| 优先级 | 静态优先级:创建时定死(依据进程类型、对资源的要求、用户要求),可能饥饿;动态优先级:随等待时间增加而提高、随占用 CPU 时间增加而降低(2016-46 的设计思路)。设置原则:系统进程 > 用户进程,交互型(前台) > 非交互型(后台),I/O 型 > 计算型(让 I/O 设备尽早忙起来,2013-31);时间片用完时适合降低优先级(2010-26) | 作业调度、进程调度 |
| RR | 时间片太大退化为 FCFS,太小切换太频繁、开销大;决定时间片大小的因素:系统响应时间要求、就绪队列中的进程数、系统的处理能力(2017-27 D)。时间片用完 → 就绪态(不是阻塞,2017-27 B 错)。新到达的进程和刚用完时间片的进程同时进队时,一般新到达的排在前面(题目会说明,没说就按这个约定) | 只用于进程调度(分时系统) |
| 多级反馈队列 | ① 设多个就绪队列,第 1 级优先级最高、时间片最小,往下优先级逐级降低、时间片逐级增大(常为加倍);② 新进程进第 1 级队尾,按 FCFS 等调度,一个时间片没做完就降到下一级队尾,最后一级按 RR;③ 第 1~(i−1) 级全空时才调度第 i 级;④ 第 i 级的进程运行时若有新进程进入更高级队列,新进程抢占 CPU,被抢占的进程放回第 i 级队尾。短作业在前几级就能做完(照顾短作业和交互作业),长作业最终也能做完 | 进程调度 |
| 多级队列 | 按进程的类型把就绪队列分成几个(如系统进程、交互进程、批处理进程),每个进程固定在一个队列里,不会在队列之间移动;队列之间按固定优先级(或按比例分时间片)调度,每个队列内部可以用不同的算法(如交互队列用 RR、批处理队列用 FCFS)。和多级反馈队列的区别就在“反馈”:多级反馈队列会根据进程的运行情况把它降到下一级,多级队列不会 | 进程调度 |
一组进程,五种算法:画在一起对比
展开解析
- SJF:t = 3 时 A 完成,只有 B 到了 → B 运行到 9(非抢占,C、D、E 到了也不能打断)。t = 9 时 C(4)、D(5)、E(2)都在,按运行时间 E → C → D。
- HRRN:t = 9 时算响应比:C = (5 + 4)/4 = 2.25,D = (3 + 5)/5 = 1.6,E = (1 + 2)/2 = 1.5 → 选 C;t = 13 时 D = (7 + 5)/5 = 2.4,E = (5 + 2)/2 = 3.5 → 选 E,最后 D。
- SRTN:t = 3 B 开始(剩 6);t = 4 C 到,C 的 4 < B 剩的 5 → 抢占;t = 6 D 到(5),不比 C 剩的 2 短,C 继续到 8;t = 8 就绪的 B(5)、D(5)、E(2)→ E 先;t = 10 B 与 D 剩余时间都是 5,先到的 B 先;B 到 15,D 到 20。
- RR(q = 2):t = 2 A 用完一个时间片(剩 1),恰好 B 到达,按约定 B 先入队 → 队列 [B, A];t = 4 C 到达 → [A, C, B];A 运行 4~5 结束;C 5~7(剩 2),其间 D 在 6 到达 → [B, D, C];B 7~9(剩 2),E 在 8 到达 → [D, C, E, B];之后 D 9~11、C 11~13 完成、E 13~15 完成、B 15~17 完成、D 17~20 完成。
| 周转时间 | A | B | C | D | E | 平均周转 | 平均带权周转 |
|---|---|---|---|---|---|---|---|
| FCFS | 3 | 7 | 9 | 12 | 12 | 8.6 | 2.56 |
| SJF | 3 | 7 | 11 | 14 | 3 | 7.6 | 1.84 |
| HRRN | 3 | 7 | 9 | 14 | 7 | 8.0 | 2.14 |
| SRTN | 3 | 13 | 4 | 14 | 2 | 7.2 | 1.59 |
| RR(q = 1) | 4 | 16 | 13 | 14 | 7 | 10.8 | 2.71 |
| RR(q = 2) | 5 | 15 | 9 | 14 | 7 | 10.0 | 2.54 |
| RR(q = 4) | 3 | 15 | 7 | 14 | 11 | 10.0 | 2.71 |
结论(全部用程序模拟验算):SRTN 的平均周转最短,SJF 次之;HRRN 介于 FCFS 和 SJF 之间;最短的 E 在 FCFS 下周转 12、带权 6,在 SJF 下只有 3——这就是“FCFS 不利于短作业”。RR 平均周转最长,但每个进程到达后很快就能第一次拿到 CPU(响应快),这正是分时系统要的。每种算法都可以用下面的工具换成这组数据验证。
- A. 60ms
- B. 61ms
- C. 70ms
- D. 71ms
展开解析
B优先值越大越优先,抢占式:新到的进程优先值更大就立即抢占。先画甘特图,再用“周转 = 完成 − 到达”。
- 0 ~ 20:只有 P1,运行(P1 还剩 40)。
- 20:P2 到达,10 > 1,抢占,P2 运行;30:P3 到达,100 > 10,抢占,P3 运行 30 ~ 43 完成(P2 还剩 32)。
- 43 ~ 75:P2 运行完;75 ~ 115:P1 运行完。
- 周转:P1 = 115 − 0 = 115,P2 = 75 − 20 = 55,P3 = 43 − 30 = 13;平均 = 183 ÷ 3 = 61ms。
- A 错平均 60 意味着三个周转时间之和是 180;按甘特图逐个核对完成时刻 P3 = 43、P2 = 75、P1 = 115,和是 183,差的 3ms 出在某个完成时刻算早了。
- B 对(115 + 55 + 13) ÷ 3 = 61ms。
- C 错平均 70 意味着周转之和 210,比正确的 183 多 27;按甘特图把完成时刻 43、75、115 逐个减去到达时刻 30、20、0,得 13、55、115,凑不出 210。
- D 错71 = (115 + 55 + 43) ÷ 3,P3 的周转用了它的完成时刻 43,忘了减去它的到达时刻 30。
抢占式调度:每有新进程到达就重新比一次优先级;周转时间一定要减到达时刻。
- A. 4
- B. 5
- C. 6
- D. 7
展开解析
C优先级值越小越优先,抢占式:新到达的进程优先级更高就立刻抢占。每次“选一个进程上 CPU”算一次调度。
- 0:只有 P0,调度 P0(第 1 次)。
- 10:P1(20)、P2(10)到达,P2 比 P0(15)高,抢占,调度 P2(第 2 次)。
- 15:P3(6)到达,抢占 P2,调度 P3(第 3 次);25:P3 完成,调度 P2(第 4 次)。
- 40:P2 完成,调度 P0(第 5 次);130:P0 完成,调度 P1(第 6 次);190:P1 完成,没有进程了。
- 共 6 次。P1 虽然 10 就到了,但优先级 20 最低,一直等到最后。
- A 错4 次只是数了“每个进程第一次上 CPU”;P2 被 P3 抢占后在 25 重新被调度、P0 在 40 重新被调度,这两次也是调度。
- B 错5 次漏了一次:通常是漏了 0 时刻的第一次调度,或漏了 P3 完成后在 25 把 P2 重新调度上来的那一次。
- C 对调度发生在 0、10、15、25、40、130 共 6 次。
- D 错7 次是把 190 时 P1 结束也算了一次;那时已没有就绪进程可选,不发生调度。
数调度次数:每个“选出进程上 CPU”的时刻都算——开头 1 次、每次抢占 1 次、每次进程完成后(还有就绪进程时)1 次。
- A. 54μs
- B. 73μs
- C. 74μs
- D. 75μs
展开解析
D非抢占、优先权大的先:P2(30)→ P3(20)→ P1(10)。每次上 CPU 前先花 1μs 调度切换;周转时间从各进程到达(T − 已等待时间)算起。
- P2:T + 1 开始,T + 25 完成;周转 = 15 + 25 = 40μs。
- P3:T + 26 开始,T + 62 完成;周转 = 18 + 62 = 80μs。
- P1:T + 63 开始,T + 75 完成;周转 = 30 + 75 = 105μs。
- 平均 = (40 + 80 + 105) ÷ 3 = 75μs。
- A 错54 = (25 + 62 + 75) ÷ 3,是从 T 时刻起算的,忘了周转时间要从进程到达算起,漏了表里的“等待时间”。
- B 错73 = (39 + 78 + 102) ÷ 3,三次调度切换的 1μs 开销一次都没算。
- C 错74 = (39 + 79 + 104) ÷ 3,漏了第一次调度的 1μs:从 T 时刻起系统要先做一次调度,P2 才能上 CPU。
- D 对(40 + 80 + 105) ÷ 3 = 75μs。
周转时间 = 完成时刻 − 到达时刻;“已等待时间”要加进去;调度开销每次上 CPU 前都要算。
- A. 25ms
- B. 20ms
- C. 15ms
- D. 10ms
展开解析
C先画甘特图:Q1 时间片轮转 10ms,用完未结束降到 Q2;Q2 短进程优先,只在 Q1 空时调度。等待时间 = 完成时刻 − 到达时刻 − 运行时间。
- 0 ~ 10:P1(剩 20,降入 Q2);10 ~ 20:P2(剩 10,降入 Q2)。
- Q1 空,Q2 短进程优先:20 ~ 30:P2(剩 10 比 P1 的 20 短)完成;30 ~ 50:P1 完成。
- 等待:P1 = 50 − 30 = 20,P2 = 30 − 20 = 10;平均 = 15ms。
- A 错25 = (20 + 30) ÷ 2:P2 那一项用了它的完成时刻 30,没减去它自己运行的 20ms;等待时间 = 周转时间 − 运行时间。
- B 错20 = (10 + 30) ÷ 2,是把 Q2 当成先来先服务(P1 先降入 Q2 先运行 20 ~ 40,P2 40 ~ 50);题目说 Q2 用短进程优先,剩 10 的 P2 先运行。
- C 对(20 + 10) ÷ 2 = 15ms。
- D 错10 只是 P2 的等待时间;P1 在 10 ~ 30 也一直在等,等了 20ms,平均应是 (20 + 10) ÷ 2。
多级反馈队列:先看本级的规则(RR / SPF / FCFS),再看降级条件。等待时间 = 周转时间 − 运行时间。
- A. 200ms
- B. 205ms
- C. 250ms
- D. 295ms
展开解析
CP 需要 25 ÷ 5 = 5 个时间片。其他 9 个进程都比它长,P 完成前每一轮都还在;P 在队尾,每轮都最后一个运行。
- 一轮 = 10 个进程 × 5ms = 50ms,P 在每轮的最后 5ms 运行。
- P 的第 5 个时间片在第 5 轮末结束:5 × 50 = 250ms。初始就在就绪队列,到达时刻为 0,周转时间 = 250ms。
- A 错200 = 4 × 50,只算了 4 轮;P 要 5 个时间片,第 5 轮末才完成。
- B 错205 = 4 × 50 + 5,相当于 P 在第 5 轮排在最前面;但其他进程都没做完,轮转顺序不变,P 仍排在第 5 轮的最后。
- C 对5 轮 × 50ms = 250ms。
- D 错295 = 250 + 9 × 5,相当于 P 做完后还要再等其余 9 个进程各跑一个时间片;但 P 在第 5 轮末已用满 25ms,此时就结束了。
RR 中求某进程完成时刻:先数它要几个时间片,再看它每轮排第几、每轮有多少进程还没完成。
- A. 240ms
- B. 260ms
- C. 340ms
- D. 360ms
展开解析
B让 CPU 和 I/O 尽量同时忙。画时间轴:一个作业做 I/O 时,CPU 去算另一个作业。
- 0 ~ 60:P1 计算(P2 在 5 到达,等着)。
- 60 ~ 180:P2 计算 120;同时 P1 做 I/O 60 ~ 140。
- 180 ~ 200:P1 计算 20,P1 完成;同时 P2 做 I/O 180 ~ 220。
- 200 ~ 220:CPU 空等 P2 的 I/O;220 ~ 260:P2 计算 40,完成。共 260ms。
- 如果 140 时让 P1 抢占 P2:P2 的计算推迟到 200 才完,I/O 200 ~ 240,再算到 280,反而更慢。程序枚举了所有调度方式,最少就是 260。
- A 错240 = 60 + 20 + 120 + 40,只是四段计算时间之和,等于假设 CPU 从不空闲;但 P2 做最后的 I/O(180 ~ 220)时 P1 只剩 20ms 计算,CPU 至少空等 20ms。
- B 对按上面的时间轴,260ms 两个作业都完成,而且已是最少。
- C 错340 比完全串行(360)只少 20,几乎没让 CPU 和 I/O 重叠;而按上面“P1 做 I/O 时 P2 计算”的排法 260 就能完成,所以 340 不是最少。
- D 错360 = 160 + 200,是一个作业全部做完再做另一个的单道串行时间,没有利用多道程序让 CPU 与 I/O 并行。
求“最少完成时间”:画 CPU、I/O 两条时间轴,一个作业做 I/O 时 CPU 立刻算另一个;抢占不一定更快。同类:2016-24(3 个作业各“输入 2、计算 3、输出 4”,三个设备各一台,流水起来最少 17ms)。
其他选择题:2014-23 不可能导致饥饿的是时间片轮转;2017-23 t=2 时 FCFS 选 J1、SJF 选 J3;2025-25 优先队列用单链表按优先级有序存放,插入 O(n)、选出(取队头)O(1)。
FCFS 的护航效应,和 RR 的时间片该取多大
问题引入:上面的对比例子里,FCFS 的平均周转最差之一。原因可以用一个更极端的场景说清楚:一辆慢吞吞的大卡车开在单车道最前面,后面几辆小轿车只能跟着它爬——这叫护航效应(convoy effect)。
教材说法:FCFS 下,一个长的(CPU 繁忙型)进程先占了 CPU,后面许多短进程(常常是 I/O 繁忙型的)都得排在它后面等很久,平均等待时间、平均周转时间大大增加;而且这些短进程的 I/O 设备也跟着闲着。SJF / SPF 正是为了解决这个问题。(SJF 用于作业调度时叫“短作业优先”,用于进程调度时叫 SPF“短进程优先”,规则相同。)
verify/08/p06_calc.py 验算)。RR 的时间片取多大:每次换进程都要花一次上下文切换的时间 s(保存、恢复现场,可能还要刷 TLB)。时间片为 q 时,切换开销约占 s ÷ (q + s)。设 s = 0.1ms:
| 时间片 q | 10ms | 4ms | 1ms |
|---|---|---|---|
| 切换开销占比 s ÷ (q + s) | 0.99% | 2.44% | 9.09% |
时间片太小,CPU 有近一成时间在“换人”而不是干活;时间片太大,RR 退化成 FCFS,护航效应又回来了,交互响应变慢。经验上让时间片略大于一次典型交互所需的时间,使大多数交互请求在一个时间片内做完。决定时间片大小的因素:系统的响应时间要求、就绪进程数、系统处理能力(2017-27 D)。
- 为什么说 FCFS“有利于 CPU 繁忙型作业、不利于 I/O 繁忙型作业”?
答案
I/O 繁忙型进程每次只用 CPU 一小会儿就去做 I/O,做完回来又要排在长的 CPU 繁忙型进程后面,等的时间远大于它要用的 CPU 时间;它的 I/O 设备也因此经常闲着(护航效应)。 - 切换开销 s = 0.2ms,要让开销占比不超过 5%,时间片至少取多大?
答案
s ÷ (q + s) ≤ 5% → q + s ≥ 4ms → q ≥ 3.8ms。
实时系统怎么调度:EDF 与 RMS 拓展
问题引入:实时系统的任务常常是周期性的(每 5ms 采样一次传感器、每 7ms 刷新一次控制量),每个任务的每次执行都有截止时间。前面的算法关心平均周转,实时调度关心的是“有没有任务错过截止时间”。
| 算法 | 规则 | 可调度条件(周期任务、截止时间 = 周期) |
|---|---|---|
| EDF 最早截止时间优先 | 动态优先级:谁的截止时间最近谁先运行,可抢占 | 总利用率 U = Σ Ci/Pi ≤ 1 就一定能调度 |
| RMS 速率单调调度 | 静态优先级:周期越短(频率越高)优先级越高,可抢占 | U ≤ n(21/n − 1) 一定能调度(n = 2 时约 0.828);超过这个界不一定不行,要实际排一下 |
跟我做一遍(已用 verify/08/p06_calc.py 逐时刻模拟到 35):T1 每 5 个单位来一次、每次要 2;T2 每 7 个单位来一次、每次要 4。U = 2/5 + 4/7 ≈ 0.971 ≤ 1,但超过 RMS 的界 0.828。
408 目前不考实时调度的计算;了解“EDF 按截止时间、动态优先级;RMS 按周期、静态优先级”即可。它也说明了第 1 篇的结论:实时系统追求的是“保证按时”,而不是平均周转最短。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 14ms
- B. 16ms
- C. 18ms
- D. 24ms
展开解析
A三台设备像流水线一样重叠工作。输入设备:J1 0~3、J2 3~6、J3 6~9。CPU:J1 3~5、J2 6~8、J3 9~11。输出设备:J1 5~8、J2 8~11、J3 11~14。
变化点:2016-24 中计算和输出较长(3ms、4ms)。一般规律:总时间 = 一个作业走完全程的时间 + (作业数 − 1) × 最慢一段的时间,这里 = 8 + 2 × 3 = 14ms。D 是完全不重叠的结果。
- A. 30ms
- B. 35ms
- C. 40ms
- D. 45ms
展开解析
B0~10 P1;10 时 P2(5)到达,抢占 P1,运行到 30;15 时 P3(3)到达,优先级低于 P2,不抢占;30~40 P3;40~60 P1。周转时间:P1 60、P2 20、P3 25,平均 105 ÷ 3 = 35ms。
变化点:P3 在 P2 运行期间到达,但优先级比 P2 低,不能抢占,只能排在 P1 前面。抢占式调度也不是“新来的就抢”,要比较优先级。
- A. 10ms
- B. 12.5ms
- C. 15ms
- D. 20ms
展开解析
BQ1:P1 运行 0~10,转入 Q2(剩 30);P2 运行 10~20,转入 Q2(剩 5)。Q2 按短进程优先:P2 运行 20~25 结束,P1 运行 25~55 结束。等待时间:P1 = 55 − 40 = 15,P2 = 25 − 15 = 10,平均 12.5ms。
变化点:2019-27 中 P1、P2 为 30ms 和 20ms(平均等待 15ms)。进入 Q2 后要按剩余时间比较:P2 剩 5ms,比 P1 的 30ms 短,先运行。
- A. 84ms
- B. 96ms
- C. 100ms
- D. 108ms
展开解析
BP 需要 3 个时间片。其他进程都至少需要 12ms,所以前 3 轮中每轮 8 个进程都各运行满 4ms。P 排在队尾,在第 3 轮的最后完成:3 × 8 × 4 = 96ms。
变化点:2024-30 是 10 个进程、时间片 5ms、P 需 25ms(5 轮 × 10 × 5 = 250ms)。关键是“P 最短”:P 完成之前,其他进程都还没结束,每轮都满员。
- A. 14、2
- B. 14、4
- C. 7.33、2
- D. 20、4
展开解析
AFCFS:等待 0、20、22,平均 42 ÷ 3 = 14。SJF:按 P2、P3、P1,等待 0、2、4,平均 6 ÷ 3 = 2。一个长进程排在最前,两个短进程只能跟着等——这就是护航效应。
考点:FCFS 的护航效应。
- 三个作业 J1(0 到达,运行 10)、J2(2,4)、J3(4,2),分别用 FCFS、SJF、HRRN 求平均周转时间。
答案
FCFS:J1 0~10、J2 10~14、J3 14~16,周转 10、12、12,平均 11.33。SJF:J1 先跑(0 时只有它)到 10,然后 J3(2)10~12、J2 12~16,周转 10、14、8,平均 10.67。HRRN:10 时 J2 响应比 (8+4)/4 = 3,J3 (6+2)/2 = 4 → 先 J3,结果同 SJF。(可用上面的工具验证。) - 同一进程内两个线程切换,需要换页表基址寄存器吗?
答案
不需要,它们共享地址空间(同一张页表),只换 PC、寄存器、栈指针。 - “一个进程被唤醒,就意味着它马上会运行”对吗?
答案
不对。被唤醒是阻塞 → 就绪,要等调度程序选中才会运行(抢占式优先级调度下,若它优先级更高可能立即抢占)。 - 管道满了,写进程会怎样?
答案
阻塞,直到读进程读走数据腾出空间。 - 单 CPU 系统中有 6 个进程,处于就绪态的进程最多几个?处于阻塞态的最多几个?
答案
就绪最多 5 个(有 1 个在运行);阻塞最多 6 个(全都在等事件,CPU 运行闲逛进程)。 - 下列转换哪些不可能发生:运行→就绪、就绪→阻塞、阻塞→运行、阻塞→就绪?
答案
就绪→阻塞、阻塞→运行 不可能。 - 下列哪些是同一进程内各线程共享的:堆、全局变量、某线程的局部变量、打开的文件、PC?
答案
堆、全局变量、打开的文件共享;某线程的局部变量在它自己的栈里,PC 是线程私有寄存器,都不共享(不过同进程线程若拿到了别的线程栈变量的地址,技术上也能访问,考试按“不共享”答)。 - 用户级线程的系统中,一个线程执行 read 系统调用阻塞了,同进程的其他线程还能运行吗?换成内核级线程呢?
答案
纯用户级线程(多对一):内核只看得见进程,整个进程被阻塞,其他线程也不能运行。内核级线程:只阻塞这一个线程,其他线程照常被调度。 - 作业 A、B、C 同时到达,运行时间 6、2、4,采用 SJF,平均周转时间是多少?若按 FCFS(到达顺序 A、B、C)呢?
答案
SJF:B 0~2、C 2~6、A 6~12,平均 (2 + 6 + 12)/3 ≈ 6.67;FCFS:A 0~6、B 6~8、C 8~12,平均 (6 + 8 + 12)/3 ≈ 8.67。同时到达时 SJF 的平均周转最短。
6.✎本篇真题闯关(39 题)
本篇各小节挂的真题全部在这里,按小节排好。可以展开后一道道做,也可以用弹窗模式连续做。做题记录(对/错)只保存在你这台电脑的浏览器里,小节标题下的题号会随之变绿或变红。
并发:同步互斥(PV 大题)与死锁
多个进程(线程)并发执行、共享数据时,结果可能随执行顺序不同而出错。这一篇先从汇编层面看清“为什么会错”,再学操作系统的解决工具——信号量和 PV 操作,这是每年一道 7~8 分综合题;最后讲并发的另一个麻烦:死锁和银行家算法。
7.1融合为什么并发会出错:从一条 x++ 说起
C 语言里 x = x + 1 看起来是一步,但编译成机器指令是三步(第 3 篇):
load R1, x ; ① 把内存里的 x 读进寄存器 inc R1 ; ② 寄存器加 1 store x, R1 ; ③ 写回内存
进程切换可以发生在任意两条指令之间(每条指令结束都会检查中断,第 5 篇),而每个进程有自己的一套寄存器现场。于是:
临界资源:一次只允许一个进程使用的资源(打印机、共享变量、缓冲区)。临界区:进程中访问临界资源的那段代码。互斥:同一时刻只能有一个进程在临界区。同步:进程之间有先后次序要求(B 必须等 A 做完某事)。同步也叫直接制约关系(为了合作),互斥也叫间接制约关系(因为争用同一资源)。
判断“要不要互斥”:两个操作访问同一个共享变量,且至少一个是写。2016-30:P1 里的两个线程 x+=1 和 x+=2 要互斥;a=1 与 a=2 是各自的局部变量不用;P1 的 x 和 P2 的 x 是不同进程的变量,互不相干。2026-27 的 Bernstein 条件说的就是这件事:两进程能安全并发 ⇔ 一个的写集合与另一个的读集合、写集合都不相交(读读不冲突)。
访问临界资源的代码结构:进入区(检查能不能进、上锁)→ 临界区 → 退出区(解锁)→ 剩余区。互斥机制要遵循四条准则:
| 准则 | 意思 | 是否必须 |
|---|---|---|
| 空闲让进 | 临界区没人时,想进的应该能进 | 必须 |
| 忙则等待 | 有人在里面,别人必须等 | 必须 |
| 有限等待 | 等待进入的时间有限,不会“饿死” | 必须 |
| 让权等待 | 进不去时应该释放 CPU(阻塞),不要空转(忙等) | 最好有,不是必须(2020-32 选前三条) |
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 只能为 3
- B. 可能为 1、2 或 3
- C. 可能为 0、1、2 或 3
- D. 可能为 2 或 3
展开解析
B最好的情况,三个线程依次执行,x = 3。最坏的情况,三个线程都先读到 0,然后各自写回 1,x = 1。也可能两个线程冲突、一个不冲突,x = 2。至少有一次写回在最后,而写回的值至少是 1,所以不可能是 0。
变化点:2018-25 只有两个线程(可能为 1 或 2)。线程变成 n 个,结果的范围是 1 ~ n。
a = x;(a 是 T1 的局部变量),线程 T2 执行 x = x + 1;,线程 T3 执行 b = 5;(b 是 T3 的局部变量)。下列操作对中,需要互斥执行的是( )。- A. T1 的 a = x 与 T3 的 b = 5
- B. T1 的 a = x 与 T2 的 x = x + 1
- C. T3 的 b = 5 与 T2 的 x = x + 1
- D. 两个不同进程中分别对各自的全局变量 x 做加 1
7.2融合实现互斥:软件方法与硬件原子指令
软件方法:只用普通变量
| 方法 | 做法 | 问题 |
|---|---|---|
| 单标志法 | turn 表示轮到谁,只有轮到自己才能进 | 必须严格轮流,违背“空闲让进” |
| 双标志先检查 | 先看对方的 flag,再把自己的 flag 置真 | “检查”和“上锁”之间可能被切换,两个都进去,违背“忙则等待” |
| 双标志后检查 | 先置自己 flag,再看对方 | 两个都置了 flag 后互相等,都进不去,违背“空闲让进/有限等待” |
| Peterson 算法 | 先置 flag[i] = true 表示想进,再 turn = j 表示谦让,while(flag[j] && turn==j); | 满足前三条,能互斥、不会饥饿(2010-27 选 D),但仍是忙等,不满足让权等待 |
四种软件方法的代码长得很像,真题会给出代码让你判断违反了哪条准则(2010-27),所以要能看懂 Peterson 算法为什么对:
bool flag[2] = {false, false}; // flag[i]:Pi 想进临界区
int turn = 0; // 两人都想进时,让谁先进
P0: flag[0] = true; turn = 1; while (flag[1] && turn == 1); 临界区; flag[0] = false;
P1: flag[1] = true; turn = 0; while (flag[0] && turn == 0); 临界区; flag[1] = false;
为什么对:对方不想进(flag 为 false)时,自己直接进——空闲让进;两人同时想进时,turn 只能是其中一个值,后写 turn 的那个把机会让给了对方,所以恰好一人进入——忙则等待;进去的人出来时把自己的 flag 置 false,等着的人就能进——有限等待,不会饥饿。只是等的时候一直在 while 里转,不让出 CPU。若去掉 turn 就变成“双标志后检查”(可能都进不去),若先检查对方 flag 再置自己的 flag 就是“双标志先检查”(可能都进去)。
三种错误的软件方法:各错在哪一步(时序反例)
问题引入:上表一句话说“违背空闲让进”“违背忙则等待”,真题却是给出代码让你自己判断(2010-27)。判断的办法只有一个:构造一种交替执行的顺序,让它出错。下面把三个反例画出来。
// ① 单标志法:turn 表示轮到谁 P0: while (turn != 0); 临界区; turn = 1; P1: while (turn != 1); 临界区; turn = 0; // ② 双标志先检查:先看对方,再举自己的旗 P0: while (flag[1]); flag[0] = true; 临界区; flag[0] = false; // P1 对称 // ③ 双标志后检查:先举自己的旗,再看对方 P0: flag[0] = true; while (flag[1]); 临界区; flag[0] = false; // P1 对称
| 方法 | 满足的准则 | 违反的准则 | 反例的关键 |
|---|---|---|---|
| 单标志法 | 忙则等待 | 空闲让进 | 对方不想进时,自己也进不去 |
| 双标志先检查 | 空闲让进 | 忙则等待 | 检查和上锁不是一步完成,中间被切换 |
| 双标志后检查 | 忙则等待 | 空闲让进、有限等待 | 同时举旗,互相等待 |
| Peterson 算法 | 空闲让进、忙则等待、有限等待 | 让权等待(忙等) | —— |
真题怎么考:2010-27 给出 Peterson 算法的代码问“能否互斥、会不会饥饿”(能互斥、不饥饿)。判别法:拿到一段代码,先试“两人同时想进”(看会不会都进、或都卡住),再试“只有一个人想进”(看能不能进)。易错:Peterson 只能用于两个进程(推广到 n 个要用更复杂的算法),而且仍然忙等。
硬件方法:让“检查+上锁”一步完成
软件方法的根本麻烦是“读锁”和“写锁”之间可能被打断。硬件的思路:提供一条原子指令,读和写在一条指令内完成,执行期间不会被打断(不会有中断插进来,多核时总线也被锁住)。
② TestAndSet(TSL)/ Swap
TSL(&lock):原子地“返回 lock 旧值并把 lock 置为 true”。while (TSL(&lock)); 拿不到锁就一直循环——忙等,不让权(2016-27 选“等待的进程不会主动放弃 CPU”)。Swap 同理:原子地交换两个变量。硬件指令法(TSL、Swap)的优缺点:优点——适用于任意数目的进程,单处理机、多处理机都能用;简单;每个临界区设一个锁变量就能支持多个临界区。缺点——忙等,不满足让权等待;从等待的进程中随机选一个进入,可能有进程一直抢不到(饥饿)。
bool lock = false; // 共享 // 每个进程: bool key = true; while (key == true) swap(key, lock); // 进入区:换到 false 才说明锁原来是空的 临界区; lock = false; // 退出区:释放锁
互斥锁(mutex lock)是最简单的互斥工具:进临界区前 acquire() 获得锁,出来时 release() 释放锁;acquire 与 release 本身必须是原子操作(用上面的 TSL、Swap 或关中断实现)。自旋锁是拿不到锁就在循环里“转圈”一直检查(忙等)的互斥锁:不需要进程切换,适合多处理器、临界区很短的场合(另一个 CPU 上的持锁者很快就会释放);单处理器上自旋只是白白浪费时间片。能让权等待(拿不到就阻塞)的是信号量。
key = TRUE; if (key == TRUE) swap key, lock;,退出区写成 lock = TRUE;。(1)哪些语句错了?改正(不增加语句条数)。(2)能否用函数 void newSwap(bool *a, bool *b){ bool temp=*a; *a=*b; *b=temp; } 的调用 newSwap(&key, &lock) 代替 swap 指令?为什么?展开解析
(1)if 改成 while:只换一次,如果换回来的 key 还是 TRUE(锁被别人占着),应该继续尝试,而不是直接进临界区。退出区应是 lock = FALSE(释放锁)。
(2)不能。newSwap 是几条普通指令(读 *b、写 *a、写 *b),执行中间可能被中断、切换,两个线程可能都读到 lock = FALSE,都进入临界区。硬件 swap 指令的价值就在于原子性。
wait(S){ while(S<=0); S=S-1; },signal(S){ S=S+1; }。方法 1:wait 中“关中断; while(S<=0); S=S−1; 开中断;”;方法 2:wait 中“关中断; while(S<=0){ 开中断; 关中断; } S=S−1; 开中断;”,signal 都是“关中断; S=S+1; 开中断;”。(1)为什么 wait、signal 对 S 的访问必须互斥?(2)两种方法是否正确?(3)用户程序能否用开/关中断指令实现临界区互斥?展开解析
(1)S 是多个进程共享的变量,wait、signal 都要读写它,和 x++ 一样会出竞争条件。
(2)方法 1 错:若 S ≤ 0,在关中断状态下死循环,时钟中断进不来,别的进程永远没机会执行 signal 把 S 加上去 → 系统卡死。方法 2 对:循环里短暂开中断,让别的进程有机会运行并修改 S。
(3)不能,开/关中断是特权指令,只能在内核态执行。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 满足“让权等待”
- B. 等待进入临界区的进程会不断测试锁,处于忙等状态
- C. 不会出现饥饿
- D. 必须在关中断的状态下执行
- A. 单标志法
- B. 双标志后检查法
- C. 双标志先检查法
- D. Peterson 算法
展开解析
C双标志先检查:P0 查到 flag[1] = false、还没来得及举旗就被切换,P1 也查到 flag[0] = false,于是两人都进去,违反忙则等待。A 违反的是空闲让进(必须轮流);B 可能两人都进不去;D 能正确互斥。
考点:三种错误的软件方法。
7.3信号量:计数器 + 等待队列
整型信号量拿不到就空转(忙等)。实际用的是记录型信号量:一个整数 value 加一个阻塞队列。P、V 操作由操作系统用原语实现(不可被打断)。
typedef struct { int value; struct process *L; } semaphore; // 记录型信号量:计数 + 阻塞队列
void wait(semaphore S){ // P 操作(原语)
S.value--;
if (S.value < 0) { 把当前进程插入 S.L; block(S.L); } // 资源不够:阻塞自己,让出 CPU
}
void signal(semaphore S){ // V 操作(原语)
S.value++;
if (S.value <= 0) { 从 S.L 取出一个进程 P; wakeup(P); } // 还有人在等:唤醒一个(阻塞 → 就绪)
}
value > 0:还有 value 个资源可用;value = 0:资源刚好用完,没人等;value < 0:|value| 个进程在阻塞队列里等。
取值范围:m 个同类资源(初值 m)被 n 个进程竞争、每个进程每次申请一个时(n > m),value 最大是 m(没人用),最小是 m − n(全部 n 个进程都执行了 P):范围 m − n ~ m。例:5 个进程共享一个最多允许 2 个进程同时进入的区域 → 信号量范围 −3 ~ 2。
跟着一个例子走一遍:打印机有 2 台,信号量 S 初值 2,进程 A、B、C、D 依次来申请:
| 事件 | S.value | S 的阻塞队列 | 说明 |
|---|---|---|---|
| 初始 | 2 | 空 | 2 台可用 |
| A 执行 P(S) | 1 | 空 | value ≥ 0,A 拿到一台 |
| B 执行 P(S) | 0 | 空 | B 拿到最后一台 |
| C 执行 P(S) | −1 | C | value < 0,C 阻塞(让出 CPU,不空转) |
| D 执行 P(S) | −2 | C、D | |−2| = 2 个进程在等 |
| A 执行 V(S) | −1 | D | value ≤ 0,说明有人等 → 唤醒 C(C 直接得到 A 还回的那台) |
| B 执行 V(S) | 0 | 空 | 唤醒 D |
| C、D 各执行 V(S) | 2 | 空 | 回到初始 |
注意:被唤醒的进程醒来后不会再做一次减 1——它在执行 P 的时候已经减过了,醒来直接拥有资源。
| 用途 | 初值 | 写法 |
|---|---|---|
| 互斥:一次只许一个进 | mutex = 1 | 临界区前 P(mutex),后 V(mutex),成对出现在同一个进程里 |
| 同步:A 做完 x,B 才能做 y | S = 0 | “前 V 后 P”:A 在 x 之后 V(S);B 在 y 之前 P(S) |
| 资源计数:有 n 个同类资源 | S = n | 用之前 P,还回去 V(缓冲区空位 empty = n,产品 full = 0) |
整型信号量 vs 记录型信号量:差在“等不到时干什么”
问题引入:两种信号量都叫 wait / signal,2021-45 考的是整型信号量,选择题问的“让权等待”却只有记录型信号量能做到。差别就在 wait 里那一行:资源不够时,是原地转圈,还是去排队睡觉。
// 整型信号量:S 只是一个整数 wait(S) { while (S <= 0); // 不够就空转 S = S - 1; } signal(S){ S = S + 1; }
// 记录型信号量:整数 + 阻塞队列 wait(S) { S.value--; if (S.value < 0) block(S.L); } // 不够就去睡 signal(S){ S.value++; if (S.value <= 0) wakeup(S.L 中的一个); }
| 整型信号量 | 记录型信号量 | |
|---|---|---|
| 数据结构 | 一个整数 S | 整数 value + 阻塞队列 L |
| 资源不够时 | while 循环反复测试(忙等) | 把自己放进队列、阻塞(让权等待) |
| 值能否为负 | 不会(先等到 S > 0 才减) | 可以,|value| = 等待的进程数 |
| 让权等待 | 不满足 | 满足 |
| 真题 | 2021-45(用关中断实现 wait 的原子性) | 2018-32、2026-26、2010-25 |
注意:整型信号量的 wait 也必须原子执行(2021-45 第 1 问),但它在 while 里等待时又不能一直关着中断(方法 1 会把系统卡死),所以要在循环里短暂开中断——这正说明整型信号量“忙等”的尴尬。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 4、1
- B. 3、1
- C. 4、2
- D. 5、1
展开解析
AS = −1 表示有 1 个进程在 S 上阻塞,资源全部被占用,即 4 个进程正在访问资源。另外 1 个进程还没有执行 wait,或已经访问完了。
变化点:2026-26 是初值 5、当前 −2(答案 5、2)。记住:S < 0 时,|S| = 阻塞的进程数,正在使用资源的进程数 = 初值。
Ⅰ. Peterson 算法 Ⅱ. TestAndSet 指令 Ⅲ. 记录型信号量 Ⅳ. 管程
- A. 仅Ⅰ
- B. 仅Ⅰ、Ⅱ
- C. 仅Ⅲ、Ⅳ
- D. 仅Ⅱ
展开解析
BPeterson 算法和硬件指令方法都靠循环测试来等待,是忙等。记录型信号量和管程在条件不满足时把进程阻塞、放进等待队列,让出 CPU。
变化点:2018-32 问“能实现让权等待的”(信号量),这里反过来问“忙等的”。
- A. 记录型信号量的值为负时,其绝对值等于在该信号量上阻塞的进程数
- B. 整型信号量满足“让权等待”
- C. 整型信号量的值可以为负数
- D. 记录型信号量的 wait 操作在资源不足时循环测试信号量的值
展开解析
A记录型信号量 value < 0 时,|value| 就是阻塞队列里的进程数。B 错:整型信号量拿不到就 while 空转,是忙等。C 错:整型信号量要等到 S > 0 才减,不会为负。D 说的是整型信号量的做法,记录型会把进程阻塞。
考点:整型 vs 记录型信号量。
7.4PV 大题五步法(配真题)
- 找进程:有几类进程,每类进程循环做哪几个动作(先把“骨架”写出来)。
- 找约束:逐对问——① 哪些动作要互斥(共享同一个东西)?② 哪些动作有先后(同步)?③ 有没有数量上限(容量、座位数)?
- 设信号量:每个约束一个信号量,写清含义和初值(评分点)。
- 放 P、V:互斥的 P/V 紧紧夹住临界区;同步“前 V 后 P”;计数资源“用前 P、还后 V”。
- 检查:先 P 同步/资源信号量,再 P 互斥信号量,否则可能死锁(拿着 mutex 去等 empty,而能释放 empty 的进程需要先拿 mutex);V 的先后顺序则可以随意交换——V 从不阻塞,换顺序最多改变“先唤醒谁”,不会死锁(生产者写“V(mutex); V(full);”或“V(full); V(mutex);”都对)。两条结论都已用状态空间穷举验证:2 个生产者 + 2 个消费者、缓冲区 2,P 顺序颠倒时可达死锁状态,V 顺序互换时 0 死锁、0 越界(
verify/08/p13/pv.py)。最后对照题意逐句检查。
类型 1:前驱关系(最简单,先练这个)
semaphore SAC = 0, SBC = 0, SCE = 0, SDE = 0; // 各对应一条先后关系 A(){ 操作A; V(SAC); } B(){ 操作B; V(SBC); } C(){ P(SAC); P(SBC); 操作C; V(SCE); } D(){ 操作D; V(SDE); } E(){ P(SCE); P(SDE); 操作E; }
展开解析
同一个线程内部的先后(B→C→D、A→E→F)本来就按顺序执行,不用信号量。只剩跨线程的两条:A→C、C→E。
semaphore AC = 0; // A 完成后才能执行 C semaphore CE = 0; // C 完成后才能执行 E T1(){ A; V(AC); P(CE); E; F; } T2(){ B; P(AC); C; V(CE); D; }
常见扣分:给每条边都设信号量(多余的不扣分但浪费时间)、把 V(CE) 放在 D 之后(D 与 E 没有先后要求,会降低并发度)。
类型 2:生产者—消费者(缓冲区)
semaphore mutex = 1, empty = n, full = 0;
producer(){ while(1){ 生产一个产品;
P(empty); P(mutex); 放入缓冲区; V(mutex); V(full); } }
consumer(){ while(1){
P(full); P(mutex); 从缓冲区取出; V(mutex); V(empty); 消费; } }
// 如果把生产者的两个 P 颠倒:先 P(mutex) 再 P(empty),缓冲区满时生产者拿着 mutex 睡着,
// 消费者要取产品却拿不到 mutex → 死锁。所以:先 P 资源,再 P 互斥。
展开解析
约束:缓冲区互斥(mutex);有空位才能放(empty = N);有奇数 P2 才能取(odd = 0);有偶数 P3 才能取(even = 0)。P1 放完根据奇偶决定 V 哪个。
semaphore mutex = 1; // 缓冲区互斥 semaphore empty = N; // 空单元数 semaphore odd = 0, even = 0; // 缓冲区中奇数、偶数的个数 P1(){ while(1){ x = produce(); P(empty); P(mutex); put(); V(mutex); if (x % 2 == 0) V(even); else V(odd); } } P2(){ while(1){ P(odd); P(mutex); getodd(); V(mutex); V(empty); countodd(); } } P3(){ while(1){ P(even); P(mutex); geteven(); V(mutex); V(empty); counteven(); } }
展开解析
在标准模型上加一个消费者之间的互斥信号量,把“连续取 10 次”整个包起来;生产者不受影响,仍可在这 10 次之间往里放。
semaphore empty = 1000, full = 0; semaphore mutex = 1; // 互斥访问缓冲区 semaphore mutex2 = 1; // 消费者之间互斥:保证一个消费者连续取 10 件 producer(){ while(1){ 生产; P(empty); P(mutex); 放入; V(mutex); V(full); } } consumer(){ while(1){ P(mutex2); for (int i = 0; i < 10; i++){ P(full); P(mutex); 取出; V(mutex); V(empty); 消费; } V(mutex2); } }
同类:2015-45 两人通过信箱辩论(每个信箱一组 full/empty/mutex,A 信箱 full=x、empty=M−x);2024-46 缓冲区 B 放一个分组,B 空才能写、非空才能读或改((2) 只需一个 full=0:P1 写完 V(full),P2 先 P(full) 再读;(3) 两个进程都改 → 一个 mutex=1);2025-45 植树(铁锹互斥 mutex=1,最多 3 个未种的坑 pits=3,已挖的坑 hole=0,待浇水的苗 water=0:甲 P(pits)P(mutex)挖V(mutex)V(hole);乙 P(hole) 放苗 P(mutex) 填土 V(mutex) V(pits) V(water);丙 P(water) 浇水——水桶只有丙用,不需要信号量)。这几道的完整解答点小节标题下的题号查看。
类型 3:有容量的“座位/房间”+ 服务
展开解析
约束:座位上限 10(empty = 10);取号机互斥(mutex = 1);有顾客营业员才叫号(full = 0,顾客→营业员);叫到号顾客才被服务(service = 0,营业员→顾客)。
semaphore empty = 10; // 空座位数 semaphore mutex = 1; // 取号机互斥 semaphore full = 0; // 已就座等待的顾客数 semaphore service = 0; // 叫号(营业员通知顾客) 顾客(){ P(empty); P(mutex); 取号; V(mutex); V(full); P(service); 获得服务; } 营业员(){ while(1){ P(full); V(empty); V(service); 叫号; 为顾客服务; } }
同类:2013-45 博物馆最多 500 人、出入口一次一人:empty = 500,mutex = 1;参观者 P(empty); P(mutex); 进门; V(mutex); 参观; P(mutex); 出门; V(mutex); V(empty)。
类型 4:哲学家就餐与读者写者(经典模板)
展开解析
经典哲学家问题的死锁:每人都拿起左边的筷子,然后都等右边的。破解办法之一:同时去抢筷子的人最多 n−1 个,这样至少有一人能拿到两根。这里正好用碗来限制:碗数 m < n 时本身就限制了人数;m ≥ n 时只放 n−1 个碗的“名额”。
semaphore bowl = min(n-1, m); // 能同时去拿筷子的人数上限 semaphore chopstick[n] = {1, 1, …, 1}; // 每根筷子互斥 philosopher(int i){ while(1){ 思考; P(bowl); P(chopstick[i]); P(chopstick[(i+1)%n]); 就餐; V(chopstick[i]); V(chopstick[(i+1)%n]); V(bowl); } }
// 读者—写者(读者优先)模板:多个读者可同时读;写者与任何人互斥 int count = 0; // 正在读的读者数 semaphore rw = 1; // 读写互斥(写者之间也互斥) semaphore mutex = 1; // 保护 count writer(){ P(rw); 写; V(rw); } reader(){ P(mutex); if (count == 0) P(rw); count++; V(mutex); // 第一个读者负责上锁 读; P(mutex); count--; if (count == 0) V(rw); V(mutex); } // 最后一个读者负责解锁
“最大并发”型:2017-46 三个线程共享复数 x、y、z——thread1 读 x、y,thread2 读 y、z,thread3 写 y、z。读和读可以并发,读写要互斥,所以 y 要用两个互斥信号量(一个管 t1 与 t3、一个管 t2 与 t3),z 一个(t2 与 t3),这样 t1、t2 能同时读 y。
经典模板补全:大题几乎都是它们的变形
408 的 PV 大题很少出“全新”的问题,基本是下面几个经典问题换了个场景。每个模板都要记住信号量设了哪几个、初值多少、P/V 放在哪。完整的做题流程和评分要点见第 11 篇 11.9 PV 专题。
展开模板
semaphore plate = 1; // 盘子里还能放几个水果 semaphore apple = 0; // 盘里的苹果数 semaphore orange = 0; // 盘里的橘子数 dad(){ while(1){ 准备苹果; P(plate); 放苹果; V(apple); } } mom(){ while(1){ 准备橘子; P(plate); 放橘子; V(orange); } } daughter(){ while(1){ P(apple); 取苹果; V(plate); 吃苹果; } } son(){ while(1){ P(orange); 取橘子; V(plate); 吃橘子; } }
为什么这里不用 mutex?盘子容量是 1,plate、apple、orange 三个信号量里同一时刻最多只有一个是 1,本身就保证了同一时刻最多一个进程在访问盘子。若盘子容量大于 1(比如 2),两个家长可能同时往里放,就必须再加一个 mutex = 1 夹住“放/取”操作。要点:分析同步关系时按“事件”而不是按“进程”来找——“盘子变空”这个事件可以由女儿或儿子引起,“放入苹果”只让女儿能取,所以苹果、橘子各一个同步信号量。
展开模板
semaphore offer1 = 0, offer2 = 0, offer3 = 0; // 桌上放着第 1/2/3 种组合 semaphore finish = 0; // 抽烟完成 int i = 0; // 轮到第几种组合(只有供应者用,不需要互斥) provider(){ while(1){ if (i == 0) { 放组合一(纸 + 胶水); V(offer1); } else if (i == 1) { 放组合二(烟草 + 胶水); V(offer2); } else { 放组合三(烟草 + 纸); V(offer3); } i = (i + 1) % 3; P(finish); // 等这一支抽完再放下一组 } } smoker1(){ while(1){ P(offer1); 拿走组合一; 卷烟; 抽; V(finish); } } // smoker2、3 同理,分别 P(offer2)、P(offer3)
要点:“一个生产者、多种产品、各有专门的消费者”——每种产品一个同步信号量,生产者用一个 finish 等消费者“干完”再继续(桌子容量为 1,同样不需要 mutex)。
展开模板
int count = 0; semaphore mutex = 1; // 保护 count semaphore rw = 1; // 读写互斥 semaphore w = 1; // 排队“闸门”:写者来了就先占住它,后来的读者进不来 writer(){ while(1){ P(w); P(rw); 写; V(rw); V(w); } } reader(){ while(1){ P(w); P(mutex); if (count == 0) P(rw); count++; V(mutex); V(w); 读; P(mutex); count--; if (count == 0) V(rw); V(mutex); } }
要点:所有读者、写者都先过闸门 w(按到达顺序排队)。写者过了闸门后不马上放开,而是一直占着 w 直到写完,于是在它后面到达的读者都被挡在门外;它前面已经在读的读者读完后,写者就能写。读者过闸门后立刻放开 w,所以读者之间仍然可以同时读。
展开模板
semaphore chopstick[5] = {1, 1, 1, 1, 1};
// 方法一:最多允许 4 人同时去拿筷子(2019-43 用碗数实现的就是它)
semaphore seat = 4;
Pi(){ while(1){ P(seat); P(chopstick[i]); P(chopstick[(i+1)%5]); 吃;
V(chopstick[i]); V(chopstick[(i+1)%5]); V(seat); 思考; } }
// 方法二:奇数号先拿左边再拿右边,偶数号先拿右边再拿左边(相邻两人会先争同一根,不会形成环)
Pi(){ while(1){
if (i % 2 == 1) { P(chopstick[i]); P(chopstick[(i+1)%5]); }
else { P(chopstick[(i+1)%5]); P(chopstick[i]); }
吃; V(chopstick[i]); V(chopstick[(i+1)%5]); 思考; } }
// 方法三:把“拿两根筷子”整体做成互斥操作,一次只有一个人在拿
semaphore mutex = 1;
Pi(){ while(1){ P(mutex); P(chopstick[i]); P(chopstick[(i+1)%5]); V(mutex);
吃; V(chopstick[i]); V(chopstick[(i+1)%5]); 思考; } }
要点:三种方法的思路分别是:方法一限制同时抢筷子的人数,5 根筷子最多 4 人抢,至少有一人能拿齐;方法二打破循环等待,相邻两人先争同一根筷子,不可能人人都拿着一根等另一根;方法三让拿筷子的过程互斥,一个人拿的时候别人不会插进来各拿一根,也就形不成环。大题里遇到“多种资源、每人要同时拿到几样才能干活”的场景(2019-43 碗 + 筷子),就用方法一:用一个计数信号量把同时去抢资源的人数限制到“一定有人能拿齐”。
两个新场景:多类资源 + 容量限制,阶段同步(屏障)
上面的模板之外,近年大题越来越爱考“约束多一点”的变形。下面两道是按五步法自己设计的练习(是否会死锁、能否同步,已用 verify/08/p07_pv.py 做了状态搜索和多线程随机测试)。
展开解答
- 找约束:① 货架是共享的,放 / 取要互斥;② 货架总空位有限(容量 10);③ 装配工要等货架上有 A、有 B;④ A、B 各自不能超过 9 个。
- 设信号量:mutex = 1(互斥访问货架);empty = 10(货架空位);fullA = 0、fullB = 0(货架上的 A、B 个数);maxA = 9、maxB = 9(还能再放几个 A、几个 B)。
semaphore mutex = 1, empty = 10, fullA = 0, fullB = 0, maxA = 9, maxB = 9;
workerA(){ while(1){ 生产 A; P(maxA); P(empty); P(mutex); 放 A; V(mutex); V(fullA); } }
workerB(){ while(1){ 生产 B; P(maxB); P(empty); P(mutex); 放 B; V(mutex); V(fullB); } }
assembler(){ while(1){ P(fullA); P(fullB); P(mutex); 取 A; 取 B; V(mutex);
V(empty); V(empty); V(maxA); V(maxB); 组装; } }
为什么要有 maxA、maxB:若不限制,甲可能连续放满 10 个 A,乙一个 B 也放不进去,装配工永远等不到 B——三个进程全部阻塞,死锁(对货架状态做穷举搜索,不加限制时状态 (A=10, B=0) 和 (0, 10) 正是死锁状态;上限改为 9 后没有死锁状态)。上限 9 = 容量 − 1,保证货架上总给另一种零件留着位置。
为什么 P(maxA) 必须在 P(empty) 之前:若反过来,设货架上已有 9 个 A、0 个 B、空 1 格:甲先 P(empty) 占走最后一格,再在 P(maxA) 上阻塞——它手里攥着空位不放;乙拿不到空位放不了 B,装配工等不到 B,又是死锁。规律同五步法第 ⑤ 步:先申请“约束更紧”的资源信号量,最后才 P(mutex)。(同一脚本对两种顺序做了信号量级的状态搜索:错误顺序存在死锁状态,正确顺序没有。)
展开解答
- 找约束:“全都到齐”是一个计数条件——要数一数已经有几个进程完成了第一阶段,计数变量是共享的,要互斥;最后一个到达的进程负责“开门”放行所有人。
- 设信号量和变量:int count = 0(已完成第一阶段的进程数);mutex = 1(互斥访问 count);barrier = 0(第二阶段的“门”)。
int count = 0; semaphore mutex = 1, barrier = 0;
Pi(){ 第一阶段;
P(mutex); count++;
if (count == 3) { V(barrier); V(barrier); V(barrier); } // 最后一个到达者开门:放行 3 次
V(mutex);
P(barrier); // 门没开就在这里等
第二阶段; }
为什么对:前两个到达者 count 为 1、2,不开门,在 P(barrier) 上阻塞;第三个到达者把 count 加到 3,连做 3 次 V,三个进程(包括它自己)都能通过 P(barrier)。V(barrier) 放在 mutex 里外都可以,但 P(barrier) 必须在 V(mutex) 之后——否则先到者拿着 mutex 睡在 barrier 上,后到者进不了计数,死锁。
另一种写法(前驱关系思路):每个进程完成第一阶段后对另外两个进程各 V 一次、再对自己 P 两次,共用 3 个信号量。计数写法的好处是进程再多也只要 3 个变量。拓展 如果要反复分阶段(第二、第三阶段……都要等齐),还要把 count 清零,并用两道“门”交替使用,防止跑得快的进程提前冲进下一轮。
7.5管程
信号量的 P、V 散落在各个进程里,写错一个位置就出错。管程把共享数据和对它的操作封装在一起(类似“类”),由编程语言/编译器保证:任何时刻最多只有一个进程在管程内执行,所以互斥自动实现;同步用管程内的条件变量:
x.wait():条件不满足时,阻塞该进程,把它插入 x 的等待队列,并释放管程(2018-28 选 D)。条件变量没有“值”,不像信号量那样计数。x.signal():唤醒一个在 x 上等待的进程;没人等就什么也不做。- 2016-32:“管程只能用于实现进程的互斥”错——有条件变量,也能实现同步。
管程由四部分组成:① 管程的名字;② 局部于管程内部的共享数据结构说明;③ 对该数据结构进行操作的一组过程(函数);④ 对共享数据设置初值的语句。基本特征:共享数据只能被管程内的过程访问;进程只能通过调用管程内的过程进入管程;每次只允许一个进程在管程内执行。
| 信号量 | 管程的条件变量 | |
|---|---|---|
| 有没有“值” | 有,value 记录剩余资源数 | 没有值,只有一个等待队列 |
| P / wait | value − 1,结果 < 0 才阻塞 | x.wait() 一定阻塞调用者,并释放管程让别人进来 |
| V / signal | value + 1,即使没人等也会“记住”这次释放 | x.signal() 只在有进程等待时唤醒一个;没人等就什么也不发生,不留记录 |
| 判断条件 | 靠 value 自己计数 | 要在管程里用普通变量(如 count)自己判断“满不满、空不空”,再决定 wait 或 signal |
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. x 的值加 1
- B. 什么也不做
- C. 执行 signal 的进程阻塞
- D. 系统报错
展开解析
B条件变量没有值,signal 只负责唤醒一个在 x 上等待的进程;没有进程在等,就什么也不做,这次 signal 不会被“记住”。这是它和信号量 V 操作(值加 1)的区别。
变化点:2018-28 考 x.wait()(阻塞该进程并插入 x 的等待队列)。这里考 x.signal(),重点是“没人等就不留痕迹”。
7.6死锁:四个必要条件
一座窄桥只能过一辆车。东边的车开到桥中间,西边的车也开到桥中间,两车顶在一起:谁都不肯倒车(不可剥夺),谁都占着自己那半座桥(请求并保持),都在等对方让开(循环等待),而桥一次只能被一方用(互斥)。四个条件全凑齐了,就卡死了——这就是死锁:一组进程中的每一个都在等待只有组内其他进程才能释放的资源,谁都无法向前推进。
| 必要条件 | 大白话 | 怎么破坏它(死锁预防) |
|---|---|---|
| ① 互斥 | 资源一次只能给一个进程用(打印机、写一个变量) | 很难破坏;SPOOLing 能把独占设备改造成“共享”的(第 9 篇) |
| ② 不可剥夺 也叫不剥夺条件 | 进程用完之前,别人不能强行拿走它的资源 | 申请不到新资源时,把已占有的资源也释放(或允许高优先级抢占);代价大,只适合 CPU、内存这种状态好保存的资源 |
| ③ 请求并保持 也叫请求与保持 | 手里拿着一些资源,又去申请新的,申请不到就等,手里的也不放 | 运行前一次性申请全部资源(静态分配);资源利用率低,可能饥饿 |
| ④ 循环等待 | 存在一个进程—资源的环:P1 等 P2 的,P2 等 P3 的……Pn 等 P1 的 | 给资源编号,只能按编号递增的顺序申请(顺序资源分配法);会限制编程、浪费资源 |
四个条件是必要条件:死锁发生时一定都成立,但都成立不一定死锁。还要分清三个容易混的词:
| 死锁 | 饥饿 | 死循环 | |
|---|---|---|---|
| 是什么 | 一组进程互相等,全都推进不了 | 某个进程一直得不到服务(别人在正常推进) | 程序自己的逻辑错误,一直在跑 |
| 涉及几个进程 | 至少 2 个,都处于阻塞态 | 可以只有 1 个;它可能在就绪态(等 CPU) | 1 个,处于运行态或就绪态 |
| 由谁造成 | 资源分配不当 + 推进顺序不当 | 调度策略不公平(如静态优先级、SJF) | 程序员 |
死锁的原因:① 系统资源的竞争——不可剥夺的资源(打印机、磁带机)数量不够分;可剥夺的资源(CPU、内存)不会引起死锁。② 进程推进顺序非法——P1 占着 R1 请求 R2,P2 占着 R2 请求 R1;PV 操作顺序不当也属于这一类(生产者先 P(mutex) 再 P(empty))。
资源分配图:把“谁占着谁、谁在等谁”画出来
圆圈是进程,方框是一类资源,方框里的小圆点是这类资源的个数。资源 → 进程的箭头是“分配边”(已经给了它),进程 → 资源的箭头是“请求边”(它在等)。
“最少要多少个资源才不会死锁”
最坏情况:每个进程都拿到“差一个就够”的数量,然后全部卡住。n 个进程分别最多需要 m1, m2, …, mn 个,这时共占用 Σ(mi − 1) 个。只要再多 1 个,就一定有人能跑完并释放资源 → 不死锁的最少资源数 = Σ(mi − 1) + 1。
- A. 2
- B. 3
- C. 4
- D. 5
展开解析
C最坏情况:每个进程都拿到“最多需求 − 1”台、都在等最后 1 台。只要这时还剩 1 台,就有进程能做完并释放,不会死锁。
- 不死锁的条件:K × (3 − 1) + 1 ≤ 8,即 K ≤ 3.5,所以 K ≤ 3 一定不死锁。
- K = 4:4 个进程各拿 2 台,8 台正好分完,每个都在等第 3 台、又都不释放 → 可能死锁。所以最小值是 4。
- A 错K = 2 时两个进程就算都拿满 3 台也只用 6 台,8 台足够,根本不会缺资源。
- B 错K = 3 时最坏各拿 2 台用掉 6 台,还剩 2 台,任意一个进程拿到第 3 台就能做完并释放,不会死锁。
- C 对K = 4 时 4 × 2 = 8 台被分光,四个进程互相等待,可能死锁;比 4 小的 K 都不会。
- D 错K = 5 确实可能死锁,但题目问“最小值”,K = 4 已经可能死锁了。
资源数 ≥ Σ(各进程最大需求 − 1) + 1 就不会死锁。同类:2014-24(需求 3、4、5 → (2 + 3 + 4) + 1 = 10);2021-31(n 个进程各需 2 个 → n + 1)。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 3
- B. 4
- C. 5
- D. 10
展开解析
B不会死锁的条件:K × (4 − 1) + 1 ≤ 10,即 K ≤ 3。K = 3 时,最坏每个进程各拿 3 台,共 9 台,还剩 1 台,总有一个进程能凑齐 4 台。K = 4 时可能出现 3、3、3、1 的分配:10 台全部分完,前三个进程各差 1 台,第四个差 3 台,谁也拿不到,发生死锁。所以最小的 K 是 4。
变化点:2009-25 是 8 台、每个进程最多 3 台(K = 4)。公式都是 K × (最大需求 − 1) + 1 ≤ 资源数。
- A. 10
- B. 11
- C. 14
- D. 15
展开解析
B最坏情况是每个进程都差一台:1 + 2 + 3 + 4 = 10 台。再多 1 台,就一定有一个进程能满足,运行完释放资源,其他进程也能依次完成。n = 11。
变化点:2014-24 是 3、4、5(10 台)。公式:Σ(需求 − 1) + 1。D 是全部需求之和,那是“同时满足所有进程”,比必要的多。
7.7死锁的三种对策与银行家算法
| 策略 | 做法 | 特点 |
|---|---|---|
| 预防 | 破坏必要条件之一:一次性申请全部资源(破坏请求并保持);按编号顺序申请(破坏循环等待);允许剥夺 | 最严格,保证不发生;资源利用率低,会限制申请顺序 |
| 避免 | 每次分配前判断:分了之后系统还安全吗?不安全就不分(银行家算法) | 需要事先知道每个进程的最大需求 |
| 检测与解除 | 随便分,定期用资源分配图检测(能完全化简 = 无死锁);发现死锁就剥夺资源、撤销进程或回退 | 不限制分配;会给可能导致死锁的进程分配资源 |
2015-26:S1 用避免、S2 用检测——“S1 需要进程运行所需的资源总量信息而 S2 不需要”“S1 不会给可能导致死锁的进程分配资源而 S2 会”正确;“S1 会限制申请顺序”错(那是预防)。2013-32:安全状态一定无死锁;不安全状态不一定会死锁(只是有可能);银行家算法是避免,不是预防。2019-30:银行家算法不能用来判断系统是否已经死锁。
银行家算法:安全性检查
- A. 存在安全序列 P1、P2、P3
- B. 存在安全序列 P2、P1、P3
- C. 存在安全序列 P2、P3、P1
- D. 不存在安全序列
展开解析
B表里给的是“需求总量”(Max),先算 Need = Max − 已分配,再从 Available 开始做安全性检查。
- Available = (6, 6) − (2 + 2 + 1, 3 + 1 + 2) = (1, 0)。Need:P1 = (2, 1),P2 = (1, 0),P3 = (2, 2)。
- Work = (1, 0):只有 P2 的 (1, 0) 满足 → P2 做完,Work = (1, 0) + (2, 1) = (3, 1)。
- Work = (3, 1):P1 (2, 1) 满足,P3 (2, 2) 不满足(B 类只有 1)→ P1 做完,Work = (3, 1) + (2, 3) = (5, 4)。
- Work = (5, 4):P3 满足。安全序列 P2、P1、P3(也是唯一的)。
- A 错第一步就走不通:P1 的 Need = (2, 1),而 Available = (1, 0),A、B 两类都不够。
- B 对P2 → P1 → P3 每一步 Need ≤ Work,是安全序列。
- C 错P2 之后 Work = (3, 1),P3 的 Need = (2, 2) 中 B 类要 2 > 1,P3 不能接在 P2 后面。
- D 错已经找到安全序列 P2、P1、P3,系统是安全的。常见错法是把“需求总量”直接当成 Need,结果谁都满足不了。
先看清表里给的是 Max(需求总量)还是 Need(尚需);Work 每次加的是“做完进程的已分配量”。
- A. 不存在安全序列,系统处于不安全状态
- B. 存在多个安全序列,系统处于安全状态
- C. 存在唯一安全序列 P3、P1、P2,系统处于安全状态
- D. 存在唯一安全序列 P3、P2、P1,系统处于安全状态
展开解析
A安全性检查:Available = 总数 − 已分配;每次找一个 Need ≤ Work 的进程,假设它做完并归还资源,直到全部做完(安全)或卡住(不安全)。
- Available = 4 − (2 + 1 + 0) = 1;Need:P1 = 4 − 2 = 2,P2 = 3 − 1 = 2,P3 = 1 − 0 = 1。
- 只有 P3 能满足(Need 1 ≤ Work 1)。假设它做完,归还它原来已占有的 0 个:Work = 1 + 0 = 1(临时给它的那 1 个也还回来,Work 不变)。
- 此时 P1、P2 都还需要 2 > 1,卡住 → 不存在安全序列,不安全。
- A 对P3 做完后 Work 仍为 1,P1、P2 都需要 2,找不到安全序列。
- B 错连一个完整的安全序列都不存在:P3 之后 Work = 1,P1、P2 都需要 2,更谈不上“多个”。
- C 错P3 之后 Work = 1,而 P1 还需要 4 − 2 = 2 > 1,P1 无法执行,P3、P1、P2 走不下去。
- D 错P3 之后 Work = 1,而 P2 还需要 3 − 1 = 2 > 1,P2 无法执行,P3、P2、P1 走不下去。
Work 只增加“做完的进程原来已占有的资源”:P3 原来占 0 个,做完后 Work 不变。这正是本题的陷阱。
处理一个新请求:进程 Pi 请求 Request:① Request ≤ Needi?(否则出错:超过声明的最大值)② Request ≤ Available?(否则等待)③ 试着分配:Available −= Request,Allocation += Request,Need −= Request;④ 做安全性检查,安全才真正分配,否则撤销试分配、让 Pi 等待。
展开解析
- 先算 Need = Max − Allocation:P0 (7,4,3),P1 (1,2,2),P2 (6,0,0),P3 (0,1,1),P4 (4,3,1)。
- (1)安全性检查,Work = (3,3,2):P1 的 Need (1,2,2) ≤ Work → P1 完成,Work = (3,3,2) + (2,0,0) = (5,3,2);P3 (0,1,1) ≤ (5,3,2) → Work = (7,4,3);P4 (4,3,1) ≤ (7,4,3) → Work = (7,4,5);P0 (7,4,3) ≤ (7,4,5) → Work = (7,5,5);P2 (6,0,0) → Work = (10,5,7)。全部完成,安全,安全序列 P1, P3, P4, P0, P2(不唯一)。
- (2)P1 请求 (1,0,2):① (1,0,2) ≤ Need1 (1,2,2) ✓;② ≤ Available (3,3,2) ✓;③ 试分配:Available = (2,3,0),Allocation1 = (3,0,2),Need1 = (0,2,0);④ 安全性检查:Work = (2,3,0),P1 (0,2,0) ✓ → (5,3,2) → P3 → (7,4,3) → P4 → (7,4,5) → P0 → (7,5,5) → P2。安全,可以分配。
- (3)P4 请求 (3,3,0):≤ Need4 (4,3,1) ✓,但 > Available (2,3,0)(A 类只剩 2 个)→ 资源不够,P4 等待,不用做安全性检查。
- (4)P0 请求 (0,2,0):≤ Need0 ✓,≤ Available (2,3,0) ✓;试分配后 Available = (2,1,0)。检查:P1 还需 (0,2,0)(B 只有 1)✗,P3 还需 (0,1,1)(C 只有 0)✗,P0、P2、P4 也都不满足 → 找不到能完成的进程,不安全,撤销试分配,P0 等待。注意这时系统并没有死锁,只是分了就可能死锁。
以上结果已用程序验算。做大题时把每一步的 Work 写出来(得分点),并写明“存在安全序列……,系统安全,可以分配”。
死锁检测:资源分配图化简与死锁定理
每类资源都只有 1 个时:资源分配图有环 ⇔ 死锁。某类资源有多个时:有环只是死锁的必要条件,有环不一定死锁(下面的化简图就是有环却能完全化简的例子),要用化简来判断。没有环则一定不死锁。
“检测与解除”策略平时随便分配,隔一段时间(或 CPU 利用率下降时)检查一次有没有死锁。检查的方法就是化简资源分配图:找一个“不阻塞”的进程(它的请求都能满足),假设它运行完并释放全部资源——把它的所有边删掉;再用释放出来的资源去满足别人,重复下去。
死锁解除:检测到了怎么办
- 资源剥夺法:挂起某些死锁进程,抢占它的资源分给别的死锁进程(要防止被挂起的进程长时间得不到资源而饥饿)。
- 撤销(终止)进程法:强制撤销部分甚至全部死锁进程,释放它们的资源。按优先级、已运行时间、还需时间等挑“代价最小”的撤销。实现简单,但已做的工作白费。
- 进程回退法:让一个或多个进程回退到足以避开死锁的地方(需要系统事先保存进程的历史检查点)。
对比三种策略:预防最保守(破坏必要条件,一定不会死锁,资源利用率低);避免要事先知道每个进程的最大需求(银行家算法,安全状态一定不死锁,不安全不一定死锁);检测与解除最乐观(不限制分配,允许死锁发生后再处理)。
预防、避免、检测、解除:四种对策放在一起比
问题引入:2013-32、2015-26、2019-30 都在考“这句话说的是哪种对策”。把四种对策放进同一张表、同一张图,就不会张冠李戴。
| 死锁预防 | 死锁避免 | 死锁检测 | 死锁解除 | |
|---|---|---|---|---|
| 分配策略 | 最保守:宁可资源闲着 | 折中:每次分配前先算一算 | 最宽松:只要有就分,出了死锁再处理 | |
| 具体做法 | 破坏四个必要条件之一:一次申请全部(破坏请求并保持)、按编号顺序申请(破坏循环等待)、允许剥夺(破坏不剥夺) | 银行家算法:试分配后做安全性检查,不安全就不分 | 化简资源分配图(死锁定理),或用类似银行家的表格算法 | 资源剥夺、撤销进程、进程回退 |
| 要事先知道最大需求吗 | 一次性申请时要 | 要 | 不要 | |
| 会发生死锁吗 | 不会 | 不会(始终停在安全状态) | 会,发生后再处理 | |
| 资源利用率 | 低 | 较高 | 高 | |
| 主要代价 | 限制申请方式,可能饥饿 | 每次分配都要计算;进程和资源数要相对固定 | 定期检测的开销;解除时已做的工作白费 | |
| 真题 | 2015-26 C(会限制申请顺序的是预防) | 2013-32、2019-30 Ⅰ | 2015-26 D(会给可能导致死锁的进程分配资源) | |
四个必要条件的名称在不同教材里说法略有不同,做题时要对得上:互斥条件;不剥夺条件(也叫不可剥夺、非抢占);请求并保持条件(也叫请求与保持、部分分配);循环等待条件(也叫环路等待)。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 不存在安全序列,系统处于不安全状态
- B. 存在唯一的安全序列 P3、P1、P2
- C. 存在唯一的安全序列 P3、P2、P1
- D. 存在多个安全序列,系统处于安全状态
展开解析
D可用 = 5 − 4 = 1。尚需:P1 2、P2 2、P3 1。只有 P3 能先完成,释放后可用 = 2;此时 P1、P2 都能完成,谁先都行。安全序列有 P3、P1、P2 和 P3、P2、P1 两个。
变化点:2018-26 是 4 个资源、已分配 2、1、0:可用 1 个,P3 完成后只还回 1 个(它原来没分到资源),可用仍是 1,P1、P2 都满足不了,所以不安全。这里总数多 1 个,而且 P3 已经占着 1 个,完成后能还回 2 个,结论完全不同。安全序列也可以不唯一。
- A. 立即分配,分配后系统安全
- B. 拒绝,因为请求超过了 P1 的尚需量
- C. 让 P1 等待,因为可用资源不足
- D. 让 P1 等待,因为试分配后系统不安全
展开解析
D请求 (1, 0, 1) ≤ 尚需 (2, 1, 2),也 ≤ 可用 (2, 1, 1),可以试分配。试分配后可用 = (1, 1, 0),三个进程的尚需分别为 (1, 1, 1)、(1, 1, 1)、(0, 0, 1),都需要 1 个 C,而 C 已经没有了,找不到安全序列。所以不分配,P1 等待。
变化点:真题只做安全性检测。这里多了“资源请求算法”:先检查请求 ≤ 尚需、请求 ≤ 可用,再试分配并做安全性检测。注意当前状态本身是安全的(例如 P2、P0、P1)。
- A. 死锁避免要求进程一次性申请全部资源
- B. 按银行家算法,系统处于不安全状态时一定会发生死锁
- C. 死锁检测时,若资源分配图不能完全化简,则系统处于死锁状态
- D. 规定所有进程按资源编号递增的顺序申请资源,属于死锁避免
展开解析
C死锁定理:系统处于死锁状态,当且仅当该状态的资源分配图不能完全简化。A 和 D 都是死锁预防(破坏请求并保持、破坏循环等待)。B 错:不安全状态只是可能进入死锁,不一定死锁(2013-32)。
变化点:把预防、避免、检测三种方法的“做法”打乱了放在一起,考能不能对号入座。
- A. 必须事先知道每个进程对各类资源的最大需求量
- B. 系统可能把资源分配给会导致死锁的进程
- C. 系统始终处于安全状态
- D. 进程必须按资源编号递增的顺序申请资源
- 信号量初值 2,当前值 −3,有几个进程在等?现在有几个进程占着资源?
答案
3 个在等;2 个资源全被占用(2 个进程在用)。 - 生产者—消费者中,交换消费者的 P(full) 和 P(mutex) 会怎样?
答案
缓冲区空时,消费者先拿到 mutex 再在 full 上阻塞,生产者拿不到 mutex 放不进产品,full 永远是 0 → 死锁。 - Peterson 算法满足“让权等待”吗?
答案
不满足,进不去就在 while 里空转(忙等)。 - 5 个进程共享同类资源,每个最多需要 4 个,不会死锁的最少资源数?
答案
5×3 + 1 = 16。 - 用 PV 实现:S1 执行完后 S2、S3 才能执行,S2、S3 都完成后 S4 才能执行。
答案
四条边四个信号量初值 0:S1 后 V(a) V(b);S2 前 P(a) 后 V(c);S3 前 P(b) 后 V(d);S4 前 P(c) P(d)。 - 有 6 个进程共享某资源区,最多允许 3 个进程同时进入,信号量的取值范围是多少?
答案
初值 3,最小 3 − 6 = −3,范围 −3 ~ 3。 - 水果盘问题中盘子能放 2 个水果,还需要改什么?
答案
plate 初值改为 2,并增加 mutex = 1,把“放水果”“取水果”分别用 P(mutex)…V(mutex) 夹住(两个家长或两个孩子可能同时访问盘子)。 - 管程中执行 x.signal() 时没有进程在 x 上等待,会发生什么?和信号量的 V 有什么不同?
答案
什么都不发生,也不留下记录;信号量的 V 会把 value 加 1,之后来的 P 能直接用上这次释放。 - 系统中每类资源都只有 1 个,资源分配图出现了环,一定死锁吗?若某类资源有 2 个呢?
答案
每类只有 1 个时一定死锁;有多个实例时不一定,要化简判断。
7.✎本篇真题闯关(37 题)
本篇各小节挂的真题全部在这里,按小节排好。可以展开后一道道做,也可以用弹窗模式连续做。做题记录(对/错)只保存在你这台电脑的浏览器里,小节标题下的题号会随之变绿或变红。
融合二:存储系统全景 —— 从一个地址到一个数据
这是全书最长、也是 408 综合大题最集中的一篇。计组讲“硬件怎么快速找到数据”(主存芯片、Cache、TLB、地址变换机构),操作系统讲“内存怎么分给多个进程、不够了怎么办”(分页、分段、虚拟内存、页面置换)。两者最终在一次访存上汇合:CPU 给出虚拟地址 → 查 TLB → 查页表 → 得到物理地址 → 查 Cache → 访问主存 → 缺页了交给操作系统从磁盘调入。本篇先分别打好基础,最后用一个可以一步步点的动画把整条路串起来。
8.1为什么要分层:局部性原理
理想的存储器又快、又大、又便宜,但现实中三者不可兼得:越快的越贵、越小。办法是分层:CPU 旁边放少量极快的,后面放大量便宜慢的,并且想办法让 CPU 大多数时候都在快的那一层找到数据。
时间局部性:刚访问过的,很可能马上又访问(循环里反复用的变量、循环体的指令)。
空间局部性:访问了某个地址,它附近的地址很可能马上被访问(数组顺序遍历、顺序执行的指令)。
所以把“刚用过的”和“它旁边的一整块”一起搬到快的一层,后面的访问大多就能在快层命中。
for (i = 0; i <= 9; i++){ temp = 1; for (j = 0; j <= i; j++) temp *= a[j]; sum += temp; },下列关于数组 a 的访问局部性的描述中,正确的是( )。- A. 时间局部性和空间局部性皆有
- B. 无时间局部性,有空间局部性
- C. 有时间局部性,无空间局部性
- D. 时间局部性和空间局部性皆无
展开解析
A内层循环顺序访问 a[0]、a[1]、… → 相邻元素接连被访问 → 空间局部性;外层每一轮都从 a[0] 开始重新访问 → 同一元素在不久后又被访问 → 时间局部性。
- A 对两种局部性都有。
- B 错a[0] 被访问了 10 次、a[1] 被访问 9 次……有时间局部性。
- C 错顺序访问数组元素,有空间局部性。
- D 错两种局部性都有:a[0] 在每轮外循环都被重新访问(时间局部性),内循环按 a[0]、a[1]… 顺序访问(空间局部性)。
“同一个东西反复用”= 时间局部性;“挨着的东西接连用”= 空间局部性。每个元素只访问一次的循环只有空间局部性(2023-43)。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
for (i = 0; i < n; i++) sum += a[i]; 中,对变量 sum 和数组 a 的访问分别主要体现了( )。- A. 时间局部性、空间局部性
- B. 空间局部性、时间局部性
- C. 都只有时间局部性
- D. 都只有空间局部性
展开解析
Asum 在每次循环中都被反复访问,是时间局部性;a[i] 按地址顺序依次访问,是空间局部性(每个元素只访问一次,没有时间局部性)。
变化点:2017-14 的数组 a 在内层循环中被反复访问,所以两种局部性都有。这里每个元素只访问一次,a 只有空间局部性。
8.2主存:芯片、容量扩展与多体交叉
存储器的分类和性能指标
| SRAM(静态) | DRAM(动态) | ROM / Flash | |
|---|---|---|---|
| 存储原理 | 触发器(双稳态电路) | 电容上的电荷 | ROM 出厂/编程写入;Flash 用 MOS 管浮栅存电荷 |
| 断电 | 丢失(易失性) | 不丢失(非易失) | |
| 刷新 | 不需要 | 需要定期刷新(电荷会漏;SDRAM 也要,2015-17) | 不需要 |
| 速度 / 集成度 / 价格 | 快 / 低 / 贵 | 慢 / 高 / 便宜 | Flash 写比读慢得多(要先擦除,2012-16) |
| 用途 | Cache、TLB | 主存 | BIOS(引导程序)、U 盘、SSD |
- 存取方式:RAM 和 ROM 都是随机存取(访问任何单元时间都一样,2010-16 选 A:RAM 易失、ROM 非易失,两者都随机存取;ROM 不能做 Cache,也不用刷新);磁带是顺序存取;磁盘是直接存取;CD-ROM 不是随机存取(2011-14);TLB 是相联存储器(按内容查找)。
- 存取时间:从发出读命令到数据出来;存取周期:连续两次访问之间的最小间隔(比存取时间长,DRAM 读后要恢复);主存带宽 = 每秒传送的数据量。
- DRAM 地址复用:行地址、列地址分两次从同一组引脚送入,地址引脚减半。4M×8 位 DRAM:地址 22 位 → 引脚 11 根,加数据 8 根 = 19(2014-15)。行列数怎么选:行列尽量接近(引脚少),且行数少一点(刷新按行进行,行少刷新开销小):2K×1 选 r=32、c=64(2018-17)。
| 按什么分 | 分类(选择题判断用) |
|---|---|
| 存取方式 | 随机存取(RAM、ROM:任何单元的存取时间相同);顺序存取(磁带:只能按顺序找);直接存取(磁盘:先直接找到磁道区域,再在区域内顺序找);相联存储器(按内容查找,如 TLB、全相联 Cache 的标记比较) |
| 信息能否长期保存 | 易失性(SRAM、DRAM:断电即丢);非易失性(ROM、Flash、磁盘、光盘) |
| 读出后信息还在吗 | 破坏性读出(DRAM:读出时电容放电,读后要“再生”写回,所以存取周期 > 存取时间);非破坏性读出(SRAM) |
| ROM 的种类 | MROM(出厂写死)、PROM(一次可编程)、EPROM(可擦除重写,紫外线擦除的 UVEPROM、电擦除的 EEPROM)、Flash(由 EEPROM 发展而来,可在线擦写,U 盘、SSD 都用它)。ROM 和 RAM 一样都是随机存取的 |
DRAM 的三种刷新方式
DRAM 电容上的电荷会慢慢漏掉,必须在刷新周期(常为 2ms)内把每一行都读出再写回一遍。刷新按行进行,一次刷新一整行;由存储器内部的刷新电路自动完成,不需要 CPU 参与,但刷新时这块芯片不能被访问。以“2ms 内刷新 128 行、存取周期 0.5μs”为例:
| 方式 | 做法 | 优缺点 |
|---|---|---|
| 集中刷新 | 在 2ms 里拿出一段连续时间,一口气把 128 行刷完(占 128 × 0.5 = 64μs),其余时间正常读写 | 读写时不受影响;但这 64μs 内不能访存,叫“死区” |
| 分散刷新 | 每个存取周期分成两半:前半读写、后半刷新一行(存取周期变成 1μs) | 没有死区;但存取周期加长,而且刷得太勤(128μs 就刷完一遍,远小于 2ms) |
| 异步刷新 | 把 128 行的刷新均匀分散在 2ms 里:每隔 2ms / 128 ≈ 15.6μs 刷新一行 | 前两者的结合,死区很短(每次只有一个存取周期),最常用 |
verify/08/p08_calc.py 验算)。算刷新间隔的套路:异步刷新间隔 = 刷新周期 ÷ 行数;集中刷新死区 = 行数 × 存取周期;分散刷新的存取周期 = 原存取周期 × 2。注意“行数”是芯片矩阵的行数,不是总单元数——这正是 2018-17 要“行数取小”的原因。
用小芯片拼出大内存
位扩展
芯片的字长不够:几片并联,地址线共用,数据线各管几位。8 片 8K×1 → 8K×8。字扩展
芯片的单元数不够:几片“串联”,数据线共用,用高位地址译码产生片选,决定哪一片工作。字位同时扩展
先位扩展成一组,再字扩展多组。芯片数 = 总容量 ÷ 单片容量(都换成“单元数×位数”)。- A. 1、15
- B. 2、15
- C. 1、30
- D. 2、30
展开解析
D芯片数 = 要的总容量 ÷ 一片芯片的容量(都换成“位”算),位不够先位扩展、字不够再字扩展。
- ROM 区 4KB = 4K×8 位,芯片 2K×8 位:位宽够,只做字扩展 → 4K×8 ÷ (2K×8) = 2 片。
- RAM 区 64KB − 4KB = 60KB = 60K×8 位,芯片 4K×4 位:先 2 片位扩展成 4K×8,再字扩展 60K ÷ 4K = 15 组 → 2 × 15 = 30 片。
- A 错ROM 算成 1 片是把 2K×8 的芯片当成了 4K;RAM 算成 15 片是只做了字扩展,忘了 4 位宽的芯片要 2 片并成 8 位。
- B 错ROM 2 片对;RAM 15 片只够 60K×4 位,每个单元只有 4 位,缺了位扩展(要再乘 2)。
- C 错RAM 30 片对;ROM 1 片只有 2K×8 = 2KB,装不下 4KB 的 ROM 区。
- D 对ROM 2 片、RAM 30 片。
芯片数 = (字数 ÷ 芯片字数) × (位数 ÷ 芯片位数)。同类:2016-16(RAM 56KB 用 8K×4 位 → 56K×8 ÷ (8K×4) = 14 片);2021-15(按 32 位字编址 4M 字,用 512K×8 位 → 8 × 4 = 32 片);2023-15(RAM:ROM = 3:1,ROM 在高端 → 30000000H ~ 3FFFFFFFH)。
主存和 CPU 怎么连:完整设计一次
大题常给出“地址空间怎么分配 + 有哪些芯片”,要求选芯片、画连接图、写出每片芯片的地址范围。步骤是固定的:① 按地址分配算每个区域的容量,选芯片、算片数;② 片内地址用低位地址线(芯片有 2k 个单元就接 Ak−1~A0);③ 剩下的高位地址线接译码器产生片选(译码片选法;若直接用某根高位地址线当片选叫线选法,简单但地址空间不连续、浪费);④ 数据线按位扩展分配;⑤ 读写控制线 R/W̄ 接 RAM 的写使能,访存请求 MREQ̄ 接译码器的使能端(不访存时一片都不选)。
展开解析
- 算容量、选芯片:系统区 0000H~1FFFH 共 2000H = 8K 字节 → 8K×8 ROM 1 片。用户区 2000H~5FFFH 共 4000H = 16K 字节 → 16K×8 ÷ (8K×4) = 4 片 RAM:每 2 片位扩展成 8K×8 为一组,共 2 组。
- 片内地址:每片 8K = 213 个单元 → A12~A0 接所有芯片的地址端。
- 片选:剩下 A15、A14、A13 接 3-8 译码器的输入 C、B、A。0000H~1FFFH 时 A15A14A13 = 000 → Y0 选 ROM;2000H~3FFFH 为 001 → Y1 选第 1 组 RAM;4000H~5FFFH 为 010 → Y2 选第 2 组 RAM;MREQ̄ 接译码器使能端。
- 数据线:ROM 接 D7~D0;每组 RAM 中一片接 D7~D4、另一片接 D3~D0。读写:R/W̄ 接 4 片 RAM 的写使能;ROM 只读,不接写信号。
检查地址范围的方法:把每组的片选码(A15A14A13)拼上片内地址全 0 和全 1。例如 Y1:001 0 0000 0000 0000 = 2000H 到 001 1 1111 1111 1111 = 3FFFH ✓。2010-15 问“地址 0B1FH 所在芯片的最小地址”,就是反过来:先看高位落在哪个片选码,再把片内地址清零。
片选的两种做法:线选法与译码片选法
问题引入:字扩展时要用高位地址决定“哪一片工作”。最省事的办法是一根地址线直接接一片芯片的片选端(线选法);更规范的办法是用译码器(译码片选法)。两者差在哪?画出地址空间就一目了然。
设定:CPU 有 16 根地址线,用 4 片 2K×8 位芯片做字扩展,A10~A0 接所有芯片作片内地址;片选低电平有效;A15 取 0。
verify/08/p08_calc.py 验算。真题怎么考:大题(如 8.2 的“完整设计一次”、2016-16、2023-15)默认用译码片选,要写出每片的地址范围;选择题偶尔问线选法的特点(简单、地址空间不连续、浪费地址空间)。判别法:给出片选连接方式后,先写“让这片被选中时高位地址必须是什么”,再拼上片内地址的全 0 和全 1,得到地址范围。
单体多字、多体并行:高位交叉与低位交叉怎么分配地址
问题引入:CPU 越来越快,一个存储体每 200ns 才能读一个字,跟不上。提速的办法有两类:一次多读几个(单体多字),或者多个体一起干(多体并行)。多体又分高位交叉和低位交叉,差别全在“相邻地址落在哪个体”。
| 组织方式 | 体号由什么决定 | 顺序读 n 个字的时间(T = 200ns,r = 50ns,n = 8) | 最大带宽(字 4B) |
|---|---|---|---|
| 单体单字 | —— | n × T = 1600ns | 4B / 200ns = 20MB/s |
| 单体四字 | —— | ⌈n / 4⌉ × T = 400ns(数据连续时) | 16B / 200ns = 80MB/s |
| 四体高位交叉 | 地址高位 | n × T = 1600ns(总在同一个体) | 顺序访问 20MB/s |
| 四体低位交叉 | 地址低位(地址 mod 4) | T + (n − 1) × r = 550ns | 4B / 50ns = 80MB/s |
以上数字已用 verify/08/p08_calc.py 验算。双端口 RAM(下面“多体交叉存储器”末尾的“了解”)是另一条思路:同一个存储体开两套独立的地址、数据、控制端口,让两个部件同时访问;只有两边同时访问同一个单元、而且至少有一边在写时才冲突(同时写 → 写入错误;一写一读 → 读出错误),由判断逻辑让一方等待(BUSY);两边同时读同一单元不冲突。
真题怎么考:2012-43(4)四体交叉的最大带宽;2015-18 低位交叉的访存冲突;2017-13、2026-15 交叉编址下一个变量跨几个存储字。判别法:先算“体号 = 地址 mod m”(低位交叉)或“体号 = 地址 ÷ 每体容量”(高位交叉),再看相邻访问是否落在同一体。
多体交叉存储器:让主存“流水”起来
一个存储体读一次要一个存取周期,这期间不能再读它。如果有 m 个独立的存储体,把相邻的地址放到不同的体(低位交叉:体号 = 地址 mod m),就可以每隔 1/m 个存取周期启动一个体,连续读 m 个字只比读 1 个字多一点点时间。
另外两种提速办法 了解:双端口 RAM——同一个存储器有两组独立的地址线、数据线和读写控制,两个部件(如 CPU 和 I/O 设备、两个 CPU)可以同时访问;两个端口同时访问同一个单元、且至少一个在写时会冲突,由判断逻辑让其中一个等待(发出 BUSY 信号);同时读同一单元不冲突。单体多字——一个存储体每次读出多个连续的字,要求指令和数据在主存里连续存放。
- A. 1
- B. 2
- C. 3
- D. 4
展开解析
C4 片低位交叉,每个存储周期 4 片各出 1 个字节,拼成一个从 4 的倍数地址开始的 32 位字。数 x 跨了几个这样的“4 字节行”,就要几个存储周期。
- double 占 8 字节:804001AH ~ 8040021H。
- 按 4 字节一行划分:8040018H ~ 1BH(用到 1A、1B)、804001CH ~ 1FH(全用)、8040020H ~ 23H(用到 20、21)→ 跨 3 行。
- 每行一个存储周期 → 需要 3 个存储周期。
- A 错主存每次最多读写 32 位 = 4 字节,8 字节的 double 至少要 2 个存储周期,1 个周期不可能读完。
- B 错2 个周期是 x 按 8 字节(至少 4 字节)对齐时的结果;但 804001AH 除以 4 余 2,没有对齐,前后各多跨出半行。
- C 对x 跨 8040018H、804001CH、8040020H 三行,3 个存储周期。
- D 错x 的 8 个字节只落在 3 个 4 字节行里,每行一个周期就能整行读出,用不到 4 个周期。
读一个数要几个存储周期 = 它跨了几个“存储字行”。边界对齐(第 2 篇)就是为了让数据不跨行。同类:2026-15(八片交叉,同一片的地址低 3 位相同)。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 2、7
- B. 2、14
- C. 4、14
- D. 2、28
展开解析
BROM:16KB ÷ (8K × 8 位 = 8KB) = 2 片。RAM:112KB × 8 位 ÷ (16K × 4 位) = 112K × 8 ÷ 64K = 14 片(每 2 片做位扩展拼成 16K × 8 位,共 7 组)。
变化点:2009-15 是 64KB、2K × 8 位 ROM 和 4K × 4 位 RAM。方法不变:芯片数 = 所需总位数 ÷ 每片的位数,ROM 区和 RAM 区分开算。A 是只数了组数,忘了每组要 2 片做位扩展。
- A. 0000H
- B. 2000H
- C. 2A00H
- D. 2800H
展开解析
B每 2 片(位扩展)组成一组 4K × 8 位,管 4K = 1000H 个地址。2A5CH 落在第 3 组,即 2000H ~ 2FFFH,所以最小地址是 2000H。
变化点:2010-15 是 2K × 4 位的芯片,每组管 800H 个地址(0B1FH → 0800H)。先算出每组管多少地址,再看目标地址落在第几组。
- A. 16
- B. 28
- C. 14
- D. 20
展开解析
A16M = 224,需要 24 位地址。DRAM 的行地址和列地址分两次从同一组引脚送入,地址引脚只要 24 ÷ 2 = 12 根。数据引脚 4 根,共 16 根。
变化点:2014-15 是 4M × 8 位(11 + 8 = 19)。B 是没有按分时复用来算(24 + 4)。SRAM 不复用地址引脚,DRAM 才复用。
- A. 8005 和 8013
- B. 8000 和 8010
- C. 8010 和 8003
- D. 8004 和 8003
展开解析
A体号 = 地址 mod 4:8000→0,8005→1,8010→2,8004→0,8013→1,8003→3。同一个体在 4 个总线周期内被再次访问就会冲突。8000 与 8004 相隔 3 次,冲突;8005 与 8013 也相隔 3 次,冲突。选项中只有 A。
变化点:2015-18 的答案是 8000 和 8004。这里它也冲突,但没放进选项,换成了另一对同体地址。判断方法:体号相同,且在序列中的距离小于 4。
- A. 1
- B. 2
- C. 3
- D. 4
展开解析
B每个存储周期读一个 4 字节对齐的字:…14H ~ …17H,…18H ~ …1BH。x 占 …16H ~ …19H,跨了两个字,需要 2 个存储周期。
变化点:2017-13 是 8 字节的 double,地址 …1AH,跨 3 个字。这里 int 只有 4 字节,但没有对齐,也要读 2 次。对齐存放时只要 1 次,这就是要边界对齐的原因。
- A. 15.6μs;64μs
- B. 7.8μs;256μs
- C. 15.6μs;128μs
- D. 7.8μs;128μs
展开解析
C异步刷新:把 256 行均匀分布在 4ms 内,间隔 = 4ms ÷ 256 ≈ 15.6μs。集中刷新:一次连续刷完 256 行,死区 = 256 × 0.5μs = 128μs。B、D 把刷新周期当成了 2ms;A 把行数当成了 128。
考点:三种刷新方式(已用程序验算)。
8.3Cache:CPU 和主存之间的“小抄”
Cache 用 SRAM 做成,容量小、速度接近 CPU。主存和 Cache 都被划分成大小相同的块(Cache 里的一块也叫一行)。CPU 访存时先看这个地址所在的块在不在 Cache 里:在(命中)就直接读 Cache;不在(缺失)就去主存读,并把整块搬进 Cache(空间局部性)。
展开解析
- 命中率 H = 1900 / (1900 + 100) = 95%。
- 若 Cache 与主存同时启动(缺失时只需主存的时间):平均 = 0.95 × 50 + 0.05 × 250 = 60ns,效率 e = 50/60 ≈ 83.3%。
- 若先查 Cache、缺失再访主存:平均 = 50 + 0.05 × 250 = 62.5ns,效率 = 50/62.5 = 80%。
两种算法都有教材用,题目一般会说明“同时访问”还是“先访问 Cache”;没说明时写出所用公式即可。
三种映射方式:主存的块能放进 Cache 的哪一行
地址怎么切:Tag | 组号(行号)| 块内地址
展开解析
- 虚拟地址:页 8KB = 213 → D = 13;A = 32 − 13 = 19。TLB 全相联没有组号,标记就是整个虚页号 → B = 19,存的是虚页号。
- 物理地址:C = 24 − 13 = 11。
- Cache:块 64B → G = 6;行数 64KB/64B = 1024,2 路 → 512 组 → F = 9;E = 24 − 9 − 6 = 9。答案 A=19,B=19,C=11,D=13,E=9,F=9,G=6。
- (2)组号 = 4099 mod 512 = 3;标记 = 块号的高位 = 4099 ÷ 512 取整 = 8 = 000001000B(9 位)。(块号 = 物理地址去掉块内 6 位 = E+F 共 18 位,低 9 位是组号、高 9 位是 Tag。)
- (3)缺页开销大得多:Cache 缺失只需访问主存(几十 ns),硬件完成;缺页要访问磁盘(ms 级),还要操作系统介入。
- (4)直写要求每次写都同时写下一层。写主存可以接受;写磁盘太慢,每次改页面都写磁盘不可接受,所以主存—辅存层次总用回写(页面被换出时才写回,要有修改位)。
工具已填 2018-44 的物理地址 0040040H(28 位:Tag 20 | 组号 3 | 块内 5),可以换成任何题目的地址和字段。
- A. 8,20
- B. 8,23
- C. 64,20
- D. 64,23
展开解析
A组相联:用组号选出一组,组内每一路各配一个比较器同时比 Tag。比较器个数 = 路数,位数 = Tag 位数。
- 行数 = 32KB ÷ 64B = 512;组数 = 512 ÷ 8 = 64 → 组号 6 位;块内地址 log264 = 6 位。
- Tag = 32 − 6 − 6 = 20 位;比较器 8 个(一组 8 路)。
- A 对8 个 20 位比较器。
- B 错23 = 32 − 6 − 3,是把“8 路”当成了 8 组(组号 3 位);实际组数 = 512 ÷ 8 = 64,组号 6 位,Tag 20 位。
- C 错比较器个数错:组号已经选定了唯一一组,只需比这一组里的 8 行,不必给 64 组各配比较器。
- D 错两处都错:比较器只要 8 个(路数),Tag 只有 20 位(组号是 6 位不是 3 位)。
比较器个数:直接映射 1 个、k 路组相联 k 个、全相联 = 行数;位数都等于 Tag 位数。同类:2009-14(16 块 2 路 → 8 组,块 32B,地址 129 在块 4 → 组 4);2026-18(1024 行 4 路 → 256 组,块 32B,地址 1028 → 块 32 → 组 32)。
Cache 的总容量:别漏了“附加位”
题目说“Cache 数据区 32KB”指的只是数据。每一行实际还要存:Tag + 有效位 1 位;采用回写时再加修改位(脏位)1 位;采用 LRU 等替换算法时再加替换控制位(组相联常按每行 log2(路数) 位的计数器算)。组号不用存(它就是行所在的位置)。
- A. 275
- B. 274
- C. 258
- D. 257
展开解析
ACache 一行 = 数据位 + Tag + 有效位 + 一致性维护位(回写要 1 位脏位)+ 替换位(直接映射不需要)。
- 行数 = 32KB ÷ 32B = 1024 → 行号 10 位;块内地址 log232 = 5 位;Tag = 32 − 10 − 5 = 17 位。
- 每行 = 数据 32 × 8 = 256 + Tag 17 + 有效位 1 + 脏位 1 = 275 位。直接映射不用替换算法,没有替换位。
- A 对256 + 17 + 1 + 1 = 275。
- B 错274 少了 1 位:回写策略必须有脏位(修改位),有效位也不能省,两者缺一都只剩 274。
- C 错258 = 256 + 1 + 1,漏了 17 位 Tag;没有 Tag 就无法判断这一行装的是主存的哪一块。
- D 错257 = 256 + 1,既漏了 17 位 Tag,也漏了回写需要的脏位。
行的位数 = 数据 + Tag + 有效位 + (回写? 脏位) + (组相联/全相联? 替换位)。同类:2015-15(直接映射、回写,4K 字、块 4 字 × 32 位 → 1K 行,Tag 18 → 每行 128 + 18 + 2 = 148 → 148K 位);2010-44(8 行 × 64B 直接映射,不计一致性和替换位 → 每行 512 + 19 + 1,共 532B);2018-44(2 路 8 组、块 32B、Tag 20、LRU + 回写 → 每行 20 + 1 + 1 + 1 + 256 = 279 位,16 行共 4464 位 = 558B)。
替换算法与写策略
替换算法(组满了踢谁)
直接映射不需要(只有一个位置)。组相联/全相联:随机;FIFO(最早进来的);LRU 近期最少使用(最久没用的,最常考);LFU(用得最少的)。都由硬件实现。写策略(CPU 要写数据)
写命中:直写(全写法)同时写 Cache 和主存(可加写缓冲,不需要脏位);回写只写 Cache,设脏位,被替换时再写回主存。写不命中:写分配(先把块调入 Cache 再写,常配回写);非写分配(直接写主存,常配直写)。- A. 0
- B. 1
- C. 2
- D. 3
展开解析
B问“至少”,就假设一切都命中:取指令在 Cache 命中;xaddr 的地址转换在 TLB 命中(不用去主存查页表);读 x 在 Cache 命中。但写回 x 时,直写要求同时写主存——这一次省不掉。
- A 错0 次:只有回写(Write Back)策略下写操作才可能不访问主存。
- B 对至少 1 次(直写引起的写主存)。
- C 错2 次:把读 x 也算成访问主存了,但读可以在 Cache 命中。
- D 错3 次:取指、读、写全部算进来,是最坏情况之一,不是“至少”。
“至少访存几次”:TLB、Cache 都命中;直写的写操作仍要写主存。
替换算法跟做一遍:FIFO、LRU、LFU、随机
问题引入:组满了要踢一块出去,踢谁?四种算法的规则一句话就能说完,但同一个访问序列下它们的命中次数可以差很多——跟着做一遍才知道差在哪。
设定:全相联 Cache 共 3 行(相当于“一组 3 路”),初始为空,访问的主存块号依次为 4、2、3、2、2、4、3、1、4、1。LFU 中若几块的使用次数相同,先踢最早进来的那块。
| 访问 | 4 | 2 | 3 | 2 | 2 | 4 | 3 | 1 | 4 | 1 | 命中 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| FIFO | 缺 | 缺 | 缺 | 中 | 中 | 中 | 中 | 缺·换 4 | 缺·换 2 | 中 | 5 |
| LRU | 缺 | 缺 | 缺 | 中 | 中 | 中 | 中 | 缺·换 2 | 中 | 中 | 6 |
| LFU | 缺 | 缺 | 缺 | 中 | 中 | 中 | 中 | 缺·换 4 | 缺·换 1 | 缺·换 4 | 4 |
- 前 7 次三种算法一样:4、2、3 装满,之后 2、2、4、3 都命中。此时使用次数 2 号 3 次、4 号 2 次、3 号 2 次;最近一次使用:4 号在第 6 次、2 号在第 5 次、3 号在第 7 次。
- 访问 1 缺失,要踢一块:FIFO 踢最早进来的 4;LRU 踢最久没用的 2;LFU 踢使用次数最少的(4、3 都是 2 次,按约定踢先进来的 4)。
- FIFO、LFU 刚踢掉 4,紧接着又要用 4,再缺一次;LFU 还因为 2 号“历史上用得多”而一直把它留着,最后连续缺失。LRU 留下了最近用过的 4,后面两次都命中。
已用 verify/08/p08_calc.py 模拟验算。随机替换没有固定结果,它不看历史、硬件最简单,实际命中率并不比 FIFO 差多少。LRU 最符合局部性,最常用也最常考;LFU 会被“很久以前频繁使用、现在已经不用”的块拖累(例子里的 2 号);FIFO 不看使用情况,可能把常用的块踢出去。直接映射不需要替换算法(只有一个位置可放)。
写策略的四种组合与写缓冲
问题引入:读操作只关心命中率;写操作还多了一个麻烦——Cache 里的副本改了,主存里的原件怎么办?于是有了“写命中”和“写不命中”两组选择,再加一个减少等待的写缓冲。
| 写命中(块在 Cache 里) | 写不命中(块不在 Cache 里) | 常见搭配 | |
|---|---|---|---|
| 选项 1 | 全写法(直写):同时写 Cache 和主存 | 非写分配:只写主存,不把块调入 Cache | 全写 + 非写分配 |
| 选项 2 | 回写法:只写 Cache,置脏位,替换时再写回 | 写分配:先把块调入 Cache,再在 Cache 里写 | 回写 + 写分配 |
为什么这样搭配:回写法希望后面对同一块的写都在 Cache 里完成,所以写不命中时把块调进来(写分配)才划算;全写法反正每次都要写主存,把块调进 Cache 也省不了写主存的那一次,干脆不调(非写分配)。2015-16“直写方式下至少访问主存 1 次”就是全写法的直接后果。
多级 Cache,以及 Cache 行、TLB 项、页表项各存什么
问题引入:现代 CPU 有 L1、L2 甚至 L3 三级 Cache;第 8 篇后半还有 TLB 和页表。这几张“表”的每一项都存了标记、有效位之类的字段,选择题最爱问“哪个字段在哪里有、在哪里没有”。
多级 Cache:L1 小而快(常分成指令 Cache 和数据 Cache,避免流水线取指和访存冲突,2014-16),L2 大一些、慢一些,最后才访问主存。例:L1 命中时间 1 个周期、缺失率 5%;L2 命中时间 10 个周期、在 L1 缺失的访问中缺失率 20%;主存 100 个周期。
| 字段 | Cache 行 | TLB 表项 | 页表项(请求分页) |
|---|---|---|---|
| 标记 / 查找用的键 | Tag(主存块号的高位) | 标记 = 虚页号(全相联)或虚页号高位(组相联) | 无,直接用页号作下标 |
| 有效位 / 存在位 | 有效位 | 有效位 | 存在位(是否在内存) |
| 修改位 | 脏位(回写时才有) | 可以有(与页表项同步) | 修改位(换出时要不要写回磁盘) |
| 替换信息 | LRU 计数位 | LRU 位 | 访问位(给 CLOCK 等置换算法用) |
| 内容 | 数据块的副本 | 物理页框号 | 物理页框号、外存地址、保护位 |
| 谁维护 | 硬件 | 硬件(MMU) | 操作系统(硬件查) |
平均访问时间已用 verify/08/p08_calc.py 验算。页表项各字段的作用详见第 8 篇后半 8.7“请求分页的页表项”。
程序怎么写,命中率差很多:数组遍历
分析方法(四步):① 元素多大、一块能放几个元素;② 数组起始地址是否对齐到块;③ 按程序的访问顺序,每一块第一次被访问时缺失,块内后续访问命中(顺序访问时);④ 检查会不会还没用完就被替换掉(同一行/组被别的块抢走)。
展开解析
- 地址切分:256MB = 228 → 28 位;块内 6 位,行号 3 位,Tag 19 位。每行 512 + 19 + 1 = 532 位,8 行 = 4256 位 = 532B。
- 行号 = 块号 mod 8:a[0][31] 地址 = 320 + 31×4 = 444,块号 444/64 = 6 → 行 6。a[1][1] 地址 = 320 + (256+1)×4 = 1348,块号 21 → 21 mod 8 = 行 5。
- 程序 A(按行):顺序访问,320 是 64 的倍数(块对齐),一块放 16 个 int,每块第一个元素缺失、后 15 个命中 → 命中率 15/16 = 93.75%。
- 程序 B(按列):相邻两次访问 a[i][j] 与 a[i+1][j] 相差一行 = 1024B = 16 块,而 16 mod 8 = 0 → 映射到同一个 Cache 行。每次访问都把上一次调进来的块挤掉,等回头访问 a[0][j+1] 时它早被替换了 → 命中率 0。
- A 快得多。
for(i=0;i<24;i++) for(j=0;j<64;j++) a[i][j]=10;,int a[24][64] 按行优先,起始虚拟地址 0042 2000H,执行前 a 不在主存,执行中不发生页面置换。(1)a 分几个页面?会发生几次缺页?页故障地址?(2)数据访问有无时间局部性?(3)虚拟地址哪几位是块内地址、哪几位是 Cache 组号?a[1][0] 的虚拟地址及其组号?(4)a 占多少主存块?命中率?若把循环改成先 j 后 i,命中率又是多少?展开解析
- (1)a 大小 24×64×4 = 6144B = 6KB。起点 00422000H 是页的开头(低 12 位为 0),占 00422000H~004237FFH → 2 页,2 次缺页,页故障地址就是第一次访问每页时的地址:0042 2000H、0042 3000H。
- (2)每个元素只写一次 → 没有时间局部性(有空间局部性)。
- (3)块 32B → 块内 A4~A0;组数 8KB/(32B×4) = 64 → 组号 A10~A5。它们都在页内偏移(低 12 位)里,所以虚拟地址和物理地址的这几位相同,可以直接用虚拟地址算。a[1][0] = 00422000H + 64×4 = 00422100H,低 12 位 100H = 0001 0000 0000,A10~A5 = 001000 = 组 8。
- (4)6144/32 = 192 块。按行顺序写:每块 8 个 int,首次缺失、后 7 次命中 → 7/8 = 87.5%。改成按列:a[i][j] 与 a[i+1][j] 相差 256B = 8 块;对同一组,i、i+8、i+16 三行会落在同一组(192 块 ÷ 64 组 = 3 块/组),4 路装得下,不会被替换,所以每块仍然只缺失 1 次 → 仍是 87.5%。
对比 2010-44:差别在于 2023 的 Cache 能装下“一列要用到的所有块”(组相联 + 数组小),2010 的直接映射装不下。
int x, d[2048], i; for(i=0;i<2048;i++) d[i] = d[i]/x; 数组 d 起始虚拟地址 0180 0020H。(1)Cache 组号、块内地址各几位?VA 中哪些位可以作 Cache 索引?(2)d[100] 的 VA 及其组号?(3)代码已在 Cache,i、x 在内存但不在 Cache,d[0] 在块内的偏移?访问 d 的缺失率?(4)d 分布在几个页中?若 d 不在主存,访问 d 引起几次缺页?展开解析
- (1)512 行 / 8 路 = 64 组 → 组号 6 位;块内 6 位。两者共 12 位,恰好在页内偏移里 → VA 的 第 6~11 位就是组号,可直接用虚拟地址索引 Cache。
- (2)d[100] = 01800020H + 400 = 01800020H + 190H = 0180 01B0H;低 12 位 1B0H = 0001 1011 0000,第 11~6 位 = 000110 = 组 6。
- (3)d[0] 块内偏移 = 20H = 32(没有对齐到块开头)。d 共 8KB,从某块中间开始,会占 8192/64 + 1 = 129 块。每个元素先读后写,共 2048×2 = 4096 次访问,每块只在第一次访问时缺失 → 缺失率 = 129/4096 ≈ 3.15%。平均访问时间按“命中时间 + 缺失率 × 缺失损失”= 2 + 0.0315×200 ≈ 8.30 个周期(也有资料按“缺失时共 200 周期”算得 ≈ 8.24,写清所用公式即可)。
- (4)8KB 从页内偏移 020H 开始,跨过 01800、01801、01802 三页 → 3 页,3 次缺页。
同类:2016-15 a[k] = a[k] + 32(每块 4 个 int,每个元素读一次写一次,8 次访问只第 1 次缺失 → 缺失率 12.5%);2020-44(s[1024] 从 008000C0H 开始,块 64B 对齐,读写各一次,1024×4B/64B = 64 块 → 缺失 64 次);2014-45 第 2 问(程序段 6 条指令 24 字节都在一个 32B 块内,1000 次循环取指 6000 次只第 1 次缺失 → 99.98%)。
展开解析
- 页内 12 位(A11~A0),虚页号 18 位(A29~A12)。
- 8 组 → 组号 3 位 = 虚页号最低 3 位(A14~A12);标记 15 位(A29~A15)。TLB 的组相联和 Cache 的组相联完全一样,只是“地址”换成了虚页号。
- 组号 = 虚页号 mod 8:10→2,12→4,16→0,7→7,26→2,4→4,12→4(命中,12 变成最近使用),20→4(组 4 已满:12 和 4,LRU 踢 4)。上面的工具已填好这组数据(16 行、2 路 = 8 组)。
- 虚页号多 2 位 → 标记多 2 位 → 每个表项多 2 位。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 8
- B. 14
- C. 46
- D. 11
展开解析
B块号 = ⌊3000 ÷ 64⌋ = 46。组数 = 64 ÷ 4 = 16,组号 = 46 mod 16 = 14。
变化点:两步不能省:先算块号,再对组数取模。A 是直接拿地址对 16 取模,C 是只算了块号。
- A. 276
- B. 277
- C. 278
- D. 286
展开解析
C行数 = 16KB ÷ 32B = 512,组数 = 256,组号 8 位,块内地址 5 位,Tag = 32 − 8 − 5 = 19 位。每行 = 数据 256 + Tag 19 + 有效位 1 + 脏位 1 + LRU 位 1 = 278 位。
变化点:2021-16 是直接映射(Tag 17 位、没有 LRU 位,共 275 位)。改成组相联后,Tag 变长,还要加 LRU 位(2 路时每行 1 位)。
- A. 4,18
- B. 4,21
- C. 512,18
- D. 2048,18
展开解析
A组数 = 64KB ÷ 32B ÷ 4 = 512,组号 9 位,Tag = 32 − 9 − 5 = 18 位。查找时只在选中的那一组里同时比较 4 行,所以比较器 4 个,每个 18 位。
变化点:2022-16 是 8 路、块 64B(8 个,20 位)。比较器个数 = 路数,与组数无关;位数 = Tag 位数。
for (k = 0; k < 1000; k++) a[k] = a[k] + 32; 中,数组 a 为 int 型(4B)。数据 Cache 采用直接映射,数据区 1KB,块大小 32B,程序执行前 Cache 为空。访问数组 a 的缺失率是( )。- A. 3.125%
- B. 6.25%
- C. 12.5%
- D. 25%
展开解析
B每块放 8 个元素,每个元素读 1 次、写 1 次,一块共访问 16 次,只有第一次读缺失。缺失率 = 1/16 = 6.25%。
变化点:2016-15 的块大小是 16B(4 个元素,缺失率 1/8 = 12.5%)。块大小翻倍,缺失率减半。每个元素访问两次(读 + 写)这一点不能漏:只算读会得到 12.5%。
- A. 1
- B. 2
- C. 3
- D. 4
展开解析
A行号 = 地址 mod 4。0、4、8 都映射到第 0 行,2、6 都映射到第 2 行。逐个看:0 缺、4 缺(换出 0)、8 缺、2 缺、0 缺、6 缺(换出 2)、8 缺、6 命中、4 缺、8 缺,只命中 1 次。
变化点:2012-17 是 2 路组相联 + LRU(命中 3 次)。同样的访问序列,直接映射下 0、4、8 挤在同一行互相替换,命中次数大大减少。
- A. 直接映射方式也需要使用 LRU 替换算法
- B. 采用全写法时,每个 Cache 行都需要一个脏位
- C. 写缓冲可以减少 CPU 因等待写主存而停顿的时间
- D. 回写法通常与非写分配法配合使用
展开解析
C写缓冲让 CPU 把要写主存的数据放进缓冲区就继续执行,由缓冲区慢慢写入主存。A 错:直接映射只有一个位置,不需要替换算法。B 错:全写法主存总是最新的,不需要脏位,脏位是回写法才要的。D 错:回写法常配写分配法,全写法常配非写分配法。
考点:写策略的四种组合与写缓冲、替换算法。
8.4操作系统内存怎么分给进程:连续分配
- 逻辑地址与物理地址:程序里用的地址是从 0 开始编的逻辑地址(也叫虚拟地址、相对地址);内存条上存储单元真正的编号是物理地址。两者之间的转换叫地址变换(重定位),第 3 篇 3.5 讲了静态和动态两种。
- 内存分配与回收:连续分配(本节)或离散分配(分页 8.5、分段 8.6)。
- 内存保护:进程只能访问自己的内存,不能碰操作系统和别的进程。两种做法:① 设一对上、下限寄存器,存进程所在区域的起止物理地址,每次访存比较;② 重定位寄存器 + 界地址寄存器:先检查逻辑地址 < 界地址(越界就产生异常),再加上重定位寄存器得到物理地址(图 3-13)。分页系统里靠页表长度寄存器和页表项里的保护位。
- 内存共享:多个进程共用同一块物理内存(同一个程序的代码、共享库、共享内存通信)。分页/分段中让各进程的页表项/段表项指向同一个页框/段即可(8.6)。被共享的代码必须是可重入代码(纯代码:执行过程中不修改自己,每个进程的数据放在各自的数据区)。
- 内存扩充了解:早期的两种办法——覆盖:程序员把不会同时运行的模块安排在同一块内存区轮流使用(要程序员自己设计,对用户不透明);交换(对换):把暂时不能运行的进程整个换出到磁盘的对换区,腾出内存给别的进程,需要时再换入(就是中级调度,6.6)。虚拟内存(8.7)可以看成它们的“自动化升级版”。
现在切到操作系统的视角:内存里要同时放多个进程,操作系统得决定每个进程放在哪、占多少。最早的办法是给每个进程一块连续的区域:
- 单一连续分配:内存只放一道用户程序。
- 固定分区:预先切成若干固定大小的分区,一个分区放一个进程——分区里用不完的部分浪费了,叫内部碎片。
- 动态分区:进程来了按需切一块。进程走了留下的空洞越来越小、越来越碎,每块都放不下新进程,叫外部碎片(可以“紧凑”:把进程挪到一起,需要动态重定位,第 3 篇)。
| 动态分区算法 | 从空闲分区里选 | 特点 |
|---|---|---|
| 首次适应 FF | 按地址递增找第一个够大的 | 简单、效果通常最好;低地址会留很多小碎片 |
| 邻近适应 NF | 从上次找到的位置接着找 | 空闲分区分布均匀,但大分区也被切碎 |
| 最佳适应 BF | 按容量递增找第一个够大的(最小的够用的) | 留下的碎片最小但最多、最容易产生外部碎片(2019-32) |
| 最坏适应 WF | 找最大的 | 大分区很快用完 |
| 伙伴算法 | 分区大小都是 2 的幂,回收时只和大小相等的“伙伴”合并 | 2024-27;Linux 回收页框用它 |
四种基本算法的区别其实就是空闲分区链怎么排序:首次适应按起始地址递增;最佳适应按容量递增;最坏适应按容量递减;邻近适应(循环首次适应)按地址递增、但从上次停下的位置接着找。分配时都是“从链首往后找第一个够大的”。
展开解析
| 请求 | 首次适应 FF | 最佳适应 BF | 最坏适应 WF |
|---|---|---|---|
| 212K | 500K 分区(剩 288K) | 300K 分区(剩 88K) | 600K 分区(剩 388K) |
| 417K | 600K 分区(剩 183K) | 500K 分区(剩 83K) | 500K 分区(剩 83K) |
| 112K | 剩下的 288K(剩 176K) | 200K 分区(剩 88K) | 剩下的 388K(剩 276K) |
| 426K | 没有够大的,等待 | 600K 分区(剩 174K) | 没有够大的,等待 |
这组数据里只有最佳适应把 4 个请求都满足了:它总挑“刚好够”的,把大分区(600K)留给了后来的大请求。但它的代价是留下了 88K、83K、88K 这样的小碎片。所以“哪个算法最好”没有绝对答案,要看请求序列。(结果已用程序模拟验算。)
回收分区:四种合并情况
进程释放一个分区时,要看它和前后的空闲分区是否地址相邻,相邻就合并(2017-25):
| 情况 | 怎么处理 | 空闲分区数 |
|---|---|---|
| 只和前面一个空闲区相邻 | 并入前面那块:前块的起始地址不变,大小加上回收区 | 不变 |
| 只和后面一个空闲区相邻 | 和后块合并:起始地址改成回收区的起始地址,大小相加 | 不变 |
| 和前后两块都相邻 | 三块合成一块:用前块的起始地址,大小是三者之和,删去后块的表项 | 减 1 |
| 和前后都不相邻 | 新建一个表项,填回收区的起始地址和大小 | 加 1 |
- A. 7MB
- B. 9MB
- C. 10MB
- D. 15MB
展开解析
B最佳适配 = 每次选“够用的里面最小的”空闲分区。按顺序把内存画出来(单位 MB)。
- 分配 15、30:[15 用][30 用][10 空]。
- 释放 15:[15 空][30 用][10 空]。
- 分配 8:空闲区 15 和 10 都够,选更小的 10 → [15 空][30 用][8 用][2 空]。
- 分配 6:空闲区 15 和 2,只有 15 够 → [6 用][9 空][30 用][8 用][2 空]。空闲区为 9 和 2,最大 9MB。
- A 错7MB 是最坏适配的结果:分配 8 时选最大的 15(剩 7),分配 6 时再选最大的 10(剩 4),最大空闲区 7。
- B 对最佳适配后空闲区为 9MB 和 2MB,最大 9MB。
- C 错10MB 是首次适配的结果:8 和 6 都从地址最低的 15 里切(剩 1),后面的 10MB 没动。
- D 错15MB 是“释放 15MB 之后、还没分配 8MB 和 6MB”时的最大空闲区,后两次分配没做。
同一组请求,首次适配(10)、最佳适配(9)、最坏适配(7)结果不同,考场上三种都可能被拿来做干扰项。同类:2017-25(回收 60K 起 140KB 的分区,与前后两块都相邻 → 合成 20K 起 380KB;最佳适应按容量递增排序,链首是 500K 起 80KB)。
保护:每个进程只能访问自己的分区,用重定位寄存器 + 界地址寄存器检查越界(2009-26,图 3-13)。连续分配的根本问题是碎片和“必须一整块”,于是有了下面的分页。
单一连续与固定分区,以及内存保护的两种做法
问题引入:动态分区之前还有两种更简单的分配方式;不管哪种方式,都得防止一个进程越界去改别人的内存。保护的两种硬件做法,选择题常让你“看图判越界”。
| 单一连续分配 | 固定分区分配 | 动态分区分配(上文) | |
|---|---|---|---|
| 做法 | 内存分为系统区和用户区,用户区只放一道程序 | 事先把用户区划成若干个固定的分区(大小可相等也可不等),每个分区装一道作业 | 按作业的需要临时划分 |
| 多道 | 不支持 | 支持,道数 = 分区数 | 支持 |
| 碎片 | 内部碎片 | 内部碎片 | 外部碎片 |
| 缺点 | 只能单用户单任务,内存利用率低 | 程序太大可能哪个分区都放不下(只能用覆盖);小程序占大分区浪费 | 碎片越来越多,要紧凑 |
真题怎么考:2009-26(分区分配内存管理方式的主要保护措施是界地址保护)、碎片类型对照(本节下面的表)。判别法:上下限寄存器比的是物理地址;界地址寄存器比的是逻辑地址(比之前不加重定位值)。
| 分配方式 | 内部碎片(分给进程但用不完) | 外部碎片(空闲但太小、太散,谁都用不上) |
|---|---|---|
| 单一连续分配 | 有(整个用户区给一道程序) | 无 |
| 固定分区 | 有 | 无 |
| 动态分区 | 无(按需切) | 有(用“紧凑”解决) |
| 分页(8.5) | 有,但很小(只在每个进程的最后一页,平均半页) | 无 |
| 分段(8.6) | 无 | 有(段长不一,像动态分区) |
| 段页式(8.6) | 有(每段的最后一页) | 无 |
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 4MB
- B. 8MB
- C. 10MB
- D. 14MB
展开解析
B分配 20、30 后,空闲区为 50~64(14MB)。释放 20MB 后,空闲区为 0~20(20MB)和 50~64(14MB)。首次适应分配 12MB:从 0 开始,剩 12~20(8MB)。分配 10MB:8MB 不够,用 50~60,剩 60~64(4MB)。最大空闲分区为 8MB。
变化点:2010-28 用最佳适应。同一组操作用最佳适应:12MB 放进 14MB 的分区,10MB 放进 20MB 的分区,最大空闲分区是 10MB(C)。算法一换,结果就变。
- A. 首次适应
- B. 最佳适应
- C. 最坏适应
- D. 邻近适应
展开解析
C最坏适应每次都从最大的空闲分区中切出一块,很快就没有大的空闲分区了。它的好处是切剩的部分还比较大,不容易留下很小的碎片。
变化点:2019-32 问哪种算法最容易产生碎片(最佳适应,切剩的部分最小)。最佳适应和最坏适应的优缺点正好相反。
8.5融合分页:把进程切成等大的“页”
分页的思路:把进程的逻辑地址空间切成大小相等的页(如 4KB),把物理内存也切成同样大小的页框(也叫页帧、物理块),每一页可以放进任意一个空闲页框,不必连续。于是没有外部碎片,只有每个进程最后一页可能有一点内部碎片。
代价:操作系统要为每个进程维护一张页表,记录“第几页放在第几个页框”;CPU 每次访存都要查页表把逻辑地址变成物理地址——这由硬件 MMU(地址变换机构)完成。
- 页表寄存器 PTR 里放着当前进程页表的起始地址和页表长度(进程切换时由 OS 装入,第 6 篇)。
- 把逻辑地址切成页号 P 和页内偏移 W(二进制地址直接按位切;题目给十进制地址 A 时,P = ⌊A ÷ L⌋、W = A mod L,L 为页面大小。例:页 1KB,A = 5000 → P = 4、W = 904,因为 4 × 1024 + 904 = 5000);若 P ≥ 页表长度,产生越界中断。
- 页表项地址 = 页表起始地址 + P × 页表项长度,读出页表项,得到页框号 b。
- 物理地址 = b 拼接 W(= b × 页面大小 + W)。
① 页表项里不存页号:页表是按页号顺序排的数组,页号就是下标,只存页框号(和标志位)。
② 页表项的长度至少要能放下页框号:物理地址 24 位、页 4KB → 页框号 12 位 → 至少 2 字节(实际常取 4 字节,多出的位放存在位、保护位等)。
③ 页面大小的取舍:页小 → 页内碎片小,但页数多、页表大,换页次数多;页大 → 页表小、换页少,但页内碎片大。页面大小由硬件(地址结构)决定,必须是 2 的幂。
④ 分页对用户透明,逻辑地址是一维的(给出一个地址就够,系统自己切页号、偏移)。
- A. 得到主存地址 02 4840H
- B. 得到主存地址 18 0840H
- C. 得到主存地址 01 8840H
- D. 检测到缺页异常
展开解析
C陷阱在进制:地址里的页号是十六进制,表里的页号是十进制。
- 页 4KB → 低 12 位 840H 是偏移,高 20 位 00082H 是页号。
- 00082H = 8 × 16 + 2 = 十进制 130。
- 查表:页 130 存在位 1,页框号 018H。
- 拼接:018H 拼 840H = 01 8840H(主存 24 位 = 6 个十六进制位)。
- A 错把 82H 当成十进制 82,查到页框 024H(何况页 82 存在位为 0)。
- B 错用了页 129 的页框号 180H。
- C 对01 8840H。
- D 错同样把页号当成 82,看到存在位 0 就以为缺页。
先统一进制再查表:页号要从十六进制换算成表中所用的进制。
展开解析
- 页大小 212 = 4KB;页表最大 220 项 × 4B = 4MB。
- 页目录号 =
(LA >> 22) & 0x3FF;页表索引 =(LA >> 12) & 0x3FF(第 0 篇的“右移去掉低位、与掩码留下几位”)。 - 0000 8000H 的页号 = 8,8KB 占第 8、9 两页。页表项地址 = 0020 0000H + 8×4 = 0020 0020H,下一项 0020 0024H。代码装在 0090 0000H 起连续两个页框 → 页框号 00900H、00901H;页面 2 起始物理地址 = 0090 1000H。
快表 TLB:页表的“Cache”
每次访存都多访问一次主存查页表,太慢。办法同 Cache:把最近用过的页表项放在 CPU 里一个很小很快的相联存储器里,叫快表 TLB(页表本身叫慢表)。先查 TLB,命中就不用访问主存里的页表。
例:tT = 20ns、tm = 100ns、h = 90%:先查 TLB 时 EAT = 0.9 × 120 + 0.1 × 220 = 130ns;同时查时 EAT = 0.9 × 120 + 0.1 × 200 = 128ns;不用快表是 200ns。TLB 的命中率高,是因为程序有局部性,最近用过的几页会被反复访问。
展开解析
- 页 4KB → 低 12 位是偏移,最高的十六进制位是页号:2362H 页 2,1565H 页 1,25A5H 页 2。
- 2362H:查 TLB 未命中 10 + 查页表 100(有效)+ 访存取数 100 = 210ns(此后页 2 进入 TLB)。
- 1565H:TLB 10 + 页表 100 发现有效位 0 → 缺页处理 108(含更新 TLB、页表)→ 重新执行这条指令:TLB 10(命中)+ 访存 100 → 10 + 100 + 108 + 10 + 100 = 100000220ns。
- 25A5H:页 2 已在 TLB → 10 + 100 = 110ns。
- (2)驻留集为 2,内存里已有页 0、页 2,页 1 缺页必须淘汰一页。LRU:页 2 刚访问过,页 0 最久没用 → 淘汰页 0,页 1 装入页框 101H → 物理地址 = 101565H。
多级页表:页表太大怎么办
32 位地址、4KB 页 → 220 个页表项 × 4B = 4MB,而且必须连续存放(要用“起始地址 + 页号×4”定位)。办法:给页表再分页。把页号再切成两段:页目录号 | 二级页表索引。页目录(一级页表)只有一页,记录每张二级页表放在哪个页框;二级页表也各占一页,用不到的二级页表可以不建、不调入内存。
每一级页表都正好占一页:一页能放 页面大小 ÷ 页表项大小 个表项,所以每级索引位数 = log2(页面大小 / 页表项大小)。4KB 页、4B 表项 → 每级 10 位;再加 12 位偏移,32 位地址正好两级。2010-29:页 210B、表项 2B → 一页放 29 项;共 216 页 → 需要 216/29 = 128 页来放页表 → 页目录至少 128 项。2026-28:64 位系统三级页表各 9 位、偏移 12 位(每页 512 个 8B 表项),第三级页表有 29×29 = 256K 张,各占一个页框。
展开解析
- 一行 1024 × 4B = 4KB = 恰好一页。a[1][2] = 1080 0000H + 1×4096 + 2×4 = 1080 1008H。
- 二进制 0001 0000 10|00 0000 0001|0000 0000 1000:页目录号 = 00 0100 0010 = 042H,页号 = 001H。
- 页目录项地址 = 0020 1000H + 42H × 4 = 0020 1000H + 108H = 0020 1108H。
- 二级页表在页框 00301H,起始物理地址 0030 1000H;页表项地址 = 0030 1000H + 1 × 4 = 0030 1004H。
- (2)虚拟地址空间必须连续(数组下标靠“首地址 + 偏移”计算);物理地址空间不必连续,每页可以在任意页框。
- (3)按行:每行正好一页,顺序访问 → 局部性好。按列:相邻两次访问在不同页,按行遍历好。
同类:2015-46(01000000H 和 01112048H 的最高 10 位都是 4 → 只访问 1 张二级页表;全部页表共 1 + 1024 = 1025 页);2018-45(页目录号 6、页号 6、偏移 8 → 0180 6008H);2019-31(2050 1225H → 页目录号 081H、页号 101H);2024-45(4MB 大页、页号 10 位、偏移 22 位:12345678H 页号 048H,其页表项虚拟地址 B8C00000H + 48H×4 = B8C00120H、物理地址 65400120H,缺页后映射到 BAB45678H → 页框号 2EAH)。
反置页表:按页框建表,全系统只要一张 拓展
问题:普通页表按虚页号建表,每个进程一张。32 位地址、4KB 页、页表项 4B 时,每个进程的一级页表就是 4MB,100 个进程就是 400MB;64 位地址更不用说。能不能让页表的大小只和物理内存有关?
大白话:普通页表像“每个学生一张清单,写着自己的书放在哪个柜子”;反置页表(也叫倒排页表)反过来,像“图书馆每个柜子贴一张条,写着这里放的是谁的哪本书”。柜子数是固定的,条子数也就固定。
定义:反置页表为每个物理页框设一个表项,内容是(进程标识 PID,虚页号),按页框号顺序排列,整个系统只有一张。表的大小只与物理内存容量有关,与虚拟地址空间多大、进程有多少无关。
跟我做一遍:物理内存 1GB、页 4KB → 230 ÷ 212 = 218 = 262144 个页框;每项 8B(PID + 虚页号 + 哈希链指针)→ 反置页表 2MB,全系统只要这一张。普通一级页表 100 个进程要 400MB(已用程序验算)。
代价:① 不能用页号直接当下标,只能搜索,通常用哈希表加速,再配 TLB;② 一个表项只能记一个(PID,页号),多个进程共享同一页框不好表示;③ 不在内存的页在表里没有记录,缺页时还要另外的外部页表告诉 OS 它在磁盘的哪里。
真题怎么考:408 真题还没有考过反置页表。记住“按页框建表、全系统一张、大小只与物理内存有关、查找要搜索”即可,下面变式卡有一道判断题。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 048H、345H
- B. 123H、456H
- C. 048H、145H
- D. 091H、345H
展开解析
A1234 5678H = 0001 0010 00 | 11 0100 0101 | 0110 0111 1000B。页目录号 = 00 0100 1000B = 048H,页号 = 11 0100 0101B = 345H。
变化点:10 位的字段不是 4 的倍数,不能直接按十六进制位切(B 就是这样切错的)。展开成二进制再按 10、10、12 切。
- A. 14
- B. 17
- C. 20
- D. 18
展开解析
B虚页号 32 − 12 = 20 位。TLB 组数 = 64 ÷ 8 = 8,组号 3 位。标记 = 20 − 3 = 17 位。
变化点:2024-17 是 1KB 页、32 项 4 路(22 − 3 = 19)。TLB 的“地址”是虚页号,组号取虚页号的低位,其余是标记。C 是全相联时的答案。
- A. 2
- B. 3
- C. 4
- D. 5
展开解析
C一页能放 4KB ÷ 8B = 512 个页表项,每级索引 9 位。虚页号 = 48 − 12 = 36 位,36 ÷ 9 = 4 级。
变化点:公式:每级索引位数 = log2(页大小 ÷ 页表项大小),级数 = ⌈虚页号位数 ÷ 每级位数⌉。
- A. 每个进程各有一张反置页表
- B. 反置页表的表项数由虚拟地址空间的大小决定
- C. 反置页表按虚页号顺序排列,可以直接用虚页号作下标查找
- D. 反置页表的大小只与物理内存容量有关
展开解析
D反置页表每个物理页框一项,整个系统一张,项数 = 页框数,只与物理内存有关。A 错:全系统只有一张。B 错:那是普通页表。C 错:它按页框号排列,要用(进程号,虚页号)去搜索,常用哈希表加速。
考点:反置页表。
8.6分段与段页式
分页是给机器看的(等长,方便管理),分段是给程序员看的:按程序的逻辑把地址空间分成代码段、数据段、栈段……每段长度不同,段内地址从 0 开始。逻辑地址 = 段号 | 段内地址,要用段表(段号 → 段基址、段长)转换。分段存储管理也叫段式存储管理(分页也叫页式);计组教材讲的“段式虚拟存储器”就是分段再加上请求调入,见本节末尾 段式虚拟存储器。
分段的地址变换
① 段号 ≥ 段表长度 → 越界中断;② 查段表得到基址和段长;③ 段内地址 ≥ 段长 → 越界中断;④ 物理地址 = 基址 + 段内地址(真的要做加法,不是拼接);⑤ 检查权限。要点
段号 8 位、地址 32 位 → 段内地址 24 位 → 最大段长 224 字节(2009-27)。便于按逻辑模块共享和保护(如只共享代码段)。有外部碎片。- A. 段缺失异常
- B. 得到内存地址 4400
- C. 越权异常
- D. 越界异常
展开解析
D分段地址变换的检查顺序:段号是否越界 → 查段表 → 段内地址是否 ≥ 段长(越界) → 检查状态和权限 → 物理地址 = 基址 + 段内地址。段 2 长 300,段内地址 400 ≥ 300 → 越界异常。
- A 错段 2 的状态是“在内存”,不会段缺失。
- B 错4000 + 400 = 4400 是没做越界检查的结果。
- C 错段 2 可读写,访问不会越权(而且越界检查先发生)。
- D 对越界异常。
分段必须检查段内地址 < 段长(分页不需要,页内偏移天然不会越界)。
| 分页 | 分段 | |
|---|---|---|
| 单位 | 页是物理单位,为了提高内存利用率、消除外部碎片而划分 | 段是逻辑单位,按程序模块(代码、数据、栈)划分,为了方便编程、共享和保护 |
| 大小 | 固定,由系统(硬件)决定 | 不固定,由程序决定 |
| 地址空间 | 一维:只需给出一个地址 | 二维:要同时给出段号和段内地址 |
| 对用户 | 透明 | 可见(程序员 / 编译器要知道分段) |
| 碎片 | 无外部碎片,有少量内部碎片 | 无内部碎片,有外部碎片 |
| 共享与保护 | 不方便(一页里可能混着能共享和不能共享的内容) | 方便(按段整体共享,按段设置读写权限) |
| 地址变换 | 页框号 拼接 偏移 | 段基址 加 段内地址,并检查段长 |
段页式:先分段,每段再分页。逻辑地址 = 段号 | 页号 | 页内偏移;每个进程一张段表,每段一张页表。地址变换:① 段号与段表长度比较,越界则中断;② 查段表项,得到该段页表的起始地址和页表长度;③ 页号与页表长度比较,越界则中断;④ 查页表项得到页框号;⑤ 页框号拼接页内偏移。不用快表时一次访存要访问主存 3 次(段表、页表、数据)。段页式兼有分段便于共享保护、分页没有外部碎片的优点,地址空间仍是二维的。
两个进程共享同一个段/页:在各自的段表/页表里,各写一个表项指向同一块物理内存。共享的东西在两个进程里的段号/页号不一定相同,但物理页框号一定相同(2023-30 选 C;2019-28 “段 S 在 P1 和 P2 中应该具有相同的段号”错)。2026-30:两进程共享文件 R,映射的虚拟地址不一定相同,但映射到的物理地址相同,一方修改另一方可见。
段式虚拟存储器:计组和操作系统说的是同一件事
问题:计组教材讲虚拟存储器时分“页式、段式、段页式”三种,操作系统讲的是“请求分页、请求分段”。它们是什么关系?
大白话:把 8.5、8.6 的分页、分段、段页式再加上“不在内存就去磁盘调、内存满了就换出”的能力,就是对应的虚拟存储器。计组关心硬件怎么查表,操作系统关心缺了怎么调、满了换谁。所以 页式虚拟存储器 = 请求分页,段式虚拟存储器 = 请求分段。
定义:段式虚拟存储器的段表项在 8.6 段表(段号、段长、基址、权限)的基础上增加 存在位(装入位)、访问位、修改位、外存始址,以及增补位(这一段运行中能否动态增长)。访问的段不在内存,就产生缺段中断。
跟我做一遍:内存中有 3 块互不相邻的空闲区 30K、20K、40K,要调入长 50K 的段。① 最大的一块 40K < 50K,没有能直接放下的;② 总和 90K ≥ 50K → 先紧凑成一块 90K,再调入,剩 40K。若要调入 100K 的段:总和 90K 也不够 → 只能淘汰若干段,再回到第 ① 步。
| 页式虚拟存储器 | 段式虚拟存储器 | 段页式虚拟存储器 | |
|---|---|---|---|
| 虚拟地址 | 页号 | 页内地址 | 段号 | 段内地址 | 段号 | 页号 | 页内地址 |
| 优点 | 页长固定,页表简单,调入方便;没有外部碎片 | 按程序逻辑分段,便于编译、修改、保护和共享 | 兼有两者的优点 |
| 缺点 | 页不是逻辑单位,共享和保护不方便;有页内碎片 | 段长不定,主存分配麻烦,段间留下外部碎片 | 要查段表和页表两次,地址变换慢 |
| 不用快表时一次访存访问主存 | 2 次 | 2 次 | 3 次 |
真题怎么考:段表越界、越权(2016-28);共享段的段号(2019-28);段页式访存次数和优缺点比较。
① “段式”“分段”是同一个意思,“页式”“分页”也是;题目说“段式虚拟存储器”就是请求分段。② 段页式的地址空间仍是二维的(程序员看到的是段)。③ 分段消除了内部碎片,却带来外部碎片,所以缺段处理里会出现“紧凑”这一步,缺页处理里没有。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 段缺失异常
- B. 得到内存地址 3150
- C. 越权异常
- D. 越界异常
展开解析
C先查段长:150 < 200,不越界。再查权限:该段只读,而这次是写操作,产生越权异常。
变化点:2016-28 是段内地址超过段长(越界)。这里地址没超,但操作类型不允许。做这类题时先查段内地址是否超过段长,再查这次操作是否符合该段的权限。
- A. 该页在两个进程中的页号一定相同
- B. 两个进程页表中对应的页表项都指向页框 f
- C. 一个进程修改该页的内容,另一个进程看不到
- D. 被共享的页只能设为只读
- A. 先紧凑(拼接)空闲区,再调入该段
- B. 把该段拆成几部分分别装入各空闲区
- C. 终止该进程
- D. 只能淘汰内存中的若干段
展开解析
A一段必须放在连续的内存里,所以 B 不行。空闲区总和够,把它们紧凑成一块就能放下,不必淘汰别的段(D 是总和也不够时才做的)。缺段是正常的调入请求,不是错误,C 错。
考点:段式虚拟存储器的缺段处理(已用程序验算例题)。
8.7融合虚拟内存:内存不够也能跑大程序
根据局部性原理,程序运行时并不需要全部装入内存。虚拟内存:只装入当前要用的几页就开始运行;访问到不在内存的页时,由硬件发出缺页异常,操作系统把它从磁盘调入(请求调页);内存满了就选一页换出(页面置换)。于是进程看到的“内存”可以比物理内存大得多——虚拟内存的大小由地址位数决定,和内存、磁盘多大无关(2012-25、2023-28)。它只能建立在离散分配(分页/分段)的基础上。
虚拟存储器的三个特征
多次性:一个作业分成多次调入内存运行,不必一次全部装入;对换性:作业运行期间可以换入、换出;虚拟性:从逻辑上扩充了内存容量,用户看到的容量远大于实际容量。实际能用的最大容量 = min(地址位数决定的范围,内存 + 外存容量)。缺页中断和一般中断的区别
① 缺页是在指令执行期间产生并处理的(一般中断在一条指令执行完才检查);② 一条指令执行期间可能发生多次缺页(例如指令本身跨两页、要访问的数据又跨两页);③ 处理完返回本条指令重新执行,而不是下一条。它属于内中断(故障)。请求分页的页表项
存在位给 MMU 判断缺页;访问位给置换算法(CLOCK)用;修改位决定换出时要不要写回磁盘;外存地址告诉 OS 去哪调入。改进型 CLOCK 需要访问位和修改位(2018-45 第 3 问)。
缺页处理:一次完整的“硬件—操作系统—磁盘”接力
展开解析
只有访问数据的 load 指令(指令 3)可能缺页(代码已在内存)。A 在同一页 → 读磁盘 1 次。查 TLB:1000 次访问各查一次,再加上第一次访问 A[0] 缺页后重新执行那条指令又查一次 → 1001 次。
何时调入、从哪调入:请求调页与预调页
问题:缺页了才去磁盘读一页,一次只读一页,进程刚启动时要连续缺页好多次,能不能一次多读几页?
大白话:请求调页像“饿了才去买一个包子”;预调页像“顺手把旁边几个也买了,猜你待会儿也要吃”——猜中了省跑腿,猜错了白占地方。
| 策略 | 什么时候调入 | 优点 | 缺点 / 用在哪 |
|---|---|---|---|
| 请求调页 | 访问到不在内存的页(缺页)时,只调这一页 | 调入的页一定会被访问 | 每次一页,I/O 次数多、开销大;进程运行期间主要用它 |
| 预调页 | 根据局部性,一次调入若干相邻的页 | 相邻页在磁盘上连续存放时,一次 I/O 读入多页,省寻道和旋转时间 | 要靠预测,调入的页不一定用得上;主要用在进程首次调入时(由程序员或系统指出先调哪些页) |
跟我做一遍:进程启动后马上要用页 0~3,它们在磁盘上连续存放。请求调页:缺页 4 次,启动磁盘 4 次,每次都有寻道和旋转延迟。预调页:启动时一次把页 0~3 读入,只启动磁盘 1 次,之后访问这 4 页都不缺页。如果预调入的页 3 其实没用到,它就白白占了一个页框。
从哪调入:磁盘分为对换区(连续分配,读写快)和文件区(离散分配,读写慢)。① 对换区空间足够时:运行前把进程的相关页从文件区复制到对换区,之后全部从对换区调入;② 对换区不够时:不会被修改的页(代码)直接从文件区调入,换出时也不用写回;可能被修改的页换出到对换区,以后从对换区调回;③ UNIX 方式:没运行过的页从文件区调入,换出过的页从对换区调入;共享页可能已被别的进程调入内存,就不用再调。
页面置换算法
| 算法 | 淘汰谁 | 说明 |
|---|---|---|
| OPT 最佳 | 以后最长时间不会被访问的页 | 缺页最少,但要预知未来,实际不能实现,用来作比较标准 |
| FIFO 先进先出 | 最早调入的页 | 实现简单;会出现 Belady 异常:分配的页框多了缺页反而更多(只有 FIFO 会,2014-30) |
| LRU 最近最久未使用 | 最长时间没被访问过的页 | 效果接近 OPT;需要硬件支持(寄存器或栈),开销大。从当前位置往回数,最后一个“出现”的就是它 |
| CLOCK(也叫 NRU 最近未用) | 指针循环扫描:访问位 A=1 → 清 0 跳过;A=0 → 淘汰 | LRU 的近似,便宜 |
| 改进型 CLOCK | 同时看 (A, M):优先淘汰 (0,0) → 再找 (0,1)(扫描时把 A 清 0)→ 重复(详见 四轮规则) | 淘汰顺序 (0,0)、(0,1)、(1,0)、(1,1)(2016-26);没修改过的页不用写回,代价小 |
展开解析
OPT 的过程(“缺”表示缺页,“换 x”表示淘汰页 x;表中页框内容按页框位置列出):
| 访问 | 7 | 0 | 1 | 2 | 0 | 3 | 0 | 4 | 2 | 3 | 0 | 3 | 2 | 1 | 2 | 0 | 1 | 7 | 0 | 1 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 页框 1 | 7 | 7 | 7 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 7 | 7 | 7 |
| 页框 2 | 0 | 0 | 0 | 0 | 0 | 0 | 4 | 4 | 4 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
| 页框 3 | 1 | 1 | 1 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | ||
| 结果 | 缺 | 缺 | 缺 | 换7 | 换1 | 换0 | 换4 | 换3 | 换2 |
OPT 每次淘汰“往后看最晚才会用到(或再也不用)”的页:例如访问 2 时,内存里有 7、0、1,往后看 0 最先用到、1 其次、7 最晚 → 淘汰 7。结果:OPT 缺页 9 次(其中置换 6 次),LRU 12 次,FIFO 15 次(已用程序验算;可以把序列和 3 个页框填进下面的工具逐步对照)。LRU 淘汰“往前看最久没用过的”,和 OPT 恰好是时间上反过来的镜像,所以效果最接近 OPT。
做大题时:题目问“缺页次数”要把一开始装入空页框的那几次算进去;问“置换次数”则不算(2019-29)。
- A. 3
- B. 4
- C. 5
- D. 6
展开解析
C先分清“缺页”和“置换”:页框还没满时缺页只是装入空页框,不算置换;页框满了再缺页才要换出一页。
- 0、1、2、7:装入 4 个空页框,缺页 4 次,置换 0 次。
- 0 命中;5 缺页,最久未用的是 1 → 换出 1;3 缺页 → 换出 2;5、0 命中。
- 2 缺页 → 换出 7;7 缺页 → 换出 3;6 缺页 → 换出 5。
- 置换共 5 次(缺页共 9 次)。
- A 错3 次比 OPT(最佳置换,理论最少)的 4 次还少;LRU 不可能比 OPT 置换得更少。
- B 错4 次是 OPT 的置换次数(每次换出将来最久才用到的页);题目用的是 LRU,看的是过去。
- C 对LRU 置换 5 次。
- D 错6 次是 FIFO 的置换次数(总换出最早装入的页);LRU 每次换出最久没被访问的页,0、5 命中会刷新它们的“最近使用”时间。
问“置换次数”时要减去开头装入空页框的那几次缺页。同类:2025-26(3 个页框、0/1/2 已在内存,序列 0,1,2,0,5,1,4,3,0,2,3,2,0 → LRU 缺页 6 次);2015-27(4 页框,序列 2,0,2,9,3,4,2,8,2,4,8,4,5,下一页 7 → LRU 淘汰页 2)。
展开解析
- 页 1KB → 偏移 10 位。17CAH = 0001 01|11 1100 1010 → 页号 = 000101 = 5,偏移 3CAH。页 5 不在内存 → 缺页。
- FIFO:最早装入的是页 0(时刻 130),在页框 7 → 页 5 放入页框 7 → 物理地址 = 7 × 1024 + 3CAH = 000111|11 1100 1010 = 1FCAH。
- CLOCK:从页框 2 开始,四个页框访问位都是 1 → 依次清 0(2→4→7→9),转一圈回到页框 2,此时访问位 0 → 淘汰页 2,页 5 放入页框 2 → 物理地址 = 000010|11 1100 1010 = 0BCAH。
- A. 00A01H
- B. 20A01H
- C. 60A01H
- D. 80A01H
展开解析
C先判断缺页,再按改进型 CLOCK 一轮一轮扫描(从页 3 开始,依次页 3 → 页 4)。
- 02A01H:页 4KB → 偏移 A01H,页号 2。页 2 存在位 0 → 缺页;2 个页框都满了 → 要置换。
- 第一轮:找 (A=0, M=0),不改任何位。页 3 是 (1,0),页 4 是 (1,1),都不是。
- 第二轮:找 (0,1),扫过而不符合的,把 A 清 0。页 3 → (0,0),页 4 → (0,1)。这一轮也没找到。
- 第三轮:重新找 (0,0),页 3 符合 → 淘汰页 3,页 2 装入页框 60H。
- 物理地址 = 60H 拼 A01H = 60A01H。
- A 错00A01H 是把页框号当成 0,没有做置换。
- B 错20A01H 是把页号 2 当成页框号直接拼接(页 2 不在内存)。
- C 对60A01H。
- D 错淘汰了页 4:它被修改过,换出要写回,在两页都刚被访问过时不会先淘汰它。
改进型 CLOCK 最多四轮:找 00(不改位)→ 找 01(清 A)→ 再找 00 → 再找 01。
改进型 CLOCK:四轮扫描规则和一个完整例子
问题:简单 CLOCK 只看访问位。可是换出一个被修改过的页要先写回磁盘,比换出没改过的页多一次 I/O。能不能在“最近都没用过”的页里,优先淘汰没改过的?
大白话:清理冰箱时,先扔“好久没碰、也没拆封”的;其次扔“好久没碰、但拆过封”的(要多收拾一下);最近刚吃过的尽量留着。“最近用没用”比“好不好收拾”更重要。
规则(每页有访问位 A、修改位 M,指针循环扫描):
- 第一轮:从指针处扫一圈,找 (A=0, M=0),找到就淘汰;不修改任何位。
- 第二轮:再扫一圈,找 (0,1),找到就淘汰;扫过而不符合的页,把 A 清 0。
- 第三轮:重复第一轮(这时所有 A 都已是 0)。
- 第四轮:重复第二轮,这一轮一定能找到。所以最多扫四轮。
跟我做一遍(已用程序验算):进程有 4 个页框,指针指向页框 0,页框 0~3 依次是页 3 (1,1)、页 5 (0,1)、页 8 (1,0)、页 9 (1,1)。访问页 7 缺页,淘汰谁?
- 第一轮找 (0,0):一圈下来没有,不改任何位。
- 第二轮找 (0,1):页框 0 是 (1,1),不符合,A 清 0 → (0,1);页框 1 是 (0,1),符合 → 淘汰页 5(它被修改过,先写回磁盘),页 7 装入页框 1,指针移到页框 2。
- 页框 2、3 没被扫到,位不变。
如果 4 页都是 (1,1):第一轮没有 (0,0);第二轮也没有 (0,1),但把 4 页的 A 全部清 0,都变成 (0,1);第三轮找 (0,0) 仍没有;第四轮在指针处就找到 (0,1) → 淘汰页框 0 的页。这就是“最多四轮”的情形。
真题怎么考:淘汰次序(2016-26);给页表算置换后的物理地址(2021-28,走到第三轮);页表项要有访问位和修改位(2018-45)。
① 第一轮不清 A,第二轮才清;把顺序记反会选错页。② 第二轮清掉 A 的页,本轮不会马上回头再检查,要等下一轮。③ 淘汰次序是 (0,0) → (0,1) → (1,0) → (1,1),把 (A, M) 当成二进制数从小到大即可。
LFU、NRU 与页面缓冲算法 PBA
问题:LRU 效果好但要记录每次访问的时间,硬件开销大;CLOCK 便宜但粗糙。教材还介绍了几种算法,它们分别解决什么问题?
LFU 最少使用
淘汰访问次数最少的页:每页一个计数器,访问一次加 1(实际常定期右移,让旧的次数逐渐“衰减”)。和 8.3 Cache 的 LFU 是同一个思路。缺点:只看次数、不看最近,过去用得多的页会赖着不走。NRU 最近未用
就是简单 CLOCK:每页一个访问位,指针循环扫描,A = 1 就清 0 跳过,A = 0 就淘汰。它只把页分成“最近用过 / 没用过”两类,所以叫“最近未用”。改进型 CLOCK 再加上修改位,分成四类。展开解析
LRU:4 次。1、2、3 装入;访问 4 时淘汰最久没用的页 1;之后 2、3、4 轮流访问,全部命中。
LFU:10 次。页 1 一开始被访问了 3 次,计数为 3;2、3、4 每次进来计数都只有 1,总是被彼此挤掉:访问 4 淘汰 2,访问 2 淘汰 3,访问 3 淘汰 4……从第 6 次访问起每次都缺页,而页 1 再也不用了,却一直占着一个页框。
结论:LFU 不是 LRU 的近似;程序的“热点”变了以后,它反应很慢。
页面缓冲算法 PBA:问题是“刚被换出去的页,很可能马上又要用”,每次都重新读盘很亏;被修改的页一页一页写回磁盘也很亏。做法(可变分配、局部置换,置换算法用简单的 FIFO):被淘汰的页不马上清除,而是按是否修改过挂到两个链表上:
跟我做一遍:页 6(没修改过)被 FIFO 淘汰,挂到空闲页链表尾。过一会儿进程又访问页 6——这时它的页框还没轮到链首被分配出去,内容还在 → 从链表摘下来放回驻留集,不用读盘。如果这期间它已被分配给别的页、内容被覆盖了,就只能重新读盘。
真题怎么考:页缓冲队列的长度不影响缺页率(再访问照样算缺页,只是处理得快,2022-30);页面缓冲能降低平均访存时间(2026-29)。
页框分配、抖动与工作集
- 驻留集:给一个进程分配的页框集合。固定分配(页框数不变)/ 可变分配;局部置换(只换自己的页)/ 全局置换(可以抢别的进程的页框)。组合:固定+局部、可变+全局、可变+局部;“固定分配 + 全局置换”不存在(全局置换会改变页框数,2015-30)。分配多少:平均分配、按比例分配(按进程大小)、考虑优先级的分配;每个进程至少要有能执行任何一条指令所需的页框数,它由指令的寻址方式决定(2025-27)。
- 抖动(颠簸):页框太少,刚换出的页马上又要用,系统大部分时间都在换页。原因是进程太多、每个分得太少;办法是撤销/挂起部分进程(2011-29 选 A;增大对换区、提高优先级都没用)。
- 工作集:最近 Δ 次访问(窗口)里访问过的不同页面的集合,应保证驻留集 ≥ 工作集。2016-29:窗口 6,最近 6 次访问是 6,0,3,2,3,2 → 工作集 {6, 0, 3, 2}。计算方法和例题见下。
- 影响缺页率的因素:页面大小、分配的页框数、置换算法、程序的局部性(2022-30:“页缓冲队列的长度”不影响缺页率,它只影响置换的速度)。影响有效访存时间的:缺页率、磁盘读写时间、内存访问时间、缺页处理程序的 CPU 时间(2020-28 全选)。能加快地址转换的:增大 TLB、让页表常驻内存(2014-28)。
工作集怎么算:t 时刻、窗口 Δ 的工作集 W(t, Δ) = 从 t 往回数 Δ 次访问(含 t 时刻这次)里出现过的不同页面。窗口数的是访问次数,同一页出现几次都只算一个。
展开解析
(1)t = 11 往回数 4 次:第 8~11 次访问是 4, 5, 1, 2 → 工作集 {1, 2, 4, 5}。
| t | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 访问 | 1 | 2 | 1 | 3 | 2 | 4 | 4 | 4 | 5 | 1 | 2 | 1 |
| 工作集大小 | 1 | 2 | 2 | 3 | 3 | 4 | 3 | 2 | 2 | 3 | 4 | 3 |
(2)工作集最大为 4(t = 6 时 {1, 2, 3, 4},t = 11 时 {1, 2, 4, 5}),所以至少分配 4 个页框。
易错:把窗口当成“往回数 4 个不同的页”;或者忘了 t 时刻这次访问本身也在窗口里。
内存映射文件
问题:用 read / write 读写文件,每次都要发系统调用,数据还要在内核缓冲区和用户缓冲区之间复制一遍。能不能像访问数组一样直接访问文件?
大白话:把文件“贴”到进程的虚拟地址空间里一段地址上,之后读写这段地址就是读写文件。哪一页第一次被访问,就靠缺页机制把文件的那一页调进来,和请求调页一模一样,只是页的来源是这个文件。
定义:内存映射文件(memory-mapped file)用一个系统调用把磁盘文件映射到进程的虚拟地址空间,之后通过访存指令访问文件内容;调入由缺页机制完成,被修改的页由系统在适当时候(解除映射、同步或换出时)写回文件。多个进程映射同一个文件时,各自的页表项指向同一批物理页框,因此可以共享内存、实现进程间通信。
跟我做一遍(已用程序验算):文件 data.bin 长 10KB,页 4KB → 占 3 页;进程 A 把它映射到 4000 0000H 起。程序要读文件的第 5000 字节:5000 = 1388H → 虚拟地址 4000 1388H → 在映射区的页 1、偏移 388H。若页 1 不在内存 → 缺页 → OS 从文件读入第 4096~8191 字节放进一个页框,改页表 → 重新执行这条指令。整个过程程序里没有一个 read 调用。
真题怎么考:映射到进程的虚拟地址空间,不是物理地址空间;可以实现进程间通信(2025-30);共享时虚拟地址可以不同、物理地址相同(2026-30)。
写时复制(copy-on-write)
问题:fork 要给子进程一个和父进程一模一样的地址空间。如果马上把父进程的几百页全部复制一份,又慢又费内存——何况子进程常常紧接着就调用 exec 换成别的程序,复制的页根本用不上。
大白话:两个人先合看一本书,谁要在书上写字,才给他复印那一页。
定义:fork 时不复制页面,而是让父、子进程的页表项指向同一批物理页框,并把这些页都设为只读。以后任何一方要写某一页,就会触发保护异常;操作系统发现这是写时复制页(不是真的越权),就为写的一方复制这一页、把它的页表项改为指向新页框并设为可写,然后重新执行那条写指令。没人写过的页始终共享。
跟我做一遍:父进程有 100 页。fork 后子进程写了其中 3 页,父进程写了另外 1 页 → 一共只复制 4 页;不用写时复制,fork 时就要立刻复制 100 页(已用程序验算)。
真题怎么考:408 选择题尚未单独考过写时复制。它把 8.6 的“共享”、8.7 的“保护异常 → OS 处理 → 重新执行指令”串在一起,理解了这两点就不会错。映射文件时若用“私有映射”,也是用写时复制让修改只对本进程可见。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 3
- B. 4
- C. 5
- D. 8
展开解析
B前 4 次缺页装入 1、2、3、4,页框未满,不需要置换。之后:1 命中;5 缺页,换出 2;2 缺页,换出 3;6 缺页,换出 4;1 命中;3 缺页,换出 5。置换共 4 次(缺页共 8 次)。
变化点:置换次数 = 缺页次数 − 开始时用来填空页框的次数。D 是缺页次数。题目问“缺页”还是“置换”,答案差了 4。
- A. {1, 3, 4}
- B. {1, 3, 4, 5}
- C. {1, 2, 3, 4, 5}
- D. {3, 4}
展开解析
A窗口 5 是指最近的 5 次访问:4, 1, 4, 1, 3。其中不同的页是 {1, 3, 4}。
变化点:窗口大小数的是“访问次数”,不是“页数”。重复访问的页在集合中只算一个,所以工作集的大小可以比窗口小。C 是往回数了 5 个不同的页。
- A. 110ns
- B. 200ns
- C. 1100ns
- D. 10ms
展开解析
BEAT = (1 − p) × 100ns + p × 10ms = 0.99999 × 100 + 10−5 × 107 ≈ 100 + 100 = 200ns。
变化点:2020-28 只问哪些因素影响访存时间。这里真的算一次:缺页率只有十万分之一,平均访存时间就变成了原来的 2 倍,所以缺页率必须非常低。
Ⅰ. 减少并发运行的进程数 Ⅱ. 在内存允许时为进程增加页框 Ⅲ. 增大磁盘交换区的容量
- A. 仅Ⅰ
- B. 仅Ⅰ、Ⅱ
- C. 仅Ⅱ、Ⅲ
- D. Ⅰ、Ⅱ、Ⅲ
展开解析
B抖动是因为分给进程的页框太少,装不下它的工作集。减少进程数、给进程更多页框都能让每个进程的工作集装进内存。交换区大小与缺页多少无关。
变化点:2011-29 的正确项只有“撤销部分进程”,这里又加了“增加页框”。两者本质相同:让每个进程分到的页框不少于它的工作集。
- A. 页框 0
- B. 页框 1
- C. 页框 3
- D. 页框 2
展开解析
D第一轮找 (0,0),一个都没有,不改位。第二轮找 (0,1):页框 0 (1,0) 不符合,A 清 0 变成 (0,0);页框 1 (1,1) 变成 (0,1);页框 2 是 (0,1),符合 → 淘汰页框 2 中的页(已用程序验算)。
变化点:A 是陷阱:页框 0 在第二轮被清成了 (0,0),但第二轮只找 (0,1),不会回头选它。2021-28 要走到第三轮,这里第二轮就找到了。考点:改进型 CLOCK 的四轮规则。
- A. 被淘汰的页面立即写回磁盘并清除
- B. 页面缓冲算法能降低进程的缺页率
- C. 未被修改的淘汰页挂到空闲页链表上,被再次访问时可以不经磁盘 I/O 直接取回
- D. 修改页链表中的页面每换出一页就单独写一次磁盘
- A. 5
- B. 2
- C. 3
- D. 50
展开解析
Afork 时不复制任何页。之后谁写哪一页(且这一页仍被共享),就为写的一方复制那一页:子进程 2 页 + 父进程 3 页 = 5 页(已用程序验算)。B、C 只算了一方。D 是不用写时复制、fork 时全部复制的情形。
考点:写时复制。
8.8融合一次访存的全过程(可以一步步点)
现在把 8.3 的 Cache 和 8.5~8.7 的分页、TLB、缺页拼起来。设:虚拟地址 32 位、页 4KB;物理地址 24 位;TLB 全相联;Cache 2 路组相联、64 组、块 64B(块内 6 位 + 组号 6 位 = 12 位,正好等于页内偏移,于是 Cache 的 Tag 恰好就是页框号)。CPU 要读虚拟地址 0040 1A3CH,页 00401H 在页框 2F3H。
CPU 给出虚拟地址 0040 1A3CH。页 4KB → 低 12 位 A3CH 是页内偏移,高 20 位 00401H 是虚页号。
用虚页号查 TLB:全相联,所有表项的标记同时和 00401H 比较,找到有效位为 1 的一项 → 页框号 2F3H。TLB 命中,不用访问主存里的页表。
页框号 2F3H 拼上原样照抄的偏移 A3CH → 物理地址 2F3A3CH。再按 Cache 的格式切:块内 6 位 = 3CH,组号 6 位 = 40,Tag = 高 12 位 = 2F3H。
用组号 40 选中 Cache 的第 40 组,组内 2 行的 Tag 同时和 2F3H 比较,有一行相等且有效位 = 1 → Cache 命中。
按块内地址 3CH 从这一行取出数据送回 CPU。整个过程一次主存都没访问,硬件自动完成,程序和操作系统都感觉不到。
CPU 给出虚拟地址 0040 1A3CH,拆成虚页号 00401H 和偏移 A3CH。
查 TLB:没有标记为 00401H 的有效项 → TLB 缺失。
到主存里查页表:页表项地址 = 页表基址寄存器 + 00401H × 4(多访问一次主存)。存在位 P = 1,页框号 2F3H。顺便把这一项装入 TLB(下次就快了)。TLB 缺失通常由硬件处理,不一定需要操作系统。
拼出物理地址 2F3A3CH,切成 Tag 2F3H | 组 40 | 块内 3CH。
查 Cache 第 40 组:两行的 Tag 都不是 2F3H(或有效位为 0)→ Cache 缺失。
按物理地址访问主存,把整个块(64B)读出来装进第 40 组的一行:组里有空行就用空行,满了按 LRU 替换;被替换的行若是脏的(回写法),先写回主存。
把需要的数据送回 CPU。这一次共访问主存 2 次:查页表 1 次 + 读块 1 次。
CPU 给出虚拟地址 0040 1A3CH,拆成虚页号和偏移。
查 TLB 未命中(页不在内存,TLB 里当然不可能有它:TLB 命中而缺页是不可能的,2010-17)。
查页表,存在位 P = 0 → MMU 产生缺页异常(故障)。CPU 保存 PC、PSW,切换到内核态,转到缺页处理程序(第 5 篇的“接力”)。
操作系统:检查地址合法;找一个空闲页框,没有就用置换算法选一页淘汰,若它被修改过,先写回磁盘。
按页表项里的外存地址启动磁盘 I/O,把页读入页框。磁盘很慢,这时进程被阻塞,CPU 调度别的进程运行(第 6 篇)。
磁盘读完发出 I/O 中断。中断处理程序修改页表(P = 1,填页框号),把进程改为就绪。
进程再次被调度,从缺页异常返回,重新执行刚才那条访存指令:这次查页表(并装入 TLB)得到页框号,拼出物理地址。
接着查 Cache(刚调入的页一般不在 Cache 里 → 缺失),从主存读块装入 Cache,把数据送回 CPU。
- A. TLB 未命中,Cache 未命中,Page 未命中
- B. TLB 未命中,Cache 命中,Page 命中
- C. TLB 命中,Cache 未命中,Page 命中
- D. TLB 命中,Cache 命中,Page 未命中
展开解析
D三者是“层层包含”的:TLB 里的页表项一定对应已在内存的页;Cache 里的块一定是内存里某页的副本。
- A 错可能发生:第一次访问一个还在外存的页——TLB 没它的表项、页不在内存(缺页)、Cache 里自然也没有它的块,三个都未命中。
- B 错可能发生:TLB 里刚好没有这页的表项,查内存页表发现页在内存(Page 命中),得到物理地址后数据块正好在 Cache 里。
- C 错可能发生:TLB 命中说明页在内存,Page 命中;但该页里要访问的那一块还没调进 Cache,Cache 未命中。
- D 对不可能:页不在内存(Page 未命中)时,操作系统不会让它的页表项留在 TLB 里,它的块也不可能在 Cache 里,TLB、Cache 都不会命中。
Page 未命中 ⇒ TLB 和 Cache 都未命中;TLB 命中 ⇒ Page 一定命中。8 种组合里只有 3 种可能、5 种不可能,见 8.8 的表。
展开解析
- (1)16MB = 224 → 虚拟地址 24 位,偏移 12 位,虚页号 高 12 位(23~12)。1MB → 物理地址 20 位,页框号 高 8 位(19~12)。
- (2)块 32B → 块内 5 位(4~0);8 行 → 行号 3 位(7~5);Tag = 20 − 8 = 12 位(19~8)。
- (3)001C60H:虚页号 001H,页表 1 号项有效,页框号 04H → 物理地址 = 04C60H。二进制 0000 0100 1100 0110 0000:Tag = 0000 0100 1100 = 04CH,行号 = 011 = 3。Cache 第 3 行有效但 Tag = 105H ≠ 04CH → 不命中。
- (4)TLB 8 项、4 路 → 2 组 → 虚页号最低 1 位是组号,其余 11 位是标记。024BACH 的虚页号 024H = 0000 0010 0100:组号 = 0,标记 = 0000 0010 010 = 012H。组 0 里有有效且标记为 012H 的项(页框 1FH)→ TLB 命中 → 该页在主存。
展开解析
- (1)20 + 3 + 5 = 28 位(也可由实页号 16 位 + 页内 12 位得到)。
- (2)每项都有比较器、同时比较整个虚页号 → 全相联;TLB 要快,用 SRAM。
- (3)每组 2 行 → 2 路组相联;附加位:LRU 位(1 位)和修改位(脏位,1 位)。8 组 × 2 行 × (20 + 1 + 1 + 1 + 32×8) = 16 × 279 = 4464 位 = 558B。有效位表示该行内容是否有效。
- (4)0008C040H:虚页号 0008CH,TLB 有有效项 → 实页号 0040H → 物理地址 = 004 0040H(28 位 = 7 个十六进制位)。Cache:Tag = 高 20 位 = 00400H,组号 = 040H 的第 7~5 位 = 010 = 组 2,块内 0。组 2 有 Tag 相同的一行但有效位为 0,另一行 Tag 不同 → 不命中。
- 0007C260H:Cache 组号在页内 12 位里,直接用 260H = 0010 0110 0000,第 7~5 位 = 011 → 组 3。
TLB、页表、Cache 三者命中的 8 种组合:哪些可能、要访存几次
问题:一次访存要过三道关——TLB 命中没有?页在不在主存?Cache 命中没有?每道关“是 / 否”两种结果,一共 2 × 2 × 2 = 8 种组合。2010-17 只问了其中一种“不可能”,换个问法(“可能的是”“访问主存几次”“哪些组合需要操作系统介入”)照样能出题。把 8 种一次列全,就不怕换问法。
判断的依据只有一条“包含关系”:TLB 里的有效页表项、Cache 里的有效块,都是已经在主存里的页的副本(操作系统换出一页时,会把它在 TLB 里的表项作废,把它在 Cache 里的块写回并作废,正是为了维持这一点)。所以:
- 页不在主存(缺页)⇒ TLB 一定不命中,Cache 也一定不命中;
- 反过来,TLB 命中 ⇒ 页一定在主存;Cache 命中(物理地址 Cache)⇒ 该块所在的页一定在主存;
- 而 TLB 命中与 Cache 命中互不决定:TLB 只缓存“页号 → 页框号”,Cache 缓存的是数据块,一个命中另一个可能不命中。
8 种组合一张表(单级页表、物理地址 Cache、先查 TLB 再查 Cache;时间按 TLB 2 ns、Cache 5 ns、主存 50 ns 串行计算,由 verify/08/p13/a_combo.py 枚举):
| TLB | 页在主存? | Cache | 可能吗 | 访问主存次数 | 时间 | 说明 |
|---|---|---|---|---|---|---|
| 命中 | 在 | 命中 | ✓ | 0 | 7 ns | 最快的情况 |
| 命中 | 在 | 缺失 | ✓ | 1 | 57 ns | 从主存调一块进 Cache |
| 缺失 | 在 | 命中 | ✓ | 1 | 57 ns | 访存查页表一次,数据在 Cache(2010-17 B) |
| 缺失 | 在 | 缺失 | ✓ | 2 | 107 ns | 查页表一次 + 调块一次 |
| 缺失 | 缺页 | 缺失 | ✓ | — | ms 级 | 缺页异常 → 操作系统从磁盘调页 → 重新执行这条指令(2010-17 A) |
| 命中 | 缺页 | 命中 | ✗ | — | — | TLB 不会有不在主存的页(2010-17 D) |
| 命中 | 缺页 | 缺失 | ✗ | — | — | 同上 |
| 缺失 | 缺页 | 命中 | ✗ | — | — | Cache 不会有不在主存的块 |
跟我做一遍:TLB 命中率 95%、Cache 命中率 90%(两者独立),不考虑缺页,按上表的时间求平均访存时间。
- 每次都要查 TLB、查 Cache:2 + 5 = 7 ns。
- TLB 缺失(5%)时多访问一次主存查页表:0.05 × 50 = 2.5 ns。
- Cache 缺失(10%)时多访问一次主存调块:0.10 × 50 = 5 ns。
- 平均 = 7 + 2.5 + 5 = 14.5 ns。用上表四种“可能”的组合按概率加权(0.95 × 0.9 × 7 + 0.95 × 0.1 × 57 + 0.05 × 0.9 × 57 + 0.05 × 0.1 × 107)结果相同,程序已核对。
- “TLB 未命中就一定缺页”——错。TLB 只放一小部分页表项,没放进去的页照样可能在主存(上表第 3、4 行)。
- “TLB 命中就不用访问主存”——不一定。TLB 命中只省掉查页表的那次访存,数据若不在 Cache 里还要访问主存(第 2 行)。
- 多级页表时“查页表”要访问主存的次数等于级数;题目说页表项可以被 Cache 缓存时另算——看清题目给的条件。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. TLB 命中,但页不在内存中
- B. TLB 未命中,页在内存中,Cache 命中
- C. 页不在内存中,但 Cache 命中
- D. TLB 命中,Cache 命中,但页不在内存中
展开解析
BTLB 未命中只是没有缓存这个页表项,查页表后照样能得到页框号,数据也可能正在 Cache 中。A、C、D 都不可能:TLB 和 Cache 中的内容都是主存中已有页的副本,页不在内存,它们里面也不会有。
变化点:2010-17 问“不可能”的组合(TLB 命中而页缺失)。这里问“可能”的,其余三项都含“页不在内存却命中”这类矛盾。
8.9虚拟存储器的性能:影响因素与改进方式
考纲在“虚拟内存管理”下单列了一条:虚拟存储器性能的影响因素及改进方式。前面几节分别讲了页面大小、置换算法、页框分配、工作集,这一节把它们放到同一个问题下面:同一个程序,为什么换一种写法、多给几个页框、换一个置换算法,运行时间会差几十倍甚至几百倍?该从哪几个方向改进?
一切从一个公式开始:缺页率决定有效访存时间
设主存访问时间为 tm,缺页时的处理时间(缺页中断 + 必要时写回旧页 + 从磁盘读入新页 + 重新执行指令)为 tf,缺页率为 p(不考虑 TLB 和 Cache):
跟我做一遍:tm = 100 ns,tf = 8 ms = 8 000 000 ns。
| 缺页率 p | 0 | 10−6 | 10−5 | 10−4 | 10−3 |
|---|---|---|---|---|---|
| EAT | 100 ns | 108 ns | 180 ns | 900 ns | 8099.9 ns |
| 是不缺页时的 | 1 倍 | 1.08 倍 | 1.8 倍 | 9 倍 | 81 倍 |
- p = 10−3:EAT = 0.999 × 100 + 0.001 × 8 000 000 = 99.9 + 8000 = 8099.9 ns——一千次访存里缺一次页,速度就只剩 1/81。
- 反过来问:要让 EAT 不超过 110 ns(只慢 10%),p 最大多少?100 + p × (8 000 000 − 100) ≤ 110 → p ≤ 10 ÷ 7 999 900 ≈ 1.25 × 10−6,也就是大约每 80 万次访存才允许缺一次页。
- 若被换出的页是“脏”的,要先写回磁盘再读入,tf 约翻倍:同样 p = 10−5,EAT 从 180 ns 变成 260 ns。这就是置换时优先淘汰未修改页(改进型 CLOCK)的理由。
数字由 verify/08/p13/b_perf.py 计算。结论:虚存性能的关键是把缺页率压到极低,其次是降低每次缺页的代价。下面的每个因素,都能归到这两条之一。2020-28 问“影响有效访存时间的因素”,缺页率、磁盘读写时间、内存访问时间、执行缺页处理程序的 CPU 时间四项都选——它们分别是公式里的 p、tf 的两部分和 tm。
因素一:页面大小
| 页面小 | 页面大 | |
|---|---|---|
| 页内碎片 | 小(平均每个进程浪费半页) | 大 |
| 页表 | 页数多 → 页表大、可能要更多级 | 页表小 |
| 缺页次数 | 每次只调入一小块,顺序访问大数组时缺页多 | 一次调入多,缺页少;但可能调入用不到的内容 |
| 磁盘 I/O | 每次传得少,寻道和旋转的开销占比高 | 一次传得多,寻道和旋转的开销被摊薄 |
| TLB 覆盖范围 | 64 项 × 4KB = 256KB | 64 项 × 2MB = 128MB(“大页”) |
所以页面大小是个折中:页表开销希望页大,碎片希望页小。拓展 设进程平均大小为 s、页表项 e 字节、页面 p 字节,则每个进程的额外开销 ≈ 页表 s·e/p + 平均碎片 p/2,求导得最优 p = √(2se)。取 s = 1MB、e = 8B:√(2 × 220 × 8) = √(224) = 4096B = 4KB,此时页表 2KB + 碎片 2KB = 4KB,是所有取值里最小的(页 2KB 或 8KB 时都是 5KB)。这正好解释了为什么 4KB 页这么常见。页面大小由硬件(地址结构)决定,必须是 2 的幂,操作系统只能在硬件支持的几种里选。
因素二:分给进程的页框数(驻留集)与抖动
给一个进程的页框越多,缺页越少——但不是均匀地少。下图的访问序列模拟一个主循环:页 1、2、3、4 反复用 5 遍,最后转到页 5、6(共 24 次访问)。
verify/08/p13/b_perf.py 模拟得到。图中“循环用到的 4 页”就是这一阶段的工作集(8.7)。页框数 ≥ 工作集,缺页只剩每页第一次的“冷启动”;页框数 < 工作集,进程大部分时间都在等页面——这就是抖动(颠簸)。放到整个系统看:
- 抖动的原因:同时运行的进程太多,分给每个进程的页框太少(少于工作集);置换算法选择不当也会加剧。
- 对策:① 降低多道程序度:挂起或撤销部分进程,把它们的页框让给其他进程(中级调度,6.2)——2011-29 选“撤销部分进程”;② 按工作集分配页框,工作集之和超过内存就不再调入新进程;③ 拓展 缺页率(PFF)策略:给每个进程的缺页率定上下限,高于上限就多给页框,低于下限就收回一些。无效的做法:增加磁盘对换区(缺页照样多,2011-29 Ⅱ)、提高某个进程的优先级(不改变页框数,2011-29 Ⅲ)。
- 页框数的下限由指令系统决定:一条指令执行时可能同时要用到的页都必须能装下(指令本身可能跨页,操作数也可能跨页、间接寻址还要多一页),否则这条指令永远执行不完——2025-27“确定进程运行所需的最少页框数要考虑指令系统支持的寻址方式”。
因素三:置换算法与页面缓冲
置换算法决定“淘汰谁”,直接影响缺页率:一般 OPT 最少、LRU 接近 OPT、CLOCK 近似 LRU、FIFO 最差,且 FIFO 可能出现 Belady 异常(8.7)。上图的序列说明:页框不够时,再好的近似算法也救不了(LRU 和 FIFO 一样差),这时该做的是增加页框或减少进程。
页面缓冲算法(PBA,8.7)和改进型 CLOCK 优先淘汰未修改页,降低的是每次缺页的代价(被淘汰的页暂留在空闲 / 修改链表里,短时间内再访问不用读盘;修改页成批写回)——缺页次数不变。2022-30 正是考这一点:页缓冲队列的长度不影响缺页率;2026-29 则问“能降低平均访存时间”,页面缓冲就要选上。
因素四:程序的局部性——程序员能做的事
同一份数据,访问的顺序不同,缺页次数可以差上千倍。设 int a[512][1024](int 4B),页面 4KB,C 语言按行存放,所以一行正好一页,整个数组 512 页;给进程分 100 个页框,用 LRU 置换:
verify/08/p13/b_perf.py 用 LRU 模拟得到。- 按行(外层 i、内层 j):第 i 行的 1024 个元素都在页 i 里,第一次访问缺页,后面 1023 次命中 → 512 页各缺 1 次,512 次。
- 按列(外层 j、内层 i):对每个 j,依次访问 512 个不同的页;LRU 只保留最近 100 页,而下一次再访问页 i 要隔 511 次其他页的访问 → 每次都缺页,512 × 1024 = 524288 次,是按行的 1024 倍。
- 条件一变:若给 600 个页框(≥ 512 页),按列访问也只缺 512 次——页框够装下所有页时,访问顺序就无所谓了。所以这类题要先看“页框够不够”。
同样的道理对 Cache、TLB 也成立:按列访问的 Cache 命中率也低(11.6 按行 vs 按列),11.15 卷一第 44 题把 Cache、TLB、缺页放在一道题里算。程序员能做的:数组按存放顺序访问、把一起用的数据放在一起、少用指针在内存里乱跳的结构。
改进方式总表:硬件、操作系统、程序员各能做什么
| 谁来做 | 措施 | 降低的是 |
|---|---|---|
| 硬件 | TLB(加快地址转换);更大的页 / 大页(扩大 TLB 覆盖范围);更快的外存(SSD);由硬件自动置访问位、修改位 | 地址转换时间、缺页代价 |
| 操作系统 | 选好置换算法(LRU 的近似 CLOCK、改进型 CLOCK);按工作集或缺页率分配页框;控制多道程序度防抖动;预调页(利用空间局部性,一次调入多页) | 缺页率 |
| 页面缓冲;修改页成批写回;对换区连续分配(读写比文件区快,8.7);写时复制(少做无用的复制,8.7) | 每次缺页的代价 | |
| 程序员 / 编译器 | 提高局部性:按存放顺序访问数组、循环交换、把常一起用的数据放在相邻位置 | 缺页率(也提高 Cache、TLB 命中率) |
不能降低缺页率的常见干扰项:多级页表(只省页表占的内存,反而多一次访存,2026-29 Ⅱ 不选);增大对换区;提高进程优先级;页面缓冲队列加长(只降代价)。
真题怎么考
| 真题 | 问法 | 抓手 |
|---|---|---|
| 2020-28 | 影响有效访存时间的因素 | 对着 EAT 公式找:p、tf(磁盘时间 + 缺页处理的 CPU 时间)、tm——四项全选 |
| 2022-30 | 不影响缺页率的是 | 页缓冲只降代价,不改缺页次数;置换算法、工作集大小、进程数量都影响 |
| 2026-29 | 能降低平均访存时间的是 | TLB、工作集、页缓冲都能;多级页表不能 |
| 2011-29 | 抖动时的有效措施 | 只有“撤销(挂起)部分进程”,把页框还给别人 |
| 2025-27 | 最少页框数由什么决定 | 指令系统的寻址方式(一条指令最多同时涉及几页) |
| 2014-30、2016-29 | Belady 异常、工作集 | 见 8.7 置换算法、工作集 |
秒杀思路:先判断选项改的是 p(缺页率)还是 tf(每次缺页的代价)还是 地址转换时间,再对照题目问的是“缺页率”还是“访存时间”。失效条件:题目若把 TLB、Cache 也放进来,平均访存时间还要加上地址转换和 Cache 的部分(8.8 组合表),不能只看缺页率。
- 主存访问 100 ns,缺页处理 8 ms,缺页率 10−4,有效访存时间是多少?是不缺页时的几倍?
答案
0.9999 × 100 + 0.0001 × 8 000 000 = 99.99 + 800 ≈ 900 ns,约 9 倍。 - 系统抖动时,管理员把磁盘对换区从 2GB 加到 8GB,有用吗?
答案
没用。对换区大小只决定“能换出多少”,抖动是因为每个进程分到的页框少于工作集,缺页次数不变。应当减少并发进程数(2011-29)。 - 上面的 int a[512][1024] 改成 int a[1024][512](每页 4KB 装 2 行),100 个页框、LRU,按列访问缺页多少次?
答案
数组仍是 512 页。按列访问时,对固定的 j 依次访问 1024 行,相邻两行在同一页,所以一列要连续用到 512 个不同的页、每页用 2 次,而 100 个页框留不住 → 每页的第一次都缺页:每列 512 次,共 512 × 512 = 262144 次(程序模拟核对)。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
Ⅰ. 在总页框数允许的范围内增加分配给它的页框数 Ⅱ. 采用页面缓冲算法 Ⅲ. 把按列遍历二维数组的代码改为按行遍历 Ⅳ. 增加 TLB 的表项数
- A. 仅Ⅰ、Ⅲ
- B. 仅Ⅰ、Ⅱ、Ⅲ
- C. 仅Ⅲ、Ⅳ
- D. Ⅰ、Ⅱ、Ⅲ、Ⅳ
展开解析
A先分清每项改的是“缺页次数”还是“缺页代价 / 地址转换时间”。
- Ⅰ 对页框数从少于工作集增加到不少于工作集,缺页会骤降(本节图中 3 → 4 个页框,22 → 6 次);LRU 是栈算法,页框增加不会使缺页增加。
- Ⅱ 错页面缓冲把被淘汰的页暂留在链表里,再访问时“缺页”照样发生,只是不用读盘——降的是代价,不是缺页率(2022-30)。
- Ⅲ 对按行遍历符合存放顺序,同一页的元素连续用完才换页(本节例子 524288 → 512 次)。
- Ⅳ 错TLB 只缓存页表项,命中省的是查页表的那次访存;页在不在内存与 TLB 无关。
干扰项来源:B 是把页面缓冲误当成减少缺页(2022-30 的陷阱);C 是把“TLB 命中”误当成“页面命中”,同时漏了Ⅰ;D 把四项都当成“性能优化”一股脑选上。变化点:2022-30 问“不影响缺页率”,2026-29 问“降低平均访存时间”(那里页面缓冲、TLB 都要选),这里问“降低缺页率”,同一组措施答案不同。考点:8.9 改进方式总表。
- A. 2 × 10−5
- B. 1.2 × 10−5
- C. 2 × 10−6
- D. 2 × 10−4
展开解析
CEAT = (1 − p) × 100 + p × 107 ≤ 120。
- 单位统一:10 ms = 10 × 106 ns = 107 ns。
- 100 + p × (107 − 100) ≤ 120 → p ≤ 20 ÷ 9 999 900 ≈ 2 × 10−6。
- A 错2 × 10−5 = 20 ÷ 106:把 10 ms 当成了 1 ms(106 ns)。代回检验:p = 2 × 10−5 时 EAT ≈ 100 + 200 = 300 ns,超了。
- B 错1.2 × 10−5 = 120 ÷ 107:直接拿“允许的总时间 120 ns”去除,忘了不缺页时本来就要 100 ns,能留给缺页的只有 20 ns。
- C 对约 2 × 10−6,即平均 50 万次访存才允许缺一次页。
- D 错2 × 10−4 = 20 ÷ 105:把 10 ms 换算成了 105 ns(ms 与 ns 之间是 106,不是 104)。
变化点:2020-28 只问“哪些因素影响 EAT”,这里把公式真正算一遍。数字由 verify/08/p13/b_perf.py 断言核对。考点:8.9 缺页率与 EAT。
8.10本篇对照表与公式
| Cache—主存 | 主存—辅存(虚拟存储) | TLB—页表 | |
|---|---|---|---|
| 目的 | 提高速度 | 扩大容量 | 加快地址转换 |
| 交换单位 | 块(几十 B) | 页(KB 级) | 页表项 |
| 谁管理 | 全硬件 | 硬件(MMU)+ 操作系统 | 硬件(有的系统由 OS 填) |
| 映射 | 直接/全相联/组相联 | 页表(相当于全相联,任意页框;不用直接映射,2024-16) | 全相联/组相联 |
| 缺失代价 | 访主存,几十 ns | 访磁盘,ms 级,要 OS 介入 | 访主存查页表 |
| 写策略 | 直写或回写 | 只能回写 | — |
| 替换 | 硬件实现 | 软件(OS)实现 | 硬件 |
2020-15:TLB 和 Cache 的命中率都和局部性有关、缺失后都要访问主存、缺失处理都可以由硬件完成;“都由 DRAM 组成”错(都用 SRAM/相联存储器)。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 两者的地址映射都由操作系统完成
- B. Cache 缺失和缺页的处理都完全由硬件完成
- C. 主存—辅存层次的写策略一般采用回写(写回)法
- D. 两者交换信息的单位都是字
- 32 位地址、按字节编址,Cache 64KB、4 路组相联、块 32B、回写、LRU:Tag、组号、块内各几位?每行共多少位?
答案
行数 2048,组数 512 → 组号 9,块内 5,Tag 18;每行 256 + 18 + 1 + 1 + 2 = 278 位(LRU 按每行 log24 = 2 位)。 - 虚拟地址 32 位、页 16KB、页表项 4B,页表最多多大?如果用多级页表且每级页表恰好一页,需要几级?
答案
偏移 14 位,页号 18 位 → 218 × 4B = 1MB。一页放 16KB/4B = 4096 项 → 每级 12 位;18 位页号需要 ⌈18/12⌉ = 2 级。 - TLB 访问 5ns,主存 100ns,TLB 命中率 95%,不考虑缺页和 Cache,EAT 是多少?
答案
5 + 100 + 0.05 × 100 = 110 ns。 - 3 个页框,序列 1 2 3 4 1 2 5 1 2 3 4 5,FIFO 缺页几次?4 个页框呢?(用上面工具验证)
答案
9 次;10 次——Belady 异常。 - 为什么“页内偏移原样传递”这个性质能让 Cache 在地址翻译完成前就开始查组?
- DRAM 刷新周期 2ms、共 256 行,采用异步刷新,每隔多久刷新一行?
答案
2ms / 256 ≈ 7.8μs 刷新一行。 - 用 4K×4 位的 RAM 芯片组成 16K×8 位的存储器,需要几片?片内地址几位?片选要用几位高位地址?
答案
16K×8 / (4K×4) = 8 片(每 2 片一组位扩展,共 4 组);片内地址 12 位;4 组要 2 位高位地址经 2-4 译码产生片选。 - 动态分区中回收一个分区,它与前后两个空闲分区都相邻,空闲分区表的表项数怎么变?
答案
三块合成一块,表项数减 1。 - 分页和分段,哪个的地址空间是二维的?哪个有外部碎片?
答案
分段(以及段页式)的地址空间是二维的;分段有外部碎片,分页只有少量内部碎片。 - 一条指令执行过程中最多可能发生几次缺页?举例说明。
答案
不止一次。例如一条指令本身跨两页,它要访问的操作数又跨两页,最坏情况下这 4 页都不在内存,要发生多次缺页,每次处理完都重新执行这条指令。
8.✎本篇真题闯关(89 题)
本篇各小节挂的真题全部在这里,按小节排好。可以展开后一道道做,也可以用弹窗模式连续做。做题记录(对/错)只保存在你这台电脑的浏览器里,小节标题下的题号会随之变绿或变红。
融合三:总线与 I/O —— 从接口电路到驱动程序
CPU 怎么和键盘、磁盘、网卡打交道?计组讲硬件:部件之间靠总线连接,设备通过 I/O 接口挂到总线上,数据传送有程序查询、中断、DMA 三种方式,常考“CPU 花在 I/O 上的时间占百分之几”。操作系统讲软件:一次 scanf 要经过库函数、系统调用、设备无关软件、驱动程序、中断处理程序好几层,进程在这期间阻塞又被唤醒,数据先进内核缓冲区再复制给用户。两边合起来,就是一次 I/O 的完整故事。
9.1总线:部件之间的公共“马路”
总线是多个部件分时共享的一组传输线:同一时刻只能有一个部件往上发送,但可以有多个部件同时接收。按传输内容,系统总线分三组:
地址总线
传地址,单向(CPU → 主存/接口)。位数决定寻址空间。控制总线
传控制和状态信号:读/写、中断请求、总线请求、握手(应答)信号等(2011-20:握手信号不在数据线上)。| 按什么分 | 分类 |
|---|---|
| 所在位置 / 功能 | 片内总线(CPU 芯片内部,连寄存器、ALU);系统总线(连 CPU、主存、I/O 接口,又分数据、地址、控制三组);I/O 总线(连中低速 I/O 设备);通信总线(计算机与计算机、计算机与其他系统之间,也叫外部总线) |
| 数据传输格式 | 并行(多位同时传,线多、距离短);串行(逐位传,线少、适合远距离,现在高速总线多是串行:PCIe、USB、SATA) |
| 时序控制 | 同步、异步(9.2) |
| 总线结构 | 怎么连 | 特点 |
|---|---|---|
| 单总线 | CPU、主存、I/O 设备都挂在一条系统总线上 | 简单、便于扩充;但所有传送都抢这一条,带宽低,慢速设备拖累整体 |
| 双总线 | 一条主存总线(CPU、主存、通道)+ 一条 I/O 总线(通道和各 I/O 设备) | 把慢速的 I/O 从主存总线上分出去,由通道负责 I/O;需要专门的通道硬件 |
| 三总线 | 主存总线(CPU—主存)、I/O 总线(CPU—I/O 设备)、DMA 总线(高速 I/O 设备—主存) | 主存总线和 DMA 总线不能同时访问主存;I/O 总线只在 CPU 执行 I/O 指令时才用 |
常见总线标准:PCIe 与 USB 拓展
选择题考总线标准,只考“它是什么、连什么、并行还是串行”这几点,记住下表即可。
| 标准 | 并行 / 串行 | 连接什么 | 要点 |
|---|---|---|---|
| ISA、EISA、PCI | 并行 | 主机内部插卡(I/O 接口) | 老式系统总线 / I/O 总线;PCI 曾是主流 |
| AGP | 并行 | 显卡 | 显卡专用,已被 PCIe 取代 |
| PCIe(PCI-Express) | 串行 | 显卡、网卡、SSD 等插卡 | 点对点、全双工;×1、×4、×16 指并用几条串行通道(lane),每条仍是串行(2017-20) |
| USB | 串行 | 设备(键盘、U 盘、打印机)与主机的 USB 控制器 | 即插即用、热插拔、可经集线器级联多台设备、可以为设备供电;一次传 1 位(2012-20、2013-19) |
| SATA | 串行 | 硬盘、光驱 | 取代并行的 IDE(PATA) |
为什么高速总线反而都改成了串行:并行总线频率一高,几十根线上的信号到达时间对不齐(时钟偏移),互相还有串扰,频率上不去;串行只有一对差分线,可以跑到几 GHz 以上,还能用多条通道并起来提高带宽(2016-21)。
| 性能指标 | 含义(选择题最容易混的几个) |
|---|---|
| 总线时钟周期 | 总线时钟信号的周期 = 1 / 总线时钟频率 |
| 总线传输周期(总线周期) | 完成一次总线操作所需的时间,通常包含若干个总线时钟周期(申请、寻址、传输、结束) |
| 总线工作频率 | 每秒传送数据的次数 = 1 / 总线传输周期;时钟周期里传多次时(DDR)可以比时钟频率高 |
| 总线宽度 | 数据总线的根数(一次能并行传几位),通常 8、16、32、64 位 |
| 总线带宽 | 单位时间内最多能传多少数据 = 总线宽度 × 总线工作频率(下面的公式) |
| 总线复用 | 同一组线分时传不同信号(如地址/数据复用),省引脚,不提高带宽 |
总线带宽(最大数据传输率)
- A. 10MB/s
- B. 20MB/s
- C. 40MB/s
- D. 80MB/s
展开解析
考点9.1 总线带宽
B总线带宽 = 每秒总线周期数 × 每个总线周期传送的字节数。
- 每秒总线周期数 = 时钟频率 ÷ 每个总线周期的时钟数 = 10M ÷ 2 = 5M 个。
- 带宽 = 5M × 4B = 20MB/s。
- A 错10MB/s ÷ 4B = 2.5M 次/秒,相当于每次传送要 4 个时钟周期,和题给“一个总线周期占 2 个时钟周期”对不上。
- B 对10M ÷ 2 × 4B = 20MB/s。
- C 错40MB/s = 10M × 4B,相当于每个时钟周期就传一次,漏看了“一个总线周期占 2 个时钟周期”。
- D 错80MB/s = 10M × 2 × 4B,相当于每个时钟周期上升沿、下降沿各传一次;题目没说双沿传送,反而是 2 个时钟周期才传一次。
带宽 = 频率 ÷ 每次传送的时钟数 × 每次传的字节数。同类:2014-19(66MHz、上下沿各传一次、32 位 → 528MB/s);2019-19(3 通道 × 8B × 1333M ≈ 32GB/s);2020-19(QPI:2.4G × 2 次 × 2B × 2 个方向 = 19.2GB/s);2024-20(420MHz × 2 × 8B = 6.72GB/s);2025-20(1333MT/s 已是“每秒传送次数”,× 8B ≈ 10.66GB/s)。
能提高同步总线传输率的:增加总线宽度、提高工作频率、支持突发传输;地址/数据线复用只是省线,不提速(2018-21 选 B)。串行总线不一定比并行慢:频率高了以后并行线之间干扰大,串行反而能跑得更快(2016-21 “并行总线传输比串行快”错)。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 1.6GB/s
- B. 3.2GB/s
- C. 6.4GB/s
- D. 12.8GB/s
展开解析
B带宽 = 每秒传送次数 × 每次传送的字节数 = 200M × 2 × 8B = 3.2GB/s。
变化点:2014-19 的地址/数据线复用、32 位、66MHz。复用只影响一次事务要花几拍,不改变“每秒最多传多少数据”的带宽。
- A. 1.6GB/s、1.16GB/s
- B. 1.16GB/s、1.6GB/s
- C. 1.6GB/s、1.6GB/s
- D. 0.8GB/s、1.16GB/s
展开解析
A总线带宽只看峰值:200M × 8B = 1.6GB/s。一次突发事务共 11 个时钟周期(55ns)传 64B,平均 64B ÷ 55ns ≈ 1.16GB/s。
变化点:2024-20 给了一大段突发事务的细节,但问的是总线带宽,细节全是干扰。这里把两个量都问了,区分“带宽”(峰值)和“一次事务的实际传输率”。
Ⅰ. 采用地址/数据线复用 Ⅱ. 采用分离事务通信方式 Ⅲ. 提高总线时钟频率 Ⅳ. 每个时钟周期传送两次数据
- A. 仅Ⅰ
- B. 仅Ⅰ、Ⅱ
- C. 仅Ⅲ、Ⅳ
- D. 仅Ⅱ
Ⅰ. 主存单元的地址 Ⅱ. I/O 端口的地址 Ⅲ. 中断类型号 Ⅳ. 握手(应答)信号
- A. 仅Ⅰ
- B. 仅Ⅰ、Ⅱ
- C. 仅Ⅰ、Ⅱ、Ⅲ
- D. Ⅰ、Ⅱ、Ⅲ、Ⅳ
9.2总线事务与定时
一次总线操作叫总线事务:请求 → 仲裁 → 寻址(送地址和命令)→ 传数据 → 释放。突发(猝发)传送:只送一次首地址,然后连续传送多个地址连续的数据(2014-20),大大节省了送地址的时间,读 Cache 块时特别有用。
总线仲裁:几个主设备同时要总线,给谁 拓展
问题:总线一次只能有一个主设备在用。CPU、DMA 控制器同时发请求,谁先用?
大白话:像一群人抢一个话筒。① 链式查询:话筒从主持人手里沿一排人往下递,第一个举手的人截住;② 计数器定时查询:主持人报号“0 号、1 号、2 号……”,报到谁、谁正好举手就给谁;③ 独立请求:每个人和主持人之间都有一根专线,主持人看着所有举手的人直接挑。
| 方式 | 优先级 | 控制线数(n 个设备) | 优点 | 缺点 |
|---|---|---|---|---|
| 链式查询 | 固定:离控制器越近越高 | 3 | 线最少,容易扩充设备 | 对电路故障敏感(一个设备的 BG 电路坏了,后面的设备都用不了总线);远端设备可能长期得不到总线 |
| 计数器定时查询 | 灵活:可固定,可轮流,也可由程序设置计数初值 | 2 + ⌈log₂n⌉ | 优先级可变,对故障不敏感 | 控制较复杂,要等计数 |
| 独立请求 | 由排队器决定,可编程设置 | 2n + 1 | 响应最快,优先级最灵活 | 控制线最多,控制器复杂 |
跟我做一遍(已用程序验算):总线上挂 8 个设备。链式查询 3 根;计数器定时查询要 ⌈log₂8⌉ = 3 根设备地址线,加 BS、BR 共 5 根;独立请求 8 对 BRi、BGi 加 BS 共 17 根。
分布式仲裁:没有中央控制器,每个主设备有自己的仲裁号和仲裁器。要用总线时,各自把仲裁号送上共享的仲裁总线,一边送一边和总线上的号比较,号小的自动退出,最后留下仲裁号最大的设备获得总线。
真题怎么考:近年 408 真题没有考过仲裁,重点放在总线事务与定时上。会数控制线、知道三种方式各自的优缺点即可。
总线定时:同步、异步(三种互锁)、半同步、分离式
问题:一次总线事务里,主设备什么时候该送地址、从设备什么时候该把数据放上去,双方怎么对上节拍?
大白话:同步像全班跟着一个节拍器做操,简单快速,但得照顾最慢的同学;异步像两个人传东西,“给你”(请求)、“收到”(应答),谁快谁慢都能对上,就是多了来回确认的时间。
| 定时方式 | 怎么协调 | 特点 |
|---|---|---|
| 同步通信(同步定时) | 统一的时钟信号(不能由各设备自己提供,2015-19) | 快、简单;设备速度差别大时只能迁就最慢的 |
| 异步通信(异步定时) | 没有公共时钟,靠握手信号(请求/应答);不互锁、半互锁、全互锁 | 全互锁最慢但最可靠,不互锁可靠性最差;一次握手完成的是一次数据交换,不一定只有 1 位(2021-19) |
| 半同步 | 同步时钟 + 等待信号 | 握手信号的采样由时钟控制 |
| 分离式 | 请求和应答分成两个事务,中间让出总线 | 提高总线利用率 |
跟我做一遍(已用程序验算):32 位异步总线采用全互锁,一个信号从发出到被对方检测到需 25ns。一次传送要依次经过“请求↑ → 应答↑ → 请求↓ → 应答↓”4 次信号变化,每次都要等前一次被对方看到,所以至少 4 × 25 = 100ns;最大数据传输率 = 4B ÷ 100ns = 40MB/s。
半同步
仍用同步时钟定时,但增加一根等待(WAIT)信号。从设备来不及时就让 WAIT 有效,主设备在时钟沿上采样到 WAIT,就插入等待周期,直到 WAIT 撤销再继续。既保留了同步的简单,又能照顾慢速设备(2015-19 D:握手信号的采样由同步时钟控制)。分离式
把一次读操作拆成两个子周期:① 主设备送出地址和命令后马上释放总线;② 从设备准备好数据后,自己作为主设备申请总线,把数据送回。从设备准备数据期间,总线可以给别人用,提高了总线利用率(2016-21 D)。要求各模块都能申请总线,控制复杂,多用于多处理器系统。真题怎么考:同步时钟由谁提供(2015-19);不互锁 / 全互锁哪个快、哪个可靠(2015-19 A、B);一次握手完成的是一次数据交换,而不是一位(2021-19);分离事务提高的是利用率而不是带宽(9.1 变式)。
① “全互锁最慢”和“全互锁最可靠”都对。② 异步定时的握手信号走控制线。③ 半同步仍然有时钟;分离式提高的是总线利用率,不提高峰值带宽。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 20ns
- B. 25ns
- C. 40ns
- D. 50ns
展开解析
B256 位 = 4 个 64 位数据。突发传送只送一次地址:1 个周期送地址 + 4 个周期送数据 = 5 个周期 × 5ns = 25ns。
变化点:2012-19 是 100MHz、32 位、128 位数据(5 个周期 × 10ns = 50ns)。不支持突发时,每个数据都要先送一次地址:4 × 2 = 8 个周期 = 40ns(C)。
- A. 28ns
- B. 84ns
- C. 112ns
- D. 128ns
展开解析
C每读一个 64 位数据:送地址 2ns + 主存准备 10ns + 传数据 2ns = 14ns。一块 64B 要读 8 次:8 × 14 = 112ns。
变化点:2023-20 是 1GHz、6ns、32B(4 × 8 = 32ns)。不支持突发时,每个数据都要重复“送地址 → 等待 → 传数据”三步。
- A. 同步总线的时钟信号可以由各设备各自提供
- B. 异步全互锁方式中,每个信号都要等对方回应后才撤销,可靠性最高
- C. 半同步方式不使用时钟信号
- D. 采用分离式通信时,从设备准备数据期间一直占用总线
- A. 3
- B. 6
- C. 32
- D. 33
展开解析
D独立请求方式中每个设备有一对请求线 BRi、允许线 BGi,16 个设备共 32 根,再加 1 根总线忙信号 BS,共 33 根(已用程序验算)。A 是链式查询;B 是计数器定时查询(2 + log216);C 漏了 BS。
考点:总线仲裁的控制线数。
- A. 25MB/s
- B. 50MB/s
- C. 100MB/s
- D. 12.5MB/s
展开解析
A全互锁一次传送依次经过请求↑、应答↑、请求↓、应答↓ 4 次信号变化,至少 4 × 20 = 80ns;每次传 16 位 = 2B → 2B ÷ 80ns = 25MB/s。B 只算了 2 次信号变化;C 只算了 1 次;D 把每次传送的数据当成了 1B。
考点:异步定时的三种握手方式(已用程序验算同类例题)。
9.3I/O 接口与端口
外设五花八门(速度、数据格式、电平都不一样),不能直接挂到总线上,中间要一块“翻译 + 缓冲”的电路:I/O 接口(设备控制器,如网卡、磁盘控制器、打印机适配器;磁盘驱动器是磁盘本身的机械部分,不是接口,2021-20)。
| 接口的功能 | 做什么 |
|---|---|
| 选址 | 识别总线上的地址是不是给自己的(地址译码),选中哪个端口 |
| 传送命令 | CPU 写到控制寄存器的命令(如“启动”)由接口转给设备 |
| 传送数据 | 用数据缓冲寄存器暂存数据,缓和 CPU 与设备的速度差 |
| 反映设备状态 | 设备的忙/闲/就绪/出错写在状态寄存器里供 CPU 查询;中断方式时由接口发出中断请求 |
| 格式、电平转换 | 串行 ↔ 并行、设备电平 ↔ 总线电平 |
接口与端口:接口是整块电路(设备控制器),端口是接口里 CPU 能直接读写的寄存器(数据端口、状态端口、控制端口)。操作系统里说的“设备控制器”就是计组里的“I/O 接口”:它由 ① 与 CPU 的接口(数据线、地址线、控制线)、② I/O 逻辑(译码 CPU 发来的命令)、③ 与设备的接口 三部分组成。接口还可按数据传送方式分为并行接口和串行接口,按功能分为可编程/不可编程、中断接口/DMA 接口。
| 设备怎么分类 | 分类(操作系统选择题) |
|---|---|
| 信息交换单位 | 块设备(磁盘:以块为单位、可寻址、传输快,常用 DMA);字符设备(键盘、打印机:以字符为单位、不可寻址、传输慢,常用中断) |
| 使用特性 | 人机交互类外设(键盘、显示器)、存储设备(磁盘)、网络通信设备(网卡) |
| 传输速率 | 低速(键盘、鼠标)、中速(打印机)、高速(磁盘、网卡) |
| 共享属性 | 独占设备、共享设备、虚拟设备(9.8) |
统一编址(存储器映射 I/O)
端口占用一部分主存地址,用普通的访存指令(mov)访问端口。独立编址(I/O 映射 I/O)
端口有自己的地址空间(端口地址可能和主存地址相同),用专门的 I/O 指令(in/out)访问;I/O 指令是在通用寄存器和 I/O 端口之间传数据(2017-21)。2014-21:“采用统一编址方式时,CPU 不能用访存指令访问 I/O 端口”错。I/O 指令是特权指令,只能在内核态执行(第 5 篇),所以用户程序必须通过系统调用请 OS 做 I/O。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 需要设置专门的 I/O 指令
- B. I/O 端口占用一部分主存地址空间,可以用访存指令访问
- C. I/O 端口地址和主存单元地址可能相同
- D. CPU 靠 I/O 指令的操作码区分访问的是主存还是 I/O 端口
展开解析
B统一编址把 I/O 端口看成主存的一部分,占用一段主存地址,用普通的访存指令访问,靠地址区分。A、C、D 都是独立编址的特点。
变化点:2014-21 的错误说法是“统一编址时 CPU 不能用访存指令访问 I/O 端口”。这里正面问统一编址的特点。
- A. 要打印的字符
- B. 设备的状态
- C. 控制命令
- D. I/O 端口的地址
9.4三种 I/O 方式:CPU 到底要花多少时间
展开解析
- 每次中断开销:(18 + 2) × 5 = 100 个时钟周期。
- 每秒中断次数:0.5MB/s ÷ 4B = 125000 次。
- 占比:100 × 125000 ÷ 500M = 12.5M / 500M = 2.5%。
- DMA:每秒 5MB/5000B = 1000 次,每次 500 周期 → 0.5M / 500M = 0.1%。数据量大了 10 倍,CPU 负担反而小了 25 倍——这就是 DMA 的意义。
展开解析
- (1)缓冲寄存器只有 4B,设备每 4B/(2MB/s) = 2μs 就装满一次,必须在下一个数据到来之前读走 → 最多每 2μs 查询一次。每秒查询 5×105 次,每次 10×4 = 40 周期 → 2×107/500M = 4%。
- (2)一次中断要 400 周期 = 400/500MHz = 0.8μs;B 每 4B/(40MB/s) = 0.1μs 就来一个新数据。中断还没处理完,缓冲寄存器已经被覆盖好几次 → 会丢数据,不能用中断(这就是“外设准备数据的时间应大于中断处理时间”,2022-21 选 C 为错误项)。
- (3)每秒 DMA 次数最多 40MB/1000B = 40000,× 500 = 2×107 周期 → 4%。
展开解析
- 1 起始位 + 7 数据位 + 1 校验位 + 1 停止位 = 10 位;每 0.5ms 一个字符 → 每秒 2000 个。
- 时钟周期 20ns,0.5ms = 25000 个周期。一个字符的“周期”:设备准备 25000 + 中断响应 10 + 服务程序执行到第 15 条(启动下一个字符)15×4 = 60 → 25070。1000 个字符 ≈ 25070000 个周期(启动后剩下的 5 条指令和设备工作重叠)。
- CPU 真正花的:1000 × (10 + 20×4) = 90000 个周期——总时长两千五百万,CPU 只忙了九万,其余时间都在跑别的程序。
- 中断响应阶段(隐指令):关中断、保存断点和程序状态、识别中断源(转服务程序入口)(第 5 篇)。
DMA 细节
- 预处理(CPU/驱动程序):设置 DMA 控制器的主存起始地址、传送字数、方向,启动设备。
- 数据传送(DMA 控制器,硬件):设备准备好一个数据(接口缓冲寄存器满)→ DMA 控制器请求总线 → 获得后把数据直接写入主存 → 地址加 1、计数减 1。常用周期挪用:每次只“偷”一个存储周期,CPU 在其余时间照常访存。DMA 请求优先级高于 CPU(不及时传会丢数据,2012-43、2022-44)。
- 后处理:计数到 0,DMA 控制器发出“DMA 传送结束”中断(外部中断),CPU 执行中断服务程序做收尾(校验、唤醒等待的进程)。
| DMA 与 CPU 怎么共用主存 | 做法 | 特点 |
|---|---|---|
| 停止 CPU 访存 | DMA 传送一整块数据期间,CPU 让出总线、不访存 | 控制简单;CPU 可能长时间不能访存,主存利用率不高 |
| 周期挪用(周期窃取) | 设备每准备好一个字,DMA 就“偷”一个存储周期写入主存;CPU 其余时间照常访存。若恰好冲突,DMA 优先 | 最常用,兼顾 CPU 和 I/O;每次挪用都要申请、释放总线 |
| DMA 与 CPU 交替访存 | 把一个存储周期分成两半,一半给 CPU、一半给 DMA | 不需要申请、释放总线;要求 CPU 周期比存储周期长很多,硬件复杂 |
跟我做一遍(已用程序验算):磁盘数据传输率 4MB/s,接口数据缓冲寄存器 32 位,主存存储周期 100ns,采用周期挪用。接口每装满 4B 就请求一次 → 每秒 4MB ÷ 4B = 106 次,即每 1μs 挪用一个 100ns 的存储周期 → 主存有 10% 的时间被 DMA 占用。CPU 只有恰好在这些周期也要访存时才需要等待,其余 90% 照常访存。
DMA 控制器里有什么:主存地址计数器(下一个数据写到主存哪里,每传一次加 1)、传送长度计数器(还剩几个字,每传一次减 1,减到 0 就结束)、数据缓冲寄存器、DMA 请求触发器(设备准备好数据时置 1)、控制/状态逻辑,以及发“DMA 传送结束”中断的中断机构。
| 程序中断方式 | DMA 方式 | |
|---|---|---|
| 数据怎么传 | 靠 CPU 执行中断服务程序,数据经过 CPU(寄存器) | 靠 DMA 控制器硬件直接在设备接口和主存之间传,不经过 CPU |
| 请求的是 | CPU 的时间(要 CPU 来处理) | 总线的使用权(只要占用总线) |
| 何时响应 | 一条指令执行结束后 | 一个存储周期(总线周期)结束后,可以在指令执行中间 |
| 对 CPU 现场 | 要中断 CPU 正在执行的程序,保存现场 | 不打断程序,只是挪用存储周期,不用保存现场 |
| 优先级 | 低于 DMA | 高于中断(不及时传会丢数据) |
| 多久干预一次 | 每传一个字(字节)中断一次 | 每传一整块才中断一次(结束时) |
| 适合 | 低速字符设备(键盘、打印机) | 高速块设备(磁盘、网卡) |
四种 I/O 控制方式总表(操作系统角度)
| 程序直接控制(查询) | 中断驱动 | DMA | 通道 | |
|---|---|---|---|---|
| 传送单位 | 字 | 字 | 块 | 一组数据块 |
| 数据流向 | 设备 → CPU → 内存 | 设备 → CPU → 内存 | 设备 → 内存 | 设备 → 内存 |
| CPU 干预 | 全程(忙等) | 每个字一次中断 | 每块开始、结束各一次 | 只在一组数据块的开始、结束时 |
| CPU 与设备并行 | 不能 | 能 | 能 | 能,且并行度最高 |
通道是专门负责 I/O 的处理机:CPU 只要发一条 I/O 指令告诉通道“去执行内存里的哪段通道程序、控制哪台设备”,通道就独立执行通道程序完成一组数据块的传送,结束时向 CPU 发中断。它和一般处理机的区别是:指令类型单一(只做 I/O),没有自己的内存,与 CPU 共享主存。一个通道可以控制多台设备。
2017-32 顺序:初始化 DMA 控制器并启动磁盘 → 从磁盘传输一块数据到内存缓冲区 → DMA 控制器发出中断请求 → 执行“DMA 结束”中断服务程序(选 B)。2019-22 四句全对(驱动设置参数、DMA 控制器请求总线、直接控制总线传送、结束后中断服务程序处理)。2020-22:周期挪用期间 CPU 可以访问主存(只是偶尔被挪走一个周期)。2013-22:中断请求的是 CPU 时间、在指令结束时响应;DMA 请求的是总线使用权、在总线事务(存储周期)结束时响应;“中断方式适用于所有外设”错。2023-22:DMA 不是“CPU 执行 DMA 传送程序”。2024-22:DMA 的数据通路在设备接口和主存之间。
展开解析
- 80M/4 = 20M 条/秒 → 20 MIPS;缺失 20M × 1.5 × 1% = 300000 次/秒;每次缺失从主存搬一块 16B → 至少 300k × 16B = 4.8MB/s。
- 缺页 = 300000 × 0.0005% = 1.5 次/秒;每次缺页 DMA 读 4KB,接口每满 4B 请求一次 → 1.5 × 4096/4 = 1536 次/秒(4KB 按 4096 算)。
- DMA 优先:磁盘一旦开始传送,数据按固定速率到来,不及时写走就会被下一个数据覆盖而丢失;CPU 晚一点访存只是慢一点。
- 4 × 4B / 50ns = 320MB/s。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 1.25%
- B. 2.5%
- C. 5%
- D. 10%
展开解析
C每次中断传 2B,每秒中断 100 × 103 ÷ 2 = 5 × 104 次。占比 = 5 × 104 × 500 ÷ (500 × 106) = 5%。
变化点:2019-21 是 32 位寄存器、1GHz(1.25%)。寄存器变窄,中断次数变多;主频降低,每秒的总周期数变少,占比上升。
- A. 20%
- B. 30%
- C. 40%
- D. 10%
展开解析
B每 500ns 中 CPU 要花 150ns 处理这个设备,占比 = 150 ÷ 500 = 30%。“允许的最长延迟”只说明响应不能太晚,不占用 CPU 时间。
变化点:2014-22 是 100 ÷ 400 = 25%。两道题都有“最长延迟”这个干扰条件:把它加进去会得到 C(40%)。
Ⅰ. 设置 DMA 控制器的传送参数 Ⅱ. 在设备接口与主存之间传送数据
Ⅲ. 执行“DMA 传送结束”中断服务程序 Ⅳ. 向总线仲裁器申请总线使用权
- A. 仅Ⅰ、Ⅲ
- B. 仅Ⅱ、Ⅳ
- C. 仅Ⅰ、Ⅱ、Ⅲ
- D. Ⅰ、Ⅱ、Ⅲ、Ⅳ
展开解析
A预处理(设置主存地址、字数等参数,由驱动程序完成)和后处理(中断服务程序)都要 CPU 执行程序。数据传送、申请总线都由 DMA 控制器用硬件完成。
变化点:2019-22 问哪些叙述正确(全对)。这里把同样四件事按“谁来做”分开:CPU 只管开头和结尾,中间的传送是 DMA 控制器的事。
- A. 停止 CPU 访存方式下,主存的利用率最高
- B. 周期挪用方式下,DMA 每次挪用存储周期都不需要申请总线
- C. 周期挪用方式下,DMA 传送整个数据块期间 CPU 不能访问主存
- D. 交替访存方式下,CPU 和 DMA 不需要申请、归还总线使用权
展开解析
D交替访存把每个存储周期分成两半,一半固定给 CPU、一半固定给 DMA,不需要申请和归还总线。A 错:停止 CPU 访存时,DMA 两次传送之间主存常常闲着,CPU 却不能用,利用率不高。B 错:周期挪用每次都要申请、释放总线。C 错:那是停止 CPU 访存方式(2020-22)。
9.5操作系统I/O 软件的层次
程序里用逻辑设备名(如“打印机”“/dev/sda”)申请设备,操作系统负责映射到具体的物理设备(2009-32)。好处:换了一台同类设备,只要换驱动程序,应用程序不用改(2020-30 选 D 为错误项)。
每一层到底干什么
| 层次 | 主要工作 | 打个比方(寄快递) |
|---|---|---|
| 用户层 I/O 软件 | 给程序员用的库函数(printf、scanf、fopen),把用户的请求整理好后发系统调用;SPOOLing 程序也在这一层 | 你填好快递单 |
| 设备独立性软件 | ① 逻辑设备名 → 物理设备(查逻辑设备表 LUT);② 缓冲区管理(9.7);③ 设备的分配与回收(9.8);④ 差错控制、权限检查;⑤ 向下提供统一的驱动程序接口 | 快递公司的调度中心:不管你寄到哪,流程都一样 |
| 设备驱动程序 | 把上层的抽象命令(“读第 100 块”)翻译成具体设备能懂的命令(柱面、磁头、扇区),写控制器的寄存器、启动设备、检查状态;每类设备一个驱动,通常由设备厂商提供 | 懂某条线路的司机 |
| 中断处理程序 | 设备完成后发中断:保存现场、判断中断原因、唤醒等待的进程、恢复现场 | 送达后给你发的短信 |
记一条原则:越往下越和具体硬件有关。和设备寄存器打交道的只有驱动程序和中断处理程序;设备独立性软件负责所有设备都要做的“通用”工作。
I/O 应用程序接口:三类设备、三种等法
| 设备接口 | 特点 | 典型操作 | 例子 |
|---|---|---|---|
| 字符设备接口 | 以字符(字节)为单位,顺序存取,不可寻址 | get、put(读一个字符、写一个字符) | 键盘、打印机、鼠标 |
| 块设备接口 | 以数据块为单位,可寻址、可随机访问 | read、write、seek;还可以把文件映射到内存(8.7 内存映射文件) | 磁盘、SSD |
| 网络设备接口 | 数据以报文、数据包的形式收发 | 套接字(socket):创建、连接、发送、接收 | 网卡 |
| 阻塞 I/O | 非阻塞 I/O | 异步 I/O | |
|---|---|---|---|
| 调用后 | 数据没好就阻塞,直到 I/O 完成才返回 | 立即返回,告诉进程当前有没有数据 | 立即返回,I/O 在后台进行 |
| 进程要不要自己反复问 | 不用(睡着等唤醒) | 要,反复调用(轮询) | 不用,完成后由系统通知(信号、回调) |
| 数据什么时候到用户缓冲区 | 返回时已经复制好 | 某次调用“有数据”时复制 | 通知到达时已经复制好 |
| 适合 | 一般程序,编程最简单 | 要同时照看多个设备 / 连接的程序 | 希望 I/O 和计算完全重叠的程序 |
非阻塞和异步都“立即返回”,区别在于后面:非阻塞要进程自己一次次回来问;异步发出请求后就不管了,等系统来通知。
设备驱动程序接口:操作系统为所有驱动程序规定了一套统一的接口(打开、关闭、读、写、控制等函数),新设备的厂商只要按这个接口写好驱动,操作系统的上层代码一行都不用改——这就是“设备独立性”能实现的原因。
I/O 核心子系统:调度、保护、缓冲、分配、假脱机
问题:考纲把“I/O 核心子系统”单独列为一条,它和上面的四个层次是什么关系?
大白话:I/O 核心子系统就是操作系统内核里“所有设备都要用到的那些公共服务”,主要在设备独立性软件这一层实现(假脱机程序在用户层)。驱动程序管“怎么让这台设备动起来”,核心子系统管“请求怎么排队、谁能用、数据先放哪儿”。
| 服务 | 做什么 | 本书哪里讲 |
|---|---|---|
| I/O 调度 | 给同一设备上的多个 I/O 请求重新排序,减少设备的平均等待时间、提高效率(如磁盘调度算法) | 下面的例子;第 10 篇磁盘调度 |
| 设备保护 | 检查进程对设备有没有访问权限;用户程序不能直接执行 I/O 指令(特权指令),只能通过系统调用 | 9.3、第 5 篇 |
| 缓冲区管理 | 单缓冲、双缓冲、循环缓冲、缓冲池 | 9.7 |
| 设备分配与回收 | 按设备类型、状态、权限分配设备、控制器、通道;逻辑设备名到物理设备的映射 | 9.8 |
| 假脱机(SPOOLing) | 把独占设备改造成共享的虚拟设备 | 9.8 |
| 出错处理 | 设备、传输出错时重试、报告错误 | — |
跟我做一遍:I/O 调度(已用程序验算)。磁头在 50 号磁道,请求队列按到达顺序是 90、20、80、30。按到达顺序服务,磁头共移动 40 + 70 + 60 + 50 = 220 个磁道;I/O 调度把它们重排成 30、20、80、90,只要移动 20 + 10 + 60 + 10 = 100 个磁道。请求还是那四个,只是换了顺序,平均等待时间就少了一半多。
真题怎么考:设备分配要考虑的因素(2023-32);逻辑设备名映射属于设备独立性软件(2020-30、2009-32);SPOOLing(2016-31);磁盘调度在第 10 篇。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 把逻辑块号转换成柱面、磁头、扇区号——设备独立性软件
- B. 检查用户是否有权访问该文件——设备驱动程序
- C. 向磁盘控制器写命令,启动磁盘——设备驱动程序
- D. 唤醒等待该 I/O 的进程——用户层 I/O 软件
- A. 驱动程序在用户态下运行
- B. 驱动程序与设备的硬件特性密切相关
- C. 所有设备可以共用同一个驱动程序
- D. 执行驱动程序的进程不会进入阻塞态
- A. 异步 I/O 发出请求后立即返回,I/O 完成后由系统通知进程
- B. 非阻塞 I/O 发出请求后,进程进入阻塞态直到数据就绪
- C. 阻塞 I/O 发出请求后立即返回,进程需要反复查询
- D. 异步 I/O 要求进程不断轮询 I/O 是否完成
展开解析
A异步 I/O 只管发出请求,整个 I/O 在后台完成后,系统用信号或回调通知进程。B 说的是阻塞 I/O;C 说的是非阻塞 I/O;D 也是非阻塞 I/O 的做法。
考点:三种 I/O 等待方式。
- A. I/O 调度
- B. 缓冲区管理
- C. 设备的分配与回收
- D. 计算磁盘的柱面号、磁头号和扇区号
9.6融合一次 scanf 的全过程:进程状态 + CPU 模式 + 数据位置
这是把第 5、6、9 篇串起来的一张图,2017-45、2023-46、2024-31、2025-46 都在考它。以“进程 P 执行 scanf 从键盘读一个字符,采用中断方式”为例:
展开解析
完整顺序:② → ⑥ → ④ → ③ → ① → ⑤(P 先发现没数据而阻塞,然后用户按键、中断、读字符、唤醒 P,最后 P 被调度并从系统调用返回)。
- ① 前是 ③,后是 ⑤;⑥ 后是 ④。
- ② 之后 P 阻塞了,CPU 一定切换到别的进程;① 之后 P 进入就绪队列,调度程序才可能选它。
- ③(和具体硬件打交道、读控制器的数据寄存器)是键盘驱动程序的代码。
- P 处于阻塞态;CPU 处于内核态。
展开解析
P:运行 → 阻塞(等待输入)→ 输入完成时被中断处理程序唤醒 → 就绪 → 被调度 → 运行。CPU 会进入内核态(系统调用、中断处理都在内核态)。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 把字符从内核缓冲区复制到用户缓冲区
- B. 把键盘控制器数据寄存器中的字符读入内核缓冲区
- C. 把输入的字符串转换成整数
- D. 发出读键盘的系统调用
① 命令解释程序分析并执行命令 ② 键盘中断处理程序 ③ 系统调用服务程序把输入交给命令解释程序
- A. ②→③→①
- B. ①→②→③
- C. ③→②→①
- D. ②→①→③
9.7缓冲区:为什么要先放进内核缓冲区
缓冲的目的:缓和 CPU 与设备的速度差、减少中断次数、放宽对中断响应时间的要求、提高 CPU 与设备的并行性。磁盘缓冲区的主要目的是减少磁盘 I/O 次数(2015-28);提前读、延迟写、为文件分配连续簇、磁盘高速缓存都能提高文件访问速度(2018-31)。
设:设备把一块数据读入缓冲区用时 T,从缓冲区传到用户区用时 M,CPU 处理一块用时 C。
- A. 1500μs、1000μs
- B. 1550μs、1100μs
- C. 1550μs、1550μs
- D. 2000μs、2000μs
展开解析
B设 T = 读入缓冲区 100μs,M = 缓冲区送用户区 50μs,C = 分析 50μs。单缓冲每块 max(C, T) + M,双缓冲每块 max(T, M + C),最后再补上最后一块的尾巴。
- 单缓冲:每块 max(50, 100) + 50 = 150μs,10 块 1500μs;最后一块还要分析 50μs → 1550μs。
- 双缓冲:每块 max(100, 50 + 50) = 100μs,10 块 1000μs;最后一块还要传送 50、分析 50 → 1100μs。
- A 错1500、1000 只算了 10 个“每块周期”,漏掉最后一块的收尾:单缓冲最后一块还要分析 50μs,双缓冲最后一块还要传送 + 分析 100μs。
- B 对单缓冲 1550μs、双缓冲 1100μs。
- C 错双缓冲也写成 1550μs,等于认为双缓冲没有带来并行。实际上两个缓冲区可以一个读盘、一个往用户区送,每块周期从 150 降到 100μs。
- D 错2000μs = 10 × (100 + 50 + 50),把读盘、传送、分析完全串行,完全没有利用缓冲区让读盘和分析并行。
单缓冲:max(C, T) + M;双缓冲:max(T, M + C);n 块总时间 = n × 每块时间 + 最后一块的尾巴。同类:2013-27(系统缓冲区、用户工作区都单缓冲,T = 100、M = 5、C = 90,读入并分析 2 块最短 = 100 + 5 + 100 + 5 + 90 = 300)。
为什么单缓冲是 max(C, T) + M:缓冲区只有一个,下一块读入缓冲区(T)必须等上一块从缓冲区传走(M);而上一块的处理(C)可以和下一块的读入(T)同时进行。双缓冲时两个缓冲区交替使用,“传 + 处理”(M + C)可以和读入(T)完全重叠。
循环缓冲与缓冲池
问题:单缓冲、双缓冲只有一两个缓冲区,输入一阵快一阵慢时还是会互相等。缓冲区多一些,怎么管?
大白话:循环缓冲像回转寿司的转盘:厨师(输入进程)往空盘子里放寿司,客人(计算进程)从装满的盘子里拿,两人各有一个“下一个该处理的盘子”的指针,绕着圈走。缓冲池则像食堂的公用餐盘架,所有窗口共用,用完放回。
定义:循环缓冲由多个大小相同的缓冲区连成环,输入指针 in 指向下一个可以装数据的空缓冲区,输出指针 out 指向下一个可以取数据的满缓冲区,两个指针都沿同一方向前进。
缓冲池:由系统统一管理、多个进程共享的一组缓冲区,比每个进程各设缓冲区省内存。池里的缓冲区按状态挂在三个队列上:空缓冲队列、输入队列(装满输入数据)、输出队列(装满输出数据)。工作时用四种工作缓冲区:
| 工作缓冲区 | 从哪个队列取一个缓冲区 | 做什么 | 用完挂到哪 |
|---|---|---|---|
| 收容输入 hin | 空缓冲队列 | 装入输入设备送来的数据 | 输入队列 |
| 提取输入 sin | 输入队列 | 把数据交给计算进程 | 空缓冲队列 |
| 收容输出 hout | 空缓冲队列 | 装入计算进程要输出的数据 | 输出队列 |
| 提取输出 sout | 输出队列 | 把数据送给输出设备 | 空缓冲队列 |
记法:“收容”是往缓冲区里装,从空队列拿空的;“提取”是从缓冲区里取,从满的队列拿,取完还给空队列。高速缓存与缓冲区的区别:高速缓存存的是低速设备上某些数据的副本(设备上仍有);缓冲区存的是低速设备传给高速设备的数据,可能是唯一的一份。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 860μs、720μs
- B. 800μs、640μs
- C. 860μs、860μs
- D. 720μs、640μs
展开解析
A第一块要经过读入、传送、处理,共 160μs,之后每块增加一个周期。单缓冲周期 = max(C, T) + M = 100μs,总时间 = 160 + 7 × 100 = 860μs。双缓冲周期 = max(T, C + M) = 80μs,总时间 = 160 + 7 × 80 = 720μs。
变化点:2011-31 是 10 块、T = 100、M = 50、C = 50(1550μs、1100μs)。这里恰好 T = C + M,双缓冲时读入和“传送 + 处理”完全平衡。
Ⅰ. 采用 SCAN 等磁盘调度算法 Ⅱ. 为文件分配连续的簇 Ⅲ. 设置磁盘高速缓存 Ⅳ. 提高磁盘转速
- A. 仅Ⅰ
- B. 仅Ⅰ、Ⅱ
- C. 仅Ⅲ、Ⅳ
- D. Ⅰ、Ⅱ、Ⅲ、Ⅳ
- A. 缓冲区全空,计算进程应阻塞
- B. 缓冲区全空,输入进程应阻塞
- C. 缓冲区全满,输入进程应阻塞
- D. 缓冲区全满,计算进程应阻塞
展开解析
Cin 指向下一个可装数据的空缓冲区。in 追上 out,说明转了一圈回来,所有缓冲区都装满了,输入进程没地方放数据,只能阻塞,等计算进程取走数据。反过来,out 追上 in 是全空,计算进程阻塞。
考点:循环缓冲;与第 7 篇生产者—消费者问题对照。
9.8设备分配与 SPOOLing
- 设备分三类:独占设备(打印机:一段时间只给一个进程)、共享设备(磁盘:可以交替访问)、虚拟设备(用 SPOOLing 把独占设备变成“共享”的)。
- 设备分配的数据结构:设备控制表 DCT、控制器控制表 COCT、通道控制表 CHCT、系统设备表 SDT。分配要考虑:设备的固有属性(类型)、分配算法、安全性、访问权限、占用状态、逻辑设备与物理设备的映射(2023-32 全选)。
- 分配步骤(单通道结构):① 根据进程请求的物理设备名查 SDT,找到该设备的 DCT,设备忙就把进程的 PCB 挂到该设备的等待队列,不忙就分配设备;② 由 DCT 找到它连着的控制器的 COCT,控制器忙就等待,不忙就分配控制器;③ 由 COCT 找到通道的 CHCT,通道忙就等待,不忙就分配通道。设备、控制器、通道三者都分配成功,才算分配成功,才能启动 I/O。改进:用逻辑设备名请求,系统从 SDT 里找一台空闲的同类设备(设备独立性)。
- 分配方式与算法:静态分配(作业开始前一次分配全部所需设备,不会死锁,但设备利用率低);动态分配(运行中按需申请);分配算法有先来先服务、优先级高者优先。安全分配(进程发出 I/O 请求后就阻塞,I/O 完成才唤醒,破坏了“请求并保持”,不会死锁,但 CPU 与 I/O 串行);不安全分配(发出请求后继续运行,还能再请求别的设备,效率高,但可能死锁)。
逻辑设备表 LUT 与设备分配的四张表
问题:进程说“我要打印机”,操作系统怎样一步步找到一台空闲的打印机,以及它连着的控制器和通道?
大白话:先查“通讯录”(LUT:你说的“打印机”是哪一台),再查“设备总表”(SDT),然后顺着设备 → 控制器 → 通道一路往下问“忙不忙”,三关都过了才能开始干活。
跟我做一遍:进程请求逻辑设备 printer。① 查 LUT:printer 对应打印机 3;② 查 SDT 找到打印机 3 的 DCT:空闲 → 分配;③ 由 DCT 找到控制器 1 的 COCT:正忙 → 进程挂到控制器 1 的等待队列,I/O 还不能启动;④ 控制器 1 空出来后再分配它,接着查 CHCT 分配通道,三样都到手才启动打印。
LUT 怎么设置:① 整个系统一张 LUT:所有用户共用,不同用户不能用相同的逻辑设备名,适合单用户系统;② 每个用户一张 LUT:用户登录时建立,放在该用户的进程控制块(或用户管理进程的 PCB)里,不同用户可以用同样的逻辑设备名,适合多用户系统。
真题怎么考:设备独立性:用逻辑设备名、更换设备不改程序(2009-32、2020-30);分配要考虑的因素(2023-32)。
| SPOOLing 的组成 | 作用 |
|---|---|
| 输入井、输出井 | 在磁盘上开辟的两块区域:输入井模拟脱机输入时的磁盘,存放输入设备送来的数据;输出井存放用户进程要输出的数据 |
| 输入缓冲区、输出缓冲区 | 在内存中开辟:暂存输入设备送来的数据再送输入井;暂存从输出井送来的数据再送输出设备 |
| 输入进程、输出进程 | 模拟脱机输入/输出时的外围控制机:输入进程把输入设备的数据经输入缓冲区送到输入井;输出进程把输出井的数据经输出缓冲区送到输出设备 |
| 井管理程序 | 控制作业与磁盘井之间的信息交换 |
共享打印机的过程:用户进程要打印时,SPOOLing 系统并不真的把打印机分给它,而是 ① 由输出进程在输出井中为它申请一块空闲区,把要打印的数据送进去;② 为它申请一张空白的用户请求打印表,填好打印要求,挂到打印请求队列上。之后用户进程就可以继续运行,好像已经打印完了一样。打印机空闲时,输出进程从队首取一张请求表,把输出井里对应的数据送打印机打印。每个进程都觉得自己独占了一台打印机——这就是“把独占设备改造成共享设备(虚拟设备)”。
SPOOLing 的特点:提高了 I/O 速度(进程对低速设备的 I/O 变成了对高速磁盘的读写);把独占设备改造成共享设备;实现了虚拟设备功能。代价是要占用磁盘空间、需要多道程序(输入/输出进程和用户进程并发)支持。
系统最大并发 I/O 速率怎么算
问题:一台机器挂了好几个控制器、几十台设备,它们同时工作时,系统每秒最多能传多少数据?
大白话:数一数“同一时刻真正能一起传数据的有几台”,把它们的速率加起来;最后再看公共的总线吃不吃得下,吃不下就以总线为准(短板)。
- 按控制器分组:同一个控制器下“一次只允许一台传输”的,这一组只算 1 台的速率;“可以同时处理所有设备”的,这一组全部相加。
- 各组相加:不同控制器可以并行工作。本例 3 × 200 + 2 × 8 × 2 = 600 + 32 = 632MB/s(已用程序验算)。
- 和公共通路比:若题目给了总线(或通道)带宽,取较小者。本例总线 500MB/s < 632MB/s → 最大 500MB/s。没给就不用比。
真题怎么考:408 真题还没有考过这种题,它是教材习题里的经典算例。会了“分组相加、再取瓶颈”这两步就够了。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. SPOOLing 把共享设备改造成独占设备
- B. 输入井和输出井设在内存中
- C. 由输入进程和输出进程负责在设备与井之间传送数据
- D. 不需要多道程序设计技术的支持
- A. 用户程序中使用物理设备名
- B. 更换同类物理设备后,应用程序不必修改
- C. 逻辑设备表由用户程序维护
- D. 设备独立性要求所有设备使用同一个驱动程序
- A. 为每个用户设置一张
- B. 整个系统只设置一张
- C. 放在设备控制表 DCT 中
- D. 由设备驱动程序维护
- A. 1850MB/s
- B. 950MB/s
- C. 350MB/s
- D. 650MB/s
展开解析
D磁盘:每个控制器同一时刻只有 1 台在传,2 个控制器并行 → 2 × 300 = 600MB/s;终端:10 台同时传 → 50MB/s。合计 650MB/s。A 把 6 台磁盘都当成同时传;B 是把一个控制器下的 3 台磁盘当成同时传(900)再加终端;C 只算了一个磁盘控制器。
考点:最大并发 I/O 速率。
- 主频 1GHz,某设备 100KB/s、以 16 位为单位用中断方式传送,每次中断开销 500 个时钟周期,CPU 用于它的时间占比?
答案
每秒 100KB/2B = 50000 次(按 K=1000 口径)× 500 = 2.5×107 → 2.5%。 - 中断方式和 DMA 方式各在什么时刻响应请求?
答案
中断:一条指令执行结束后;DMA:一个存储周期(总线事务)结束后,可以在指令执行过程中。 - 键盘中断处理程序在运行时,等待输入的进程处于什么状态?
答案
阻塞态(中断处理程序运行在内核态,它会把该进程唤醒成就绪)。 - 单缓冲(系统缓冲区和用户区各一个),T=80、M=20、C=100,读入并处理 5 块共需多少时间?
答案
画时间线:块1 读 0~80、传 80~100、处理 100~200;块2 读 100~180,但要等用户区空(块1 处理完)才能传:传 200~220、处理 220~320;之后每块都是“传 20 + 处理 100” = 120 一个周期:块3 处理到 440,块4 到 560,块5 传 560~580、处理 580~680。即 5 × max(C,T)+M = 5×120 = 600,再加上第一块开头的 T = 80。(和 2011-31 对比:那里 T > C,最后要加的是 C。) - 总线时钟频率 100MHz,一个总线传输周期占 4 个时钟周期,总线宽度 32 位,总线带宽是多少?
答案
每秒 100M / 4 = 25M 次,× 4B = 100MB/s。 - DMA 方式下,数据在设备和主存之间传送时要经过 CPU 的寄存器吗?DMA 请求在什么时候被响应?
答案
不经过 CPU,由 DMA 控制器直接在设备接口和主存之间传;在一个存储周期(总线周期)结束时响应,可以在一条指令执行的中间。 - 设备分配时,设备、控制器、通道中哪些分到了才能启动 I/O?
答案
三者都分配成功才行,缺一个就要在相应队列上等待。 - SPOOLing 系统中,用户进程执行“打印”后立刻继续运行,打印机当时被分配给它了吗?
答案
没有。数据只是被放进了磁盘上的输出井,并挂了一张打印请求表;真正打印由输出进程在打印机空闲时完成。
9.✎本篇真题闯关(56 题)
本篇各小节挂的真题全部在这里,按小节排好。可以展开后一道道做,也可以用弹窗模式连续做。做题记录(对/错)只保存在你这台电脑的浏览器里,小节标题下的题号会随之变绿或变红。
融合四:磁盘与文件系统 —— 数据怎么长期存在磁盘上
前面讲的都是“内存里的事”。程序、文件最终都存在磁盘上。这一篇自底向上:先看磁盘的机械结构和一次读写要多长时间(计组),再看操作系统怎么调度多个磁盘请求、怎么格式化和引导(OS 5.3),最后看文件系统怎么组织:目录、inode、文件的块放在哪、空闲块怎么记录、打开一个文件时内存里建了哪些表(OS 第 4 章)。文件系统的综合题几乎每两年一道。
10.1计组磁盘的结构和访问时间
展开解析
- (1)柱面号(磁道号):20000 个 → ⌈log220000⌉ = 15 位;磁头号(盘面号):4 片 × 2 面 = 8 → 3 位;扇区号:500 → ⌈log2500⌉ = 9 位。
- (2)转一圈 60s/7200 ≈ 8.33ms;寻道 5 + 旋转半圈 4.17 + 传输 8.33/500 ≈ 0.017 → 约 9.18ms。
- (3)缓冲区 8 字节满一次就请求一次总线:512/8 = 64 次。可以:周期挪用 DMA 优先级高于 CPU,磁盘数据一旦开始传送就必须按时写走,否则会被后面的数据覆盖而丢失。
同类:2013-21(10000rpm、寻道 6ms、传输率 20MB/s、控制器延时 0.2ms,读 4KB:6 + 3 + 0.2 + 0.2 = 9.4ms);2015-20(7200rpm、寻道 8ms、每道 1000 扇区:8 + 4.17 + 0.008 ≈ 12.2ms)。
① 为什么地址按“柱面号—盘面号—扇区号”排,而不是“盘面—柱面—扇区”?读一段地址连续的数据时,地址增加先让扇区号变、再让盘面号变、最后才让柱面号变:同一柱面上换盘面只是电子地切换磁头,不用移动磁臂;若把盘面放在最高位,连续的数据就会分布在同一盘面的不同柱面上,每读一个磁道都要移动磁臂(寻道最慢)。2019-44 的簇号换算就是按这个顺序:簇号 ÷ 每柱面簇数 = 柱面号,余数 ÷ 每磁道簇数 = 盘面号,再余的就是磁道内的位置。
② 交替编号:读完一个扇区后,控制器要花一点时间把数据送走,这期间盘片还在转。如果逻辑上相邻的扇区在物理上也紧挨着,处理完时磁头已经转过了下一个扇区,只好再等一整圈。办法是让逻辑相邻的扇区在物理上隔开一个或几个扇区(例如逻辑 0、1、2、3 放在物理 0、2、4、6),处理的时间里正好转过中间的扇区。
③ 错位命名:不同盘面上相同编号的扇区彼此错开一点,读完一个盘面的某磁道、切换到下一盘面继续读时,也留出了磁头切换的时间。
RAID 磁盘阵列:条带、镜像、分布式校验
问题:一块磁盘太慢、坏了数据就全没了。把好几块磁盘组合起来,能不能又快又可靠?
大白话:抄作业的三种办法。条带化(RAID 0):一本书拆开,几个人同时抄不同的页,快,但谁丢了一页书就残了;镜像(RAID 1):两个人抄同样的内容,丢一份还有一份,但纸用了双倍;校验(RAID 5):几个人各抄一部分,再多抄一页“校验页”,谁丢了,用其余的几页就能把它推算回来。
| 级别 | 做法 | n 块容量为 C 的盘,可用容量 | 最多坏几块不丢数据 | 特点 |
|---|---|---|---|---|
| RAID 0 | 条带化 | nC | 0 | 读写最快,可靠性比单盘还差 |
| RAID 1 | 镜像(两盘一组) | nC / 2 | 每组坏 1 块 | 最简单可靠,空间利用率 50% |
| RAID 5 | 条带 + 分布式奇偶校验 | (n − 1)C | 任意 1 块 | 读快;写一块小数据要 4 次 I/O(读旧数据、读旧校验、写新数据、写新校验) |
| RAID 10 拓展 | 先镜像再条带 | nC / 2 | 每个镜像组坏 1 块 | 又快又可靠,但空间利用率 50% |
跟我做一遍(已用程序验算):4 块 1TB 的盘,RAID 0 可用 4TB,RAID 1(两组镜像)和 RAID 10 可用 2TB,RAID 5 可用 3TB。校验怎么恢复:一条带上 A1 = 1011、A2 = 0110、A3 = 1100,校验 Ap = A1 ⊕ A2 ⊕ A3 = 0001。存 A2 的盘坏了:A2 = A1 ⊕ A3 ⊕ Ap = 1011 ⊕ 1100 ⊕ 0001 = 0110,原样算回来了。
真题怎么考:提高可靠性的措施是镜像和校验,不是条带化和 Cache(2013-20)。
固态硬盘 SSD:页写块擦、闪存翻译层与磨损均衡
问题:SSD 用 Flash 做成,没有磁头和盘片。它和机械硬盘在读写上有什么根本不同?操作系统要为它改什么?
大白话:Flash 像一本“只能用铅笔写、而且只能整页撕下来重写”的本子:写之前要擦,但擦只能一大块一起擦。所以 SSD 改一页数据时不在原地改,而是写到一张新纸上,再记一笔“这个内容现在在新的位置”。
定义:Flash 以页(几 KB)为单位读写,以块(几十到几百页)为单位擦除,写之前必须先擦(页写块擦)。SSD 内部的控制器运行闪存翻译层 FTL:把操作系统看到的逻辑块号映射到 Flash 的物理页,让 SSD 在操作系统看来就是一个普通的块设备。
- 磨损均衡:每个块能承受的擦写次数有限(几千到几万次),某些块被反复擦写会先坏。动态磨损均衡:写新数据时,优先选擦除次数少的空闲块;静态磨损均衡:还会把长期不改的“冷”数据从年轻的块搬到老的块上,让年轻的块也参与写入,效果更好。磨损均衡由 SSD 的控制器完成,不是文件系统(2025-32)。
- 和机械硬盘比:没有寻道和旋转延迟,随机读很快;写比读慢(可能要先擦除);对 SSD 做磁盘调度没有意义,FCFS 即可(2010-45 第 3 问)。
- 拓展 写放大:垃圾回收要搬有效页,Flash 实际写入的数据比主机要求写的多。例(已用程序验算):一个块 64 页,回收时里面还有 48 个有效页,把它们搬走、擦除整块后只腾出 16 页给主机写 → 主机写 16 页,Flash 共写 48 + 16 = 64 页,写放大 = 64 ÷ 16 = 4。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 4.3ms
- B. 9.3ms
- C. 14.3ms
- D. 9.1ms
展开解析
B平均旋转延迟 = (60 ÷ 6000) ÷ 2 = 5ms;传输 4KB ÷ 40MB/s ≈ 0.1ms。总时间 = 4 + 5 + 0.1 + 0.2 ≈ 9.3ms。
变化点:2013-21 是 10000rpm、6ms 寻道(9.4ms)。C 用了整圈旋转时间,A 漏了旋转延迟,D 漏了控制器延迟。
- A. 4.0ms
- B. 7.0ms
- C. 10.0ms
- D. 7.5ms
展开解析
B转一圈 6ms,平均旋转延迟 3ms;读一个扇区 = 6ms ÷ 500 = 0.012ms。总时间 ≈ 4 + 3 + 0.012 ≈ 7.0ms。
变化点:2015-20 是 7200rpm、8ms、1000 个扇区(约 12.2ms)。扇区很多时传输时间很小,平均存取时间主要是寻道时间加半圈。
- A. 8TB、0
- B. 4TB、2
- C. 6TB、1
- D. 6TB、2
- A. 可以直接在原位置覆盖写某一页
- B. Flash 的擦除以页为单位
- C. 磨损均衡由操作系统的文件系统实现
- D. 闪存翻译层(FTL)把逻辑块号映射到 Flash 的物理页
10.2操作系统磁盘调度算法
寻道时间是大头,所以当有多个请求排队时,操作系统(设备驱动)按某种顺序服务,目标是让磁头少跑路。
| 算法 | 规则 | 特点 |
|---|---|---|
| FCFS | 按请求到达顺序 | 公平,不会“磁臂黏着”(2018-30);寻道长 |
| SSTF 最短寻道优先 | 每次选离磁头最近的 | 平均寻道短;远处的请求可能饥饿 |
| SCAN 扫描(电梯) | 沿一个方向服务到磁盘端点,再掉头 | 无饥饿;两端的请求等得久 |
| LOOK | 同 SCAN,但到最后一个请求就掉头 | SCAN 的改进 |
| C-SCAN 循环扫描 | 只沿一个方向服务,到端点后直接返回另一端重新扫描(返回途中不服务) | 各位置等待时间更均匀 |
| C-LOOK | 同 C-SCAN,但到最后一个请求就返回,返回到另一侧最远的请求 |
展开解析
| 算法 | 服务顺序 | 移动总距离 | 平均寻道长度 |
|---|---|---|---|
| FCFS | 55 58 39 18 90 160 150 38 184 | 498 | 55.3 |
| SSTF | 90 58 55 39 38 18 150 160 184 | 248 | 27.6 |
| SCAN | 150 160 184 (199) 90 58 55 39 38 18 | 280 | 31.1 |
| LOOK | 150 160 184 90 58 55 39 38 18 | 250 | 27.8 |
| C-SCAN | 150 160 184 (199)(0) 18 38 39 55 58 90 | 388 | 43.1 |
| C-LOOK | 150 160 184 18 38 39 55 58 90 | 322 | 35.8 |
括号里的 199、0 是到达端点(不是请求),距离要算上。平均寻道长度 = 移动总距离 ÷ 请求个数(这里 ÷ 9)。以上全部用程序验算;也可以把数据填进下面的工具逐个核对。
口径提醒:汤子瀛《计算机操作系统》里称为 SCAN、C-SCAN 的例题(平均 27.8、35.8)实际走的是“到最远的请求就掉头/返回”,即这里的 LOOK、C-LOOK。408 题目一般会说明“移动到磁盘端点”还是“到最远请求”;没说明时按题目给出的选项反推口径,C-SCAN 的返回距离是否计入也同理(2024-32 计入)。
- A. 208
- B. 287
- C. 325
- D. 382
展开解析
C“从外侧向内侧”= 磁道号增大。SCAN(电梯)沿当前方向走到头,再掉头处理另一边。
- 向增大方向:58 → 130 → 180 → 199(199 正好是最内侧磁道,也是请求):199 − 58 = 141。
- 掉头:199 → 42 → 15:199 − 15 = 184。
- 合计 141 + 184 = 325。
- A 错208 比 325 还少。只要从 58 出发先向内走、又要服务 199 和 15,最少就要 (199 − 58) + (199 − 15) = 325,208 漏掉了一段移动。
- B 错287 也小于 325 这个下限:路线必须先从 58 走到 199(141)、再掉头走到 15(184),少一道都服务不到全部请求。
- C 对141 + 184 = 325。
- D 错382 = 141 + 199 + 42,是 C-SCAN 的走法:到 199 后返回 0 号磁道(计返回距离 199)再向内处理 15、42。题目要求的是 SCAN,不做单向回零。
SCAN 走到端点再掉头;LOOK 只走到最远的请求;C-SCAN 单向,返回距离要不要算看题目。本题 199 正好是端点,SCAN = LOOK。同类:2009-29(105 向增大方向 SCAN:110, 170, 180, 195, 68, 45, 35, 12);2021-26(SSTF 从 184:184, 182, 187, 176, 199 → 0 + 2 + 5 + 11 + 23 = 41)。
- A. 599
- B. 619
- C. 788
- D. 799
展开解析
CC-SCAN(循环扫描)只朝一个方向服务:走到这一端后直接返回另一端,再继续同一方向。本书与 2010-45 的口径都把返回的距离计入。
- 向减小方向:200 → 160 → 120 → 110 → 0,移动 200。
- 返回另一端:0 → 399,移动 399。
- 继续向减小方向:399 → 300 → 210,移动 189(399 本身也是请求,返回时顺便完成)。
- 合计 200 + 399 + 189 = 788。
- A 错599 = 200 + 399,是 SCAN 的走法:到 0 后掉头向增大方向,顺路服务 210、300、399。C-SCAN 不在回程服务,回到 399 后还要向下走到 210(再加 189)。
- B 错619 少于 C-SCAN 的必经路程:向下到 0(200)、返回到 399(399)、再向下到 210(189),三段缺一段都服务不到全部请求。
- C 对200 + 399 + 189 = 788。
- D 错799 比 788 多了 11:最后一个请求是 210,磁头走到 210 就完成了,不用继续向下多走。
C-SCAN 的返回要不要计入看题目口径,408 真题(2010-45、2024-32)都计入。2010-45:从 100 向增大方向 C-SCAN,请求 50、90、30、120 → 100 → 120 → 30 → 50 → 90,移动 20 + 90 + 20 + 40 = 170。
展开解析
- 300 × 10 × 200 × 512B = 3×105 KB(≈ 293MB)。
- 每磁道 200/2 = 100 簇,每柱面 1000 簇 → 簇号 ÷ 1000 = 柱面号:100260 → 100,60005 → 60,101660 → 101,110560 → 110。从 85 出发 SSTF:100(距 15)→ 101(1)→ 110(9)→ 60(50)→ 次序 100260、101660、110560、60005。
- 100530:柱面 = 100530 ÷ 1000 = 100,余 530;磁道(磁头)= 530 ÷ 100 = 5,余 30;扇区 = 30 × 2 = 60。由磁盘驱动程序完成。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 100
- B. 110
- C. 120
- D. 130
展开解析
B从 100 出发,每次去最近的:90(10)→ 70(20)→ 60(10)→ 115(55)→ 130(15),共 110。
变化点:SSTF 不管方向。从 90 出发时,70(距离 20)比 115(距离 25)近,所以先去 70,最后再掉头。
- A. 220
- B. 250
- C. 280
- D. 330
展开解析
C先向减小方向:90、60、30,一直走到 0 号磁道才掉头,再去 150、180。移动 = (100 − 0) + (180 − 0) = 280。
变化点:按 LOOK(到最远的请求就掉头)是 (100 − 30) + (180 − 30) = 220(A)。题目写明“到达端点才掉头”,就要一直走到 0。
- A. 359
- B. 558
- C. 758
- D. 798
展开解析
C向增大方向:210、300、399,然后返回 0 号磁道(399 个磁道,计入),再向增大方向:0、110、120、160。共 (399 − 200) + 399 + 160 = 758。
变化点:2024-32 是向减小方向(788)。408 真题的口径是把返回的距离计入;不计入则是 A(359)。
10.3磁盘的初始化与引导
- 物理格式化(低级格式化):把磁道划分成扇区,每个扇区写上头部(地址)、数据区、尾部(校验码),标记坏扇区。一般出厂时完成。
- 分区:把磁盘分成一个或多个分区(C 盘、D 盘),分区表放在第一个扇区的主引导记录 MBR 里。
- 逻辑格式化(高级格式化):在分区上建立文件系统——写入根目录、初始化空闲空间管理的数据结构(位示图/空闲链等)、确定盘块(簇)大小(2017-29 选“建立根目录、初始化空闲块结构”;2025-32 文件系统能为机械硬盘和 SSD 提供的是“确定盘块大小”)。
- 安装操作系统。
2021-46:制作启动盘的顺序是物理格式化 → 分区 → 逻辑格式化 → 安装操作系统;扇区的划分在物理格式化中完成,根目录的建立在逻辑格式化中完成;开机时执行顺序是 ROM 中的引导程序 → 磁盘引导程序(MBR)→ 分区引导程序 → 操作系统初始化程序(第 1 篇图 1-19)。
扇区、块、簇,以及坏块怎么处理
问题:题目里一会儿说扇区,一会儿说块、簇,它们是什么关系?磁盘用久了有扇区坏掉,数据会不会写进坏扇区?
大白话:扇区是磁盘这张“纸”上最小的格子,磁盘一次至少读写一个格子;块(Windows 里叫簇)是文件系统分配空间的单位,由几个相邻的扇区组成,像“按整行发纸”。坏了的格子要么由文件系统记下来不再用,要么由磁盘自己偷偷换成备用格子。
- 扇区:磁盘的物理读写单位(常见 512B 或 4KB),物理格式化时划分(2019-20)。
- 块 / 簇:文件系统分配空间的单位,= 2k 个连续扇区,逻辑格式化时确定(2025-32)。文件最后一块没用满也整块占用(内部碎片,2017-26)。
| 坏块处理方式 | 谁来做 | 怎么做 |
|---|---|---|
| 文件系统标记 | 操作系统(简单磁盘,如早期 IDE) | 逻辑格式化时扫描全盘,把坏块在 FAT 或坏块表中标记出来,以后不再分配;对操作系统不透明 |
| 扇区备用 | 磁盘控制器(复杂磁盘,如 SCSI、现代硬盘) | 控制器维护坏块链表,低级格式化时初始化,用备用扇区替换坏扇区;对操作系统透明 |
跟我做一遍(已用程序验算):扇区 512B、簇 4KB → 一簇 = 8 个扇区。一个 10000B 的文件要 ⌈10000 ÷ 4096⌉ = 3 个簇 = 24 个扇区,实际占 12288B,最后一簇浪费 2288B。
引导块:计算机开机后,ROM 里只有一个很小的自举程序,它把磁盘引导块(第一个扇区 MBR 中的引导程序)读进内存执行,再由它找到活动分区的分区引导记录,最终装入操作系统内核(第 1 篇图 1-19)。存有引导块和操作系统的磁盘叫启动盘(系统盘)。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 建立文件系统的根目录
- B. 把磁道划分成扇区,确定每个扇区的头部、数据区和校验码
- C. 初始化空闲块管理的数据结构
- D. 建立分区表
- A. 由文件系统在 FAT 中把坏簇标记出来
- B. 由用户程序自行跳过坏扇区
- C. 每次访问前重新进行低级格式化
- D. 由磁盘控制器用备用扇区替换坏扇区,对操作系统透明
10.4操作系统文件、FCB 与 inode
操作系统为每个文件建立一个文件控制块 FCB,存文件的名字、大小、类型、创建时间、存取权限(2009-30)、数据在磁盘上的位置等。一个目录就是一个特殊的文件,内容是很多条“目录项”。
为了让查目录更快,UNIX 把 FCB 拆成两部分:目录项里只放文件名 + inode 号(很短,一个盘块能放很多项),其余的描述信息都放在 inode(索引节点)里。
硬链接
新建一个目录项,指向同一个 inode,inode 的链接计数 +1。删除时计数 −1,减到 0 才真正删除文件。软链接(符号链接、快捷方式)
新建一个独立的文件(有自己的 inode),内容是目标的路径名。目标被删了,软链接就“断了”。删除文件时内核不必去删它的快捷方式(2021-30)。- A. 0、1
- B. 1、1
- C. 1、2
- D. 2、1
展开解析
B软链接是一个新文件(有自己的 inode,内容是 F1 的路径);硬链接是一个新目录项,和 F1 共享同一个 inode。
- 建 F2(软链接):F2 自己的计数为 1;F1 的计数不变,仍为 1。
- 建 F3(硬链接):F1、F3 共享的 inode 计数变为 2。
- 删除 F1:共享 inode 的计数减 1 → 1。F2 不受影响,仍为 1。
- A 错删除 F1 不会把软链接 F2 的计数变成 0(F2 只是“断了”,指向的路径不存在了)。
- B 对1、1。
- C 错忘了删除 F1 时共享 inode 的计数要减 1。
- D 错把建软链接也算成了 F1 计数加 1。
只有硬链接会增加 inode 的链接计数;删除一个名字就减 1,减到 0 才真正删除文件。
文件属性:FCB 里记了什么
FCB(inode)里记的就是文件的属性。不同系统各有增减,教材列出的常见属性如下:
| 属性 | 含义 |
|---|---|
| 名称 | 给人看的文件名(inode 方式下放在目录项里,不在 inode 里) |
| 标识符 | 系统内部唯一的编号(如 inode 号),人看不懂 |
| 类型 | 普通文件、目录文件、设备文件…… |
| 位置 | 文件存在哪个设备上、占了哪些盘块(10.6) |
| 大小 | 当前大小(字节数或块数),也可以有允许的最大值 |
| 保护 | 访问控制信息:谁能读、写、执行(10.8) |
| 时间、日期、用户标识 | 创建、最后修改、最后访问的时间和用户,用于保护和监控 |
文件的基本操作:每个操作 OS 做了什么
| 操作 | 操作系统做的事 |
|---|---|
| 创建 create | ① 为文件在外存上分配空间(需要时);② 在目录中为它建一个新的目录项(FCB/inode),记下文件名、位置等 |
| 删除 delete | 按文件名在目录中找到目录项;回收文件占的磁盘块(和 inode,若链接计数减到 0);删除目录项。不删除它所在的目录(2013-23) |
| 打开 open | 按文件名逐级查目录找到 FCB/inode(按名查找只在 open 时做,2024-29),检查权限,把 FCB/inode 读入内存,在系统打开文件表、进程打开文件表中建表项,返回文件描述符 fd。不读文件的数据(2014-29) |
| 关闭 close | 删除进程打开文件表中的表项;系统打开文件表项的打开计数减 1,减到 0 时删除该项(修改过的 FCB 写回磁盘,2023-31) |
| 读 read / 写 write | 用 fd(不用文件名,2012-28)找到表项,按读写指针算出逻辑块号,查 FCB/inode 转换成物理块号,读写后移动读写指针 |
| 定位 seek | 只修改读写指针 |
有了 open,后面每次读写就不用再“按名查目录”——目录查找很慢(要逐级读盘),只做一次。系统打开文件表里一个文件只有一项(多个进程共享,记打开计数),进程打开文件表里各进程有自己的表项(记读写指针、打开方式等)。
文件的逻辑结构:用户眼里文件长什么样
“物理结构”(10.6 节)说的是文件的块在磁盘上怎么放;“逻辑结构”说的是用户把文件看成什么:
| 逻辑结构 | 样子 | 查找第 i 条 / 按关键字查找 |
|---|---|---|
| 无结构(流式)文件 | 一串字节(源程序、图片、可执行文件),现代系统的普通文件都是这种 | 按字节偏移定位 |
| 顺序文件 | 记录一条接一条;可以按关键字排好序 | 定长记录可以直接算出第 i 条的位置;按关键字只能顺序找(排好序可以折半) |
| 索引文件 | 另建一张索引表:每条记录一个表项(关键字、长度、地址),适合变长记录 | 查索引表就能直接定位,但索引表本身可能很大 |
| 索引顺序文件 | 记录分组,每组在索引表里只占一项(组内按顺序) | 先查索引找到组,再在组内顺序找;N 条记录分 √N 组时平均查找约 √N 次 |
| 直接文件(散列文件) | 由关键字经散列函数算出存放地址 | 最快,但有冲突 |
按有没有结构分两大类:无结构文件(流式文件,一串字节)和有结构文件(记录式文件,由一条条记录组成,每条记录有关键字)。顺序、索引、索引顺序、散列都是记录式文件的组织方式。
跟我做一遍:平均查找次数(已用程序验算,按“查找成功时平均比较一半”计算):
| 文件有多少条记录 | 顺序文件 | 一级索引顺序文件 | 二级索引顺序文件 |
|---|---|---|---|
| N = 10000 | 10000 ÷ 2 = 5000 | 分 100 组,每组 100 条:查索引 50 + 组内 50 = 100 | — |
| N = 106 | 500000 | 分 1000 组:500 + 500 = 1000 | 100 × 100 × 100:50 + 50 + 50 = 150 |
规律:N 条记录分成 √N 组时,一级索引顺序文件平均约查 √N 次;记录再多,就给索引表再建一级索引。
打开文件时内存里建了什么
| 教材(王道 / 汤小丹)的两级表 | UNIX / Linux 的实际实现(三级) | |
|---|---|---|
| 每个进程一张 | 进程打开文件表:读写指针、访问权限(打开方式)、指向系统表项的指针;fd 就是它的下标 | 文件描述符表:fd → 指向一个“文件表项” |
| 中间层 | — | 文件表(file 表):读写偏移、打开方式、引用计数;每次 open 新建一项 |
| 全系统一份 | 系统打开文件表:每个打开的文件一项,含 FCB 副本、打开计数 | 内存 inode 表:每个打开的文件一项 |
两种说法只是分层不同:教材把“读写指针”放在进程打开文件表里,UNIX 把它放在中间的文件表里。做题按教材的两级说法即可:读写指针各进程各一份,FCB(inode)全系统一份。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 1、1;不能访问
- B. 2、1;能访问
- C. 1、1;能访问
- D. 1、0;不能访问
展开解析
AF2 与 F1 共用一个 inode,建硬链接后计数为 2,删除 F1 后减为 1。F3 有自己的 inode,计数为 1。F3 里存的是路径“F1”,F1 删除后路径失效,不能访问。
变化点:2009-31 先建软链接、后建硬链接,计数结果相同(1、1)。这里多问一句能否访问:硬链接能,软链接不能。
Ⅰ. 按文件名查找目录 Ⅱ. 把文件的 FCB(inode)读入内存
Ⅲ. 把文件的全部内容读入内存 Ⅳ. 在打开文件表中建立表项,返回文件描述符
- A. 仅Ⅰ、Ⅱ
- B. 仅Ⅱ、Ⅳ
- C. 仅Ⅰ、Ⅱ、Ⅳ
- D. Ⅰ、Ⅱ、Ⅲ、Ⅳ
展开解析
Copen 只沿路径查目录,找到 FCB(inode)读入内存,在系统打开文件表和进程打开文件表中登记,返回文件描述符。文件的数据要等到 read 时才读。
变化点:2014-29 是单选(把 FCB 读到内存)。这里把 open 的完整步骤列出来,干扰项是“读入全部内容”。
- A. 3
- B. 6
- C. 9
- D. 27
展开解析
C每类用户对每种权限都要 1 位表示“有/无”,共 3 × 3 = 9 位,也就是 UNIX 的 rwxrwxrwx。
变化点:2017-30 是 4 类用户 × 5 种权限 = 20 位。D 是把 3 种权限的所有组合数当成了位数。
- A. 50
- B. 100
- C. 5000
- D. 10000
10.5目录结构
四种目录结构:单级、两级、树形、无环图
问题:目录要解决三件事:按名字快速找到文件、允许不同用户起同样的名字、允许多人共享同一个文件。不同的目录结构各解决了几件?
大白话:单级目录像全校一张花名册,重名就乱套;两级目录像每个班一张花名册(主目录记有哪些班),不同班可以重名,但班里不能再分组;树形目录像“年级 → 班 → 小组”一层层分下去;无环图目录再允许一个人同时出现在两个小组的名单上(共享)。
| 结构 | 允许重名 | 文件分类 | 共享 | 查找 |
|---|---|---|---|---|
| 单级 | ✗ | ✗ | — | 文件多时慢 |
| 两级 | 不同用户间 ✓ | ✗(每个用户只有一层) | 不便 | 比单级快 |
| 树形 | 不同目录间 ✓ | ✓ | 不便(只能用路径访问别人的文件) | 用当前目录 + 相对路径,查找更快 |
| 无环图 | ✓ | ✓ | ✓(硬链接、软链接,见本节开头的树形目录图) | 同树形;删除要看链接计数 |
跟我做一遍(每个目录只占一个盘块、目录项就是 FCB、内存里只有根目录的位置):打开 /home/alice/a.c 要依次读根目录、home 目录、alice 目录 3 个盘块,才能在 alice 目录里找到 a.c 的 FCB;若当前目录是 /home/alice(内存里记着它的位置),用相对路径 a.c 只要读 alice 目录这 1 个盘块——这就是设置当前目录能加快检索的原因(2010-31)。
- 单级目录:所有文件一个目录,不能重名。两级目录:每个用户一个目录。
- 树形目录:现在都用它;绝对路径从根开始,相对路径从当前目录开始——设置当前工作目录是为了加快检索速度(2010-31,不用每次从根一级一级查)。
- 无环图目录:允许多个目录项指向同一文件(共享),用链接计数决定何时真正删除。
- 目录的实现(目录项在目录文件里怎么组织):线性列表——目录项一条接一条存放,查找时逐个比较文件名,实现简单但查找慢,创建文件前也要先查一遍有没有重名;哈希表——按文件名算出哈希值,直接定位目录项,查找快,但要处理冲突,表的大小也不好改。
- 删除一个文件:删它在目录中的目录项、释放 FCB/inode、释放它占的磁盘块和内存缓冲区;不会删除文件所在的目录(2013-23)。
展开解析
- (1)目录占 256 × 64B / 1KB = 16 个盘块,要找的目录项可能在第 1~16 块中任何一块,平均读 (1 + 16)/2 = 8.5 块。
- (2)目录项 16B,目录只占 256 × 16B / 1KB = 4 块,平均读 (1 + 4)/2 = 2.5 块找到目录项,再读 1 块得到 inode → 平均 3.5 块。
这就是 UNIX 用 inode 的原因:目录项越短,一个盘块能放的目录项越多,查目录时读盘越少。(按“查找成功时平均要比较一半”的口径计算。)
展开解析
- stu 的目录项:(course,2)、(doc,10)。
- doc 和 course1 的 inode 号都是 10 → 它们是同一个文件(硬链接),数据块相同 → x = 30。
- 目录项已在内存,知道 course1 的 inode 号 → 读 inode 所在的块(1 次)→ 从 inode 得知数据在块 30 → 读数据块(1 次),共 2 个。
- 6MB / 4KB = 1536 块。一块能放 4KB/4B = 1024 个地址:直接 10 块 + 一级 1024 块 = 1034 < 1536 → 还要二级 → 需要一级和二级间接地址项。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 216
- B. 230
- C. 232
- D. 2256
展开解析
A每个文件都有一个索引结点,索引结点号只有 16 位,最多有 216 个不同的索引结点,所以最多 216 个文件。文件名的长度与文件数的上限无关。
变化点:2020-31 的索引结点号占 4B(232)。只看索引结点号的位数,不要被文件名的长度干扰。
Ⅰ. 删除该文件在目录中的目录项 Ⅱ. 若链接计数减为 0,回收它的索引结点和磁盘块 Ⅲ. 删除该文件所在的目录
- A. 仅Ⅰ
- B. 仅Ⅰ、Ⅱ
- C. 仅Ⅱ、Ⅲ
- D. Ⅰ、Ⅱ、Ⅲ
- A. 每个文件名的长度可以不同
- B. 系统为每个用户设置了当前目录
- C. 每个用户有自己的用户文件目录(UFD)
- D. 系统允许建立硬链接
10.6文件的物理结构:文件的块放在磁盘哪里
展开解析
- dir:(dir1,48);dir1:(file1,100)、(file2,200)。
- 簇号 16 位 → 最多 216 个表项 → FAT 最大 216×2B = 128KB;文件最大 216×4KB = 256MB。
- FAT 第 i 项存“簇 i 的下一个簇号”:106 存在 FAT[100],108 存在 FAT[106]。
- 从内存中的 dir 得知 dir1 在簇 48 → 读簇 48 得到 file1 首簇 100;第 5000 字节在第 2 簇(4096 ≤ 5000 < 8192),查内存中的 FAT[100] = 106 → 读簇 106。共访问 48、106 两个簇。
| 连续分配 | 隐式链接 | 显式链接(FAT) | 索引分配 | |
|---|---|---|---|---|
| 目录项/FCB 里记 | 起始块号、长度 | 首块号、末块号 | 首块号 | 索引块号(或 inode 中的地址项) |
| 访问方式 | 顺序、随机都快 | 只能顺序 | 顺序、随机(查 FAT) | 顺序、随机 |
| 访问第 i 块读盘次数 (FCB 已在内存,块号从 0 起) | 1 | i + 1(要沿指针从第 0 块读起) | 1(FAT 常驻内存,查表不读盘) | 2(索引块 + 数据块;索引块已在内存则 1) |
| 碎片 | 外部碎片 | 无外部碎片 | 无外部碎片 | 无外部碎片 |
| 文件增长 | 困难(后面可能被占) | 方便 | 方便 | 方便 |
| 额外开销 | 无 | 每块要存指针 | FAT 表要占内存 | 索引块(小文件也要一整块) |
| 缺点 | 要预先知道文件长度 | 一个指针坏了,后面全丢 | FAT 损坏影响全盘 | 大文件索引块不够,要多级/混合索引 |
一个索引块不够用:链接方案、多层索引、混合索引
问题:索引分配时,一个索引块只能放 N = 块大小 ÷ 地址项大小 个块号。块 1KB、地址项 4B 时 N = 256,文件最多 256 块 = 256KB。更大的文件怎么办?
大白话:一本通讯录写满了,有三种办法:① 在最后一行写“续见下一本”(链接方案);② 做一本“目录的目录”,记各本通讯录放在哪(多层索引);③ 常用的几个号码直接写在封面上,不够再用续本、目录的目录(混合索引)。
跟我做一遍(块 1KB、地址项 4B,FCB 已在内存,已用程序验算):
- 链接方案:一个 1000 块的文件要 ⌈1000 ÷ 255⌉ = 4 个索引块。读第 800 块(从 0 编号):800 ÷ 255 = 3 余 35,在第 4 个索引块里,要沿链读 4 个索引块,再读 1 个数据块,共 5 次。
- 两层索引:最多 256 × 256 = 65536 块 = 64MB;读任何一块都是 3 次(第一层、第二层索引块各 1 次,数据块 1 次)。
- 混合索引:把少量直接地址、一个一级间接(单层索引)、一个二级间接(两层索引)……组合在一个 inode 里,小文件快、大文件也装得下(下面详讲)。
说法统一:“二级间接地址”也叫“二次间接地址”,“三级间接”也叫“三次间接”;“一级间接”也叫“一次间接”或“单级间接”。
混合索引(UNIX inode):最常考的计算
- A. 1、2
- B. 1、3
- C. 2、3
- D. 2、4
展开解析
B先算一个索引块能放几个指针,确定直接、一级、二级各覆盖哪些逻辑块,再数要读几个盘块(索引节点已在内存,不算)。
- 每块指针数 N = 1KB ÷ 4B = 256。直接:逻辑块 0 ~ 9;一级:10 ~ 265;二级:266 起。
- 偏移 1234:逻辑块 1234 ÷ 1024 = 1(取整),在直接范围,直接读数据块 → 1 次。
- 偏移 307400:逻辑块 307400 ÷ 1024 = 300(取整),超过 265 → 二级:读二级索引块、一级索引块、数据块 → 3 次。
- A 错第二个算成 2 次,是把逻辑块 300 当成在一级索引范围里;但一级只覆盖 10 ~ 10 + 256 − 1 = 265,300 必须走二级,要多读一个索引块。
- B 对1 次、3 次。
- C 错第一个算成 2 次,是把已在内存的索引节点也算成一次读盘;题目说索引节点已在内存,逻辑块 1 用直接指针,只读数据块。
- D 错两个都多 1,同样是把已在内存的索引节点算成了一次读盘。
逻辑块号 = 偏移 ÷ 块大小(取整);读盘次数 = 用到的索引块层数 + 1(数据块),已在内存的 inode 不算。同类:2010-30(4 直接、2 一级、1 二级,块 256B、指针 4B → N = 64,最大 (4 + 2×64 + 64²) × 256B = 1057KB);2018-46 第 3 问(簇 4KB、8 个直接:6KB 的 F1 只用直接,40KB 的 F2 最后一簇要一级间接);2026-46(偏移 21460 在逻辑块 5,只有 5 个直接地址 → 读间接块 + 数据块 = 2 块)。
展开解析
(1)N = 4KB/4B = 1024;最大 = (8 + 1024 + 10242 + 10243) × 4KB ≈ 4TB。(2)索引节点总数 = 1M × 4KB / 64B = 64M 个;5600B 占 2 个簇,数据区能放 512M/2 = 256M 个;但每个文件都要一个 inode,所以受限于 inode 数 → 最多 64M 个。
连续与链接的访盘次数:2014-46(200 条记录,每块一条,插入为第 30 条:连续分配最少把前 29 条各前移一块,读写 29×2 + 写新记录 1 = 59 次,FCB 的起始块号和长度改变;链接分配读前 29 块 + 写新块 1 + 改写第 29 块 1 = 31 次;块 1KB、指针 4B → 最大 232 × 1020B = 4080GB)。2017-26:簇 1KB,1026B 的文件要分配 2 个簇 = 2048B。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 130.5KB
- B. 8322.5KB
- C. 8448KB
- D. 16645KB
展开解析
B每个索引块放 512 ÷ 4 = 128 个地址。最大块数 = 5 + 2 × 128 + 1282 = 16645 块,× 512B = 8322.5KB。
变化点:2010-30 的块是 256B(N = 64)、4 个直接地址项。一级间接有 2 个,要乘 2。D 是块数,没有乘块大小。
- A. 1、2
- B. 1、3
- C. 2、3
- D. 2、2
展开解析
AN = 2KB ÷ 4B = 512。20000 ÷ 2048 → 第 9 块,属于直接索引,读 1 次。500000 ÷ 2048 → 第 244 块,10 ≤ 244 < 522,属于一级间接,先读索引块再读数据块,共 2 次。
变化点:2015-29 的块是 1KB,307400 落在二级间接(3 次)。块变大后,每一级能管的块数变多,同样大小的偏移可能落在更低的一级。
- A. 5000B
- B. 5120B
- C. 8192B
- D. 4096B
展开解析
C空间按簇分配:5000B 需要 2 个簇,2 × 4KB = 8192B。
变化点:2017-26 是 1KB 簇、1026B 文件(2048B)。B 是按扇区分配的结果;分配单位是簇,最后一个簇没用满也整个分给它(内部碎片)。
- A. 2
- B. 3
- C. 5
- D. 4
展开解析
D每个索引块放 255 个块号:第 0~254 块在第 1 个索引块,255~509 在第 2 个,510~764 在第 3 个。读第 600 块要沿链读 3 个索引块,再读数据块,共 4 次(已用程序验算同类例题)。A 是只有一个索引块时的次数;B 少读了一个索引块(第 600 块在第 3 个索引块里,不在第 2 个)。
考点:链接方案与多层索引。
10.7空闲空间管理:位示图等
| 方法 | 做法 | 特点 |
|---|---|---|
| 空闲表法 | 每个空闲区一项:起始块号、块数 | 适合连续分配 |
| 空闲链表法 | 空闲块(或空闲区)串成链 | 简单,分配多块要多次操作 |
| 位示图 | 每一位对应一个盘块:0 空闲、1 已分配(或相反) | 占用空间只和总块数有关、与当前空闲块多少无关(2024-26);容易找连续空闲块 |
| 成组链接法(UNIX) | 空闲块分组,每组第一块记下一组的块号 | 适合大文件系统 |
空闲表法与空闲链表法
问题:文件系统要随时知道磁盘上哪些块是空的。最直接的办法就是把空闲的块记成一张表或串成一条链,具体怎么做?
大白话:空闲表像停车场门口的一块黑板,写着“3 号起连着 4 个空位”;空闲链表则是每个空车位里放一张纸条,写着“下一个空位在哪”。
跟我做一遍(用上图的空闲表,首次适应,已用程序验算):要分配 5 块:第 1 项(4 块)、第 2 项(3 块)都不够,第 3 项正好 5 块 → 分配块 15~19,删除这一项。之后回收块 12~14:它紧接在“9 起 3 块”(9~11)后面,合并成“9 起 6 块”;它后面的块 15 已被占用,不再合并。
| 方法 | 额外占用的空间 | 找连续空闲块 | 适合 |
|---|---|---|---|
| 空闲表 | 随空闲区个数变化 | 容易 | 连续分配、空闲区少 |
| 空闲链表 | 指针放在空闲块自己身上;链长随空闲块数变化 | 盘块链难,盘区链较易 | 离散分配 |
| 位示图 | 固定,只与总块数有关(2024-26) | 容易(找连续的 0) | 中小磁盘,位图可常驻内存 |
| 成组链接 | 大部分信息放在空闲块里 | 不考虑连续 | 大型文件系统(UNIX) |
位示图:一位管一块
- A. 81、1
- B. 81、2
- C. 82、1
- D. 82、2
展开解析
考点10.7 位示图
C一个位图盘块有 1024 × 8 = 8192 位,盘块号 ÷ 8192 得到在位图的第几块(从 0 数),余数 ÷ 8 得到块内字节号。
- 409612 ÷ 8192 = 50 余 12 → 位图的第 50 块,盘块号 32 + 50 = 82。
- 块内第 12 位:12 ÷ 8 = 1 余 4 → 第 1 号字节(的第 4 位)。
- A 错盘块 81 是把位图的“第 50 块”按从 1 开始数又减了 1;50 × 8192 = 409600 ≤ 409612,它就在从 0 数的第 50 块,即 32 + 50 = 82。
- B 错盘块号同样少了 1;字节号 2 是把 12 ÷ 8 = 1.5 向上取整,或把字节从 1 开始数——题目明确块内字节从 0 编号。
- C 对盘块 82、字节 1。
- D 错盘块 82 对;但第 12 位落在 8 ~ 15 位,即第 1 号字节(0 号字节是第 0 ~ 7 位),不是 2 号。
位示图定位:块号 = 起始块 + ⌊b ÷ 每块位数⌋,字节号 = ⌊(b mod 每块位数) ÷ 8⌋,都从 0 数。同类:2014-27(10GB、簇 4KB → 2.5M 位 = 320KB → 80 个簇);2023-25(位图管 16GB 内存的 4KB 页框:4M 位 = 512KB);2010-45 第 1 问(2KB 记录 16384 块:16384 位 = 2KB,正好用位示图)。
成组链接法:分配和回收怎么做
UNIX 用的办法:把空闲盘块分组(比如每组 100 块);每一组的第一个空闲块里记录下一组的空闲块数和所有块号;第一组的空闲块数和块号记在内存里的超级块(空闲盘块号栈)中。于是只要超级块在内存,就能不断找到空闲块。
- 分配:从超级块(栈)里取一个块号分配出去,块数减 1。若超级块里只剩最后一个块号——这一块正是记着下一组信息的那块——就先把这块的内容(下一组的块数和块号)读入超级块,再把这一块分配出去。
- 回收:若超级块里的块号还没满(不到 100 个),把回收的块号压进去,块数加 1。若已满,就把超级块当前的内容(100 个块号和块数)写入这个回收的块,然后超级块清空、只记这一块(块数为 1)——回收的块成了新的一组的“记录块”。
好处:空闲块的信息大部分放在空闲块自己身上,不占额外空间;内存里只要一个超级块,分配回收大多数时候不用读盘,适合大型文件系统。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 40
- B. 80
- C. 320
- D. 40K
展开解析
A簇数 = 20GB ÷ 8KB = 2.5M 个,位图 2.5M 位 = 320KB,占 320KB ÷ 8KB = 40 个簇。
变化点:2014-27 是 10GB、4KB 簇(80 簇)。这里容量和簇大小都翻倍,簇的总数不变,位图的位数也不变;但每个簇能放的位数翻倍,所以位图占的簇数减半(80 → 40)。
- A. 75、1
- B. 75、12
- C. 74、1
- D. 25、1
展开解析
A每块位图有 2048 × 8 = 16384 位。409612 ÷ 16384 = 25 余 12,所以在位图的第 25 块,即盘块 50 + 25 = 75;块内第 12 位,在第 12 ÷ 8 = 1 个字节(第 4 位)。
变化点:2015-31 是 1024B 的块、从 32 号块开始(82、1)。步骤:先求在位图的第几块(除以每块位数),加上起始块号;余数再除以 8 得到字节号。D 忘了加起始块号。
- A. 256KB
- B. 512KB
- C. 2MB
- D. 16MB
- A. 2
- B. 15
- C. 25
- D. 9
展开解析
B首次适应从表头找第一个够大的空闲区:(2, 4)、(9, 3) 都不够 5 块,(15, 5) 正好够 → 从 15 开始分配(已用程序验算)。C 是最坏适应(最大的区)的结果。
考点:空闲表法;分配方法与第 8 篇动态分区相同。
10.8文件系统的全局结构、VFS 与挂载
- 虚拟文件系统 VFS:在各种具体文件系统(ext4、NTFS、FAT32、网络文件系统…)之上定义统一的接口(open/read/write…),上层程序不用管底层是哪种(2025-28 选 C);它不是“运行在虚拟内存的文件系统”,也不是为了加速。
- 挂载(mount):把一个文件系统(如 U 盘)接到目录树的某个目录(挂载点)上,之后访问这个目录就是访问那个文件系统。
- 文件保护:口令、加密、访问控制(访问控制表/访问权限位)。2017-30:4 类用户 × 5 种权限,用位串表示每类用户对每种权限的允许与否 → 4×5 = 20 位。
文件保护:访问控制矩阵、访问控制表与权限表
问题:系统里有很多用户、很多文件,每个用户对每个文件能做什么(读、写、执行)都可能不同,怎么记?
大白话:画一张大表,行是用户,列是文件,格子里写权限——这就是访问控制矩阵。这张表太大而且大部分格子是空的,所以实际只存它的一部分:按列存,每个文件带一张“谁能用我”的名单(访问控制表 ACL);按行存,每个用户带一张“我能用哪些文件”的清单(权限表,也叫能力表)。
跟我做一遍:系统有 100 个用户、1000 个文件,完整的访问控制矩阵有 100 × 1000 = 105 格,其中绝大多数是空的。若平均每个文件只有 3 个用户能访问,ACL 一共只需 3 × 1000 = 3000 项。用户分 3 类、权限 3 种时,每个文件只需 3 × 3 = 9 位(同理,2017-30:4 类用户 × 5 种权限 = 20 位)。
真题怎么考:访问权限存放在 FCB(inode)里,不在目录项里(2025-29、2009-30);用位串表示权限要几位(2017-30)。
| 保护方式 | 做法 | 特点 |
|---|---|---|
| 口令 | 用户访问文件时要提供口令,与 FCB 中存的口令比较 | 简单、开销小;但口令存在系统里,不够安全 |
| 加密 | 用密钥对文件编码,读时解码 | 保密性强;编码解码要花时间 |
| 访问控制 | 为每个文件设一张访问控制表 ACL,列出每个用户(或用户组)允许的操作(读、写、执行、追加、删除、列表……);为了精简,常把用户分成拥有者、组、其他三类(UNIX 的 rwxrwxrwx 共 9 位) | 最常用;口令和加密都是防止别人偷看,访问控制还能区分不同用户的不同权限 |
文件系统的层次结构
| 层次(自上而下) | 负责什么 |
|---|---|
| 应用程序 | 通过系统调用(open、read…)使用文件 |
| 逻辑文件系统 | 管理元数据(除文件数据以外的所有结构):目录结构、用 FCB/inode 维护文件结构,负责文件保护(检查权限) |
| 文件组织模块 | 知道文件的逻辑块和物理块,把逻辑块号转换成物理块号(按连续/链接/索引结构);还包括空闲空间管理 |
| 基本文件系统 | 向设备驱动程序发出一般的读写命令(“读第 x 个物理块”),管理内存中的缓冲区和各种缓存 |
| I/O 控制层 | 设备驱动程序和中断处理程序,在内存和磁盘之间真正传送数据(第 9 篇) |
这张表把本篇串了起来:权限、目录在最上层;10.6 的物理结构、10.7 的空闲空间管理在文件组织模块;10.1、10.2 的磁盘访问和调度由最下面的驱动程序完成。
选择题变式:真题换个条件再做一遍
每道题都由一道真题改动一两个条件而来。先做本节的真题,再做这里的变式,最后看“变化点”:改了什么、答案为什么跟着变。
- A. 挂载后,访问挂载点目录就是访问被挂载文件系统的根目录
- B. 挂载会把被挂载的整个文件系统复制到挂载点所在的磁盘上
- C. 一个文件系统只能挂载在根目录下
- D. 挂载前后,被挂载文件系统中文件的访问路径不变
展开解析
A挂载把另一个文件系统接到当前目录树的某个目录(挂载点)上,访问挂载点就进入了那个文件系统的根目录,不复制任何数据。挂载点可以是任意一个目录,挂载后文件的路径以挂载点开头。
变化点:2025-28 考虚拟文件系统(VFS 定义统一接口)。这里考同一节里的挂载。
- A. 权限表(能力表)
- B. 访问控制表(ACL)
- C. 位示图
- D. 文件分配表(FAT)
10.9融合一次 open + read 的全过程
把全书串起来:程序执行 fd = open("/home/a.txt", O_RDONLY); read(fd, buf, 100); 时发生了什么?
- open 陷入内核(系统调用,第 5 篇)。
- 按路径逐级查目录:从根目录的 inode 找到根目录文件的数据块 → 在里面找 “home” 的目录项 → 读 home 的 inode → 读 home 目录的数据块 → 找到 “a.txt” 的 inode 号 → 把 a.txt 的 inode 读入内存。每一步不在缓冲区里就要读磁盘。
- 检查权限;在系统打开文件表和进程打开文件表中各建一项,返回 fd(10.4 节)。
- read(fd, …) 陷入内核:通过 fd 找到表项 → 读写指针 = 0 → 逻辑块号 0 → 查 inode 的直接地址得到物理块号(10.6 节)。
- 查磁盘高速缓存/缓冲区,命中就直接复制;否则:驱动程序把块号换算成柱面/磁头/扇区,请求进入队列,按磁盘调度算法排序(10.1~10.2 节),写磁盘控制器的寄存器启动传送,进程阻塞(第 6 篇)。
- 磁盘控制器用 DMA 把数据送进内存的内核缓冲区,传完发出中断(第 9 篇);中断处理程序唤醒进程。
- 进程被调度后,内核把数据从内核缓冲区复制到用户的 buf(buf 是虚拟地址,写它的时候又要经过页表、TLB、Cache,第 8 篇),读写指针后移,返回用户态。
也可以用内存映射文件(第 8 篇):把文件映射进虚拟地址空间,之后直接读内存数组,缺页时由缺页处理程序去读文件的块——文件 I/O 变成了“缺页 + 页面调入”。
- 磁盘 6000rpm,每道 100 扇区,平均寻道 5ms,读一个扇区平均要多久?
答案
一圈 10ms:5 + 5 + 10/100 = 10.1 ms。 - 磁头在 100 号磁道正向磁道号增大方向移动,请求 55、58、39、18、90、160、150、38、184,SSTF 和 SCAN(到 199)的移动距离?(用工具验证)
答案
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→199→90→58→55→39→38→18 = 99 + 181 = 280。 - 块 4KB、地址项 4B,inode 有 12 个直接地址和一、二、三级间接各 1 个,最大文件约多大?
答案
N = 1024:(12 + 1024 + 1024² + 1024³) × 4KB ≈ 4TB + 4GB + 4MB + 48KB。 - 硬链接和软链接,哪个在目标文件被删除后还能访问到原数据?
答案
硬链接(共享 inode,计数没到 0 就不删数据);软链接只存了路径,目标被删就找不到了。 - 磁盘转速 6000rpm,每道 200 个扇区、每扇区 512B,数据传输率是多少?
答案
100 转/秒 × 200 × 512B = 10240000B/s ≈ 10.24MB/s。 - 隐式链接分配下,FCB 已在内存,读文件的第 10 块(从 0 编号)要读几次盘?若改为 FAT 且 FAT 在内存呢?
答案
隐式链接要从第 0 块沿指针读到第 10 块,共 11 次;FAT 在内存时查表即可得到块号,只读 1 次。 - 为什么打开文件后,读写时不再需要文件名?
答案
open 时已经按名查到了 FCB/inode 并放进内存的打开文件表,返回了 fd;之后用 fd 直接找到表项,避免每次都逐级查目录读盘。 - 成组链接法中,超级块里只剩一个空闲块号时又来了一个分配请求,要做什么?
答案
这个块记录着下一组空闲块的信息,先把它的内容读入超级块,再把这个块分配出去。
10.✎本篇真题闯关(50 题)
本篇各小节挂的真题全部在这里,按小节排好。可以展开后一道道做,也可以用弹窗模式连续做。做题记录(对/错)只保存在你这台电脑的浏览器里,小节标题下的题号会随之变绿或变红。
大题满分训练营:13 类综合题逐类击破
前十篇按知识讲,这一篇按题型讲。2009—2026 年计组和操作系统一共考了 72 道综合题,拆开看只有 13 类。每一类都按同一个顺序教:这类题长什么样 → 做它必须会的几件事(从零讲) → 解题模板 → 一道真题完整示范(连答题卡怎么写都给出) → 常见丢分点 → 本类全部真题。72 道题每一道都配了“满分答案”式的分步解答:先把条件翻译成大白话,再逐问写思路、答题卡写法和易错点。每类后面还有一道自编模拟大题和一张“举一反三”变式表;最后的 11.15 是两套按真实考试格式出的整卷模拟,附评分细则。
11.1先弄清楚:大题怎么考、怎么给分
408 的综合题是第 41~47 题,共 7 道,一般是数据结构 2 道、计组 2 道、操作系统 2 道、计算机网络 1 道。计组两道一般排在 43、44 题,操作系统两道在 45、46 题(2014、2016 年计组是 44、45,操作系统是 46、47;2019 年操作系统在前,是 43、44,计组是 45、46)。分值每年略有不同,2026 年卷面标的是:43 题 10 分、44 题 15 分、45 题 7 分、46 题 8 分——这四道题合计 40 分,差不多是整张卷子的四分之一。
综合题像一套“连环选择题”:出题人先编一个场景(一台机器、一段程序、一个进程),然后在这个场景里问 3~5 个小问题。每个小问题单拿出来,难度和一道选择题差不多。所以大题难的不是知识,而是两件事:① 读懂场景,把一大段文字翻译成你熟悉的量(“页大小 4KB”→“页内偏移 12 位”);② 知道每一问在考哪个知识点,按步骤写出来。这一篇就是专门练这两件事的。
阅卷是怎么给分的
- 按步骤给分。每一问会拆成几个“得分点”(例如:算出每秒中断次数、算出每次中断的开销、算出占比)。最后结果算错了,前面写对的步骤仍然有分;反过来,只写一个结果、不写过程,往往只能拿一部分分,题目里写着“要求给出计算过程”“说明理由”的更是如此。
- 理由要写到“点子”上。“为什么会死循环”这类问题,阅卷看的是关键词:比如“n 是无符号数,n−1 = 232−1,i ≤ n−1 恒成立”。写一大段但没说到关键原因,照样没分。
- PV 题看三样东西:信号量的定义和初值、每个进程的完整代码、同步/互斥关系是否正确。代码能跑但会死锁、或者没有实现题目要求的并发度,都要扣分。
答题卡上的五条规矩
- 先翻译条件,再动笔算。在草稿纸上把题目里的数字全部“翻译”一遍:页 4KB → 偏移 12 位;按字节编址 → 地址单位是字节;主频 500MHz → 每秒 5×108 个周期、周期 2ns。翻译错一个数,后面全错。
- 公式先写成文字或符号,再代数字。例如写“CPU 占比 = 每秒中断次数 × 每次开销 ÷ 每秒总周期数 = 1.25×105 × 100 ÷ 5×108 = 2.5%”。阅卷老师一眼能看到你的思路,数字算错也能给步骤分。
- 地址题一律展开成二进制,在二进制上画竖线分字段,再换回十六进制。十六进制直接心算切字段最容易出错(字段边界往往不在 4 位的整数倍上)。
- 单位和进制写全。十六进制结尾加 H(或写 0x);B(字节)和 b(位)分清;存储容量的 K、M 是 210、220,速率和频率的 K、M 是 103、106。
- 题目条件优先于教材默认。流水线有没有转发、Cache 是直写还是回写、“优先数小的优先”还是“大的优先”、页号给的是十进制还是十六进制——一律以题目为准,并且在答案里写一句“由题意……”。
✗ 容易丢分的写法
(2009-43 第 1 问)
0.5M/4 = 125000,125000×100/500M = 2.5%。
问题:100 从哪来没交代;“125000 是什么”没说;读卷人要自己猜你的思路。
✓ 步骤分拿满的写法
每次中断传送 32 位 = 4B,每秒中断次数 = 0.5MB/s ÷ 4B = 1.25×105 次;
每次中断的开销 = (18 + 2) 条指令 × CPI 5 = 100 个时钟周期;
CPU 用于该外设的时间占比 = 1.25×105 × 100 ÷ (500×106) = 2.5%。
13 类题型 × 18 年分布
下表每一格是那一年考的那道题(题号),点一下直接弹出题目和满分解答。一道题跨两类时两格都标出(虚线框的是“次要归类”)。看一眼就知道:计组的 ②指令/机器代码、⑥虚存+Cache 综合、⑦I/O 性能,操作系统的 ⑧PV、⑩分页、⑪文件系统是年年轮着来的主力。
这一篇怎么用
- 先把对应的知识篇读完(每类开头都写了要先读哪几节)。
- 读“必须会的几件事”和“解题模板”,把模板抄在自己的本子上。
- 跟着“完整示范”走一遍,重点看绿色的“答题卡这样写”——这是考场上真正要写下来的内容。
- 点小节标题下的红色题号,先在纸上自己做完,再点“看解答”对照。做完自评“基本做对了 / 没做出来”,题号会变绿或变红,方便二刷。
- 每一类在“完整示范”后面都有一道自编模拟大题(虚线框),问法照真题出,但换了场景和数字。真题做完再做它,用来检验这一类是不是真的会做了。
11.2计组 ① 数据的表示与运算:看一段 C 程序,算机器数、判溢出
先读:2.1 补码 2.2 类型转换 2.4 标志位 2.5 乘除法 2.6 IEEE 754
- 2.1 定点小数与四种编码的范围 —— “能不能表示 / 范围是多少”
- 2.1 编码互转速算 —— 快速写机器数、求 [−x]补
- 2.2 有符号与无符号混用 —— 2017-43 死循环这类“为什么”
- 2.3 三种码的算术移位 —— 移位后的机器数
- 2.4 溢出判断三法 —— “硬件如何判断溢出”(2011-43(4)、2022-43(1))
- 2.5 原码一位乘与 Booth —— 乘法电路怎么做(2020-43)
- 2.5 加减交替除法 —— 除法器初值与步骤(2025-44)
- 2.6 浮点一般格式、规格化与 2009-13 五步 —— 浮点加减、判溢出
- 2.6 舍入方式 —— float 精度、舍入(2017-43(3)(5))
这类题长什么样
题目给一小段 C 代码,变量有 unsigned、int、short、float 等类型,然后问“执行完之后某个寄存器/变量里是什么”。它有时单独成题(2011-43、2017-43、2020-43、2025-44),更多时候作为别的大题的一两个小问出现。
| 常见问法 | 考过的地方 |
|---|---|
| 某变量/寄存器的机器数是多少(十六进制) | 2011-43(1),2017-43(2),2020-43(4),2021-43(3),2025-44(1) |
| 某变量的值是多少(十进制) | 2011-43(2) |
| 运算是否溢出?硬件怎么判断溢出? | 2011-43(4),2017-43(4),2020-43(4),2021-43(3),2019-45(4)(5),2022-43(1) |
| 为什么死循环 / 为什么结果变成 −1 | 2017-43(1)(4) |
| float 为什么不精确、不溢出/不舍入的最大 n | 2017-43(3)(5) |
| 加减乘除能否共用加法器、乘法怎么用加法和移位实现、哪种乘法最快 | 2011-43(3),2020-43(1)~(3) |
| 除法器初值、除法异常 | 2025-44 |
| 地址计算时偏移量做零扩展还是符号扩展 | 2021-43(4) |
做这类题必须会的 6 件事
① 一个位串,两种读法。计算机里存的只是一串 0 和 1,本身没有正负。C 语言的类型决定怎么读它:unsigned 就按普通二进制读;int 按补码读。最高位是 0 时两种读法结果一样;最高位是 1 时,按有符号读出的值 = 按无符号读出的值 − 2n(n 是位数)。下图是 8 位的情况。
② 快速写出 n 位机器数。正数:直接转二进制,高位补 0 补够 n 位。负数:先算 2n − |x|,再转二进制。例:8 位的 −10 = 256 − 10 = 246 = F6H;16 位的 −1 = FFFFH;32 位的 −2AH = 1 0000 0000H − 2AH = FFFF FFD6H。十六进制要写满位数(32 位的 255 写成 0000 00FFH)。
③ 赋值、类型转换只有三条规则。等长(int ↔ unsigned):位串不变,换个读法。短变长:看原来的类型,原来有符号就高位补符号位(符号扩展),原来无符号就补 0(零扩展)。长变短:直接砍掉高位。另外,有符号数和无符号数一起比较或运算时,按无符号数处理——unsigned 的 0 − 1 是 232 − 1,这就是 2017-43 死循环的原因。
④ 加减法只有一个加法器,按模 2n 算。不管有没有符号,位串的加法都一样,超出 n 位的进位直接丢掉;减法 A − B 就是 A + (B 取反 + 1)。有没有溢出要分开看:
| 无符号数 | 带符号数(补码) | |
|---|---|---|
| 表示范围(n 位) | 0 ~ 2n − 1 | −2n−1 ~ 2n−1 − 1 |
| “结果不对”的标志 | CF(加法有进位 / 减法有借位) | OF(溢出) |
| 考场判断法(最稳) | 用十进制真值算,看是否 < 0 或 ≥ 2n | 用十进制真值算,看是否超出范围 |
| 硬件判断法(问“如何判断”时写) | 加法:Cout = 1;减法:Cout = 0(CF = Cout ⊕ 减法信号) | OF = 最高位进位 ⊕ 次高位进位;或“两个同号数相加,结果符号相反” |
⑤ 乘法:2n 位乘积,只留低 n 位。n 位 × n 位的完整乘积是 2n 位,C 语言只取低 n 位。无符号乘法:高 n 位全为 0 才不溢出;带符号乘法:高 n 位每一位都等于低 n 位的最高位才不溢出。没有乘法器时,乘法 = 循环 n 次“看乘数一位、决定加不加、移位”。整数除法有两种异常:除数为 0;−2n−1 ÷ (−1)(商放不下)。
⑥ float:1 + 8 + 23,有效位 24 位。
值 = (−1)S × 1.M × 2E − 127;E 全 1、M 全 0 表示 ∞;E 的有效范围 1 ~ 254(指数 −126 ~ 127)。
整数转 float 的步骤:写成二进制 → 写成 1.xxx × 2k → E = k + 127 → 把小数点后的位填进 M(不够补 0)。从最高位的 1 到最低位的 1 超过 24 位,就必须舍入,结果不再精确。例:224 − 1(24 个 1)能精确存;225 − 1(25 个 1)会被舍入成 225。
解题模板
- 列变量表。每个变量写:类型、位数、有无符号。8 位机、16 位机的 int 就是 8 位、16 位,别按 32 位算。
- 常量先变成机器数。每个初值写成 n 位二进制,再写成十六进制。
- 一句一句“执行”。运算在位串上做,按模 2n 丢掉进位;赋值按“等长不变、扩展看原类型、截断砍高位”。
- 按目标变量的类型读值。问“值”时,看结果存进了什么类型的变量。
- 判溢出。先看这句运算是有符号还是无符号(看参与运算的变量类型),用真值法判断;题目问“硬件如何判断”,再写 OF/CF 的规则。
- 浮点数算有效位。写成 1.xxx × 2k,数有效位是否超过 24,阶码是否超过 254。
- 写答案。机器数带 H、写满位数;每个“是/否”后面跟一句理由。
假定在一个 8 位字长的计算机中运行如下 C 程序段:
unsigned int x=134;
unsigned int y=246;
int m=x;
int n=y;
unsigned int z1=x-y;
unsigned int z2=x+y;
int k1=m-n;
int k2=m+n;
若编译器编译时将 8 个 8 位寄存器 R1~R8 分别分配给变量 x、y、m、n、z1、z2、k1 和 k2。请回答下列问题。(提示:带符号整数用补码表示)
(1) 执行上述程序段后,寄存器 R1、R5 和 R6 的内容分别是什么(用十六进制表示)?
(2) 执行上述程序段后,变量 m 和 k1 的值分别是多少(用十进制表示)?
(3) 上述程序段涉及带符号整数加/减、无符号整数加/减运算,这四种运算能否利用同一个加法器及辅助电路实现?简述理由。
(4) 计算机内部如何判断带符号整数加/减运算的结果是否发生溢出?上述程序段中,哪些带符号整数运算语句的执行结果会发生溢出?
先在纸上自己做一遍,再点这里展开满分解答
考点2.1 补码2.2 类型转换2.4 加减法与溢出训练营 ①
- “8 位字长”:题里的 unsigned int 和 int 都只有 8 位。无符号能表示 0 ~ 255,有符号(补码)能表示 −128 ~ 127。
- x = 134、y = 246 都比 127 大——这是本题的“机关”:把它们原样交给 int 型的 m、n 时,位串不变,但会被读成负数。
- R1 ~ R8 依次放 x、y、m、n、z1、z2、k1、k2,所以问的 R1 = x、R5 = z1、R6 = z2。
- 寄存器里放的永远是位串;“值”是按变量类型去读这个位串得到的。
先把 x、y 写成 8 位二进制,再在 8 位里做加减:结果超出 8 位就把第 9 位扔掉(相当于对 256 取余)。
x = 134 = 1000 0110B,所以 R1 = 86H;y = 246 = 1111 0110B = F6H。
z1 = x − y:134 − 246 = −112,在 8 位中按模 256 得 −112 + 256 = 144 = 1001 0000B,所以 R5 = 90H。
(用补码加法验证:86H − F6H = 86H + [−F6H]补 = 86H + 0AH = 90H。)
z2 = x + y:86H + F6H = 1 7CH,只保留低 8 位,所以 R6 = 7CH。
z1 是 unsigned,它的“值”是 144 而不是 −112;但本问只问寄存器内容,写位串 90H 即可。结果写成十进制或忘了丢掉第 9 位(写成 17CH)都会丢分。
m = x 是“等长的无符号 → 有符号”,位串原样复制(仍是 86H),只是改成按补码读。k1 = m − n 在位串上和 z1 的运算完全一样,结果位串也是 90H,同样按补码读。
m 的机器数与 x 相同,为 86H,最高位为 1,按补码解释:m = 134 − 256 = −122。
n 的机器数为 F6H,n = 246 − 256 = −10。k1 = m − n = −122 − (−10) = −112(机器数 90H,−112 在 −128 ~ 127 之内,结果正确)。
有符号 8 位数“最高位为 1 时,值 = 无符号读出的值 − 256”,这一条要熟到不用想。
从“位串”的角度看:无符号加法、补码加法都是“两串 8 位二进制相加、丢掉第 9 位”,一模一样;减法都能变成“加上减数的相反数”。不同的只是结果怎么解释、看哪个标志。
能。n 位加法器实现的是模 2n 的加法。无符号整数和带符号整数(补码)的加法在位串层面完全相同;减法 A − B 都可以化为 A + [−B]补,即把 B 各位取反、最低位进位输入为 1 后与 A 相加,只需在加法器的一个输入端加一个受“减法”信号控制的取反电路。两者的区别只在于结果的解释和溢出判断方法不同(无符号看进位/借位 CF,带符号看溢出标志 OF),这些标志由辅助电路同时产生。
先找出“带符号运算”的语句:只有 k1 = m − n 和 k2 = m + n(z1、z2 是无符号运算,不讨论 OF)。判断是否溢出最稳的方法是用十进制真值算一遍,看在不在 −128 ~ 127 之内;然后再用硬件规则验证一次。
判断方法:最高数值位产生的进位 Cn−1 与符号位产生的进位 Cn 不同时溢出,即 OF = Cn ⊕ Cn−1;等价地,两个符号相同的数相加(或符号不同的数相减),结果的符号与被加数(被减数)不同时溢出。
k1 = m − n = −122 − (−10) = −112,在范围内,不溢出。
k2 = m + n = −122 + (−10) = −132 < −128,溢出:86H + F6H = (1)7CH,两个负数相加得到正数 7CH,符号位进位为 1、次高位进位为 0。
所以只有 k2 = m + n 发生溢出。
不要拿 z2 的“最高位进位丢了”说事——那是无符号加法的进位(CF = 1),不是带符号溢出。
- 寄存器里只有位串;同一个位串,按 unsigned 读和按 int 读可以差 2n。
- 加减法永远在 n 位里按模 2n 做,一个加法器搞定有符号和无符号。
- 判溢出:先确定是有符号还是无符号运算,再用真值法算范围,最后写出硬件判断规则。
模拟大题:一段 C 程序把本类问法走一遍(按 408 风格自编)
这一类单独成题的真题有 2011-43、2017-43、2020-43、2025-44。下面这道自编题把最常见的四种问法放进同一段程序:写机器数、类型转换、标志位与溢出、float 编码与舍入。所有结果都用 gcc 实际运行核对过,加法按补码回绕,编译时用了 -fwrapv。
在 32 位计算机上运行下列 C 程序段。int 型占 32 位,short 型占 16 位,带符号整数用补码表示,float 型采用 IEEE 754 单精度格式。
short s = -20000; unsigned short us = s; unsigned int u = us; int i = s; short t = s * 2; int m = 0x7FFFFFF0; int n = m + 0x20; float f = -20000.75; float h = 16777217;
(1) s、us、u、i 的机器数各是多少(用十六进制表示)?us 和 u 的值(十进制)各是多少?u 和 i 都是由 −20000 扩展得到的,为什么它们的高 16 位不同?(3 分)
(2) 执行“t = s * 2”后,t 的机器数和值各是多少?若在一台 16 位机器上用算术左移 1 位来实现 s × 2,硬件怎样判断结果是否溢出?本例是否溢出?(3 分)
(3) 执行“n = m + 0x20”对应的加法指令后,OF、SF、ZF、CF 各是多少?n 的值是多少?若 m、n 都声明为 unsigned int,这次加法的结果是否正确?为什么?(3 分)
(4) f 和 h 的机器数各是多少(用十六进制表示)?h 中实际存储的值是多少?它为什么不等于 16777217?(3 分)
先在纸上自己做一遍,再点这里展开满分解答
考点2.1 写机器数2.2 类型转换2.3 算术移位2.4 溢出判断2.6 IEEE 7542.6 舍入
- 先列变量表:s 是 16 位有符号数,us 是 16 位无符号数,u 是 32 位无符号数,i 是 32 位有符号数,t 是 16 位有符号数,m、n 是 32 位有符号数。
- 赋值只有三条规则:等长赋值位串不变;短变长看原来的类型,有符号就符号扩展,无符号就零扩展;长变短直接截断高位。
- float 是 1 位符号 + 8 位阶码(指数 + 127)+ 23 位尾数,有效位数是 24 位(含隐藏的 1)。
负数的 n 位补码 = 2n − |x|,即 65536 − 20000 = 45536 = B1E0H。然后按赋值规则逐个推。
s = −20000,16 位补码 = 65536 − 20000 = 45536 = B1E0H。
us = s:等长赋值,位串不变,us = B1E0H,按无符号数读值为 45536。
u = us:us 是无符号数,短变长做零扩展,u = 0000B1E0H,值为 45536。
i = s:s 是有符号数,短变长做符号扩展,i = FFFFB1E0H,值仍为 −20000。
u 和 i 的高 16 位不同,是因为扩展方式由原来的类型决定:u 从无符号的 us 扩展,高位补 0;i 从有符号的 s 扩展,高位补符号位 1。
C 语言会先把 s 提升为 int 再乘:得 −40000 = FFFF63C0H,赋给 16 位的 t 时截断高 16 位。
s × 2 = −40000,32 位机器数为 FFFF63C0H。赋给 short 型的 t 时截去高 16 位,t = 63C0H,值为 25536。结果变成了正数,是错误的。
用 16 位算术左移实现:1011 0001 1110 0000 → 0110 0011 1100 0000 = 63C0H。
判断方法:补码算术左移 1 位时,若移位前的符号位与最高数值位不同,也就是移出的位与移位后的符号位不同,结果就溢出。本例移位前最高两位是 1、0,所以溢出。从数值上看,−40000 < −32768,超出了 16 位补码的表示范围。
算术左移的溢出不看“移出的位是不是 1”,而看移位后符号有没有变。例如 FFFFH(−1)左移后得 FFFEH(−2),移出的是 1,却没有溢出。
7FFFFFF0H + 00000020H = 80000010H。
OF = 1:两个正数相加得到负数。也可以用进位判断:最高位的进位 Cn = 0,次高位向最高位的进位 Cn−1 = 1,OF = Cn ⊕ Cn−1 = 1。
SF = 1(结果最高位为 1);ZF = 0(结果不为 0);CF = 0(最高位没有向外进位)。
n 的值:80000010H 按补码读为 −231 + 16 = −2147483632,发生溢出,结果错误。
若 m、n 都是 unsigned int:2147483632 + 32 = 2147483664 = 80000010H,没有超过 232 − 1,CF = 0,所以结果正确。同一个位串、同一次加法,有符号数要看 OF(溢出了),无符号数要看 CF(没溢出)。
硬件做加法时,四个标志位全都会生成,不管变量是什么类型。题目问“是否溢出”时,要按变量的类型选看 OF 还是 CF。
写成二进制 → 规格化成 1.xxx × 2k → 阶码 = k + 127 → 把小数点后的位填入尾数。h 要数一数有效位数有没有超过 24 位。
f:20000.75 = 100 1110 0010 0000.11B = 1.0011 1000 1000 0011B × 214。符号 S = 1,阶码 E = 14 + 127 = 141 = 1000 1101B,尾数 M = 0011 1000 1000 0011 000 0000(共 23 位)。
拼起来:1 10001101 00111000100000110000000 = 1100 0110 1001 1100 0100 0001 1000 0000B = C69C4180H。
h:16777217 = 224 + 1 = 1 0000 0000 0000 0000 0000 0001B(25 位有效位)= 1.000…0001B × 224,小数点后有 24 位。
float 的尾数只有 23 位,最后那个 1 要舍掉。它恰好是“一半”,按默认的就近舍入(恰为一半时取偶数)舍去,尾数全为 0。所以 h 实际存储的值是 224 = 16777216,阶码 E = 24 + 127 = 151 = 1001 0111B,h = 0 10010111 000…0 = 4B800000H。
h ≠ 16777217 的原因:float 的有效位只有 24 位(含隐藏的 1),而 16777217 需要 25 位有效位,无法精确表示。
判断 int 转 float 是否精确,要数从最高位的 1 到最低位的 1 一共几位,超过 24 位就要舍入。224 + 1 是最小的不能被 float 精确表示的正整数。
- 写机器数的三板斧:负数用 2n − |x|;扩展看原类型;截断砍高位。
- 同一次加法,OF 给有符号数看,CF 给无符号数看。
- 算术左移溢出 ⇔ 符号位变了。
- float 看有效位数,超过 24 位就要舍入;恰为一半时就近取偶。
举一反三:条件一变,答案怎么变
以上面的模拟题为基准,每行只改一个条件。先自己想出新答案和理由,再点开核对。
| 条件怎么变 | 新答案和理由 |
|---|---|
| s 改为 20000(正数) | 想好了再点s = us = 4E20H;u = i = 0000 4E20H。正数的符号扩展和零扩展结果相同,u 和 i 的高 16 位都是 0。 |
| s 改为 −16000,再执行 t = s * 2 | 想好了再点−32000 没有超出 16 位补码的范围(≥ −32768),t = 8300H,值为 −32000,不溢出。用算术左移看:C180H 的最高两位是 11,相同,所以不溢出,结果是 8300H。 |
| n = m + 0x20 改为 n = m − 0x20 | 想好了再点n = 7FFF FFD0H。正数减正数不会溢出,OF = 0;按无符号数看够减,CF = 0;SF = 0,ZF = 0。 |
| m 改为 0x80000000,执行 n = m − 1 | 想好了再点n = 7FFF FFFFH。有符号:−231 − 1 超出范围(负数减正数得到正数),OF = 1。无符号:231 − 1 够减,CF = 0。SF = 0。 |
| h 改为 16777218(224 + 2) | 想好了再点从最高位的 1 到最低位的 1 只有 24 位,能精确表示,h = 4B80 0001H。 |
| h 改为 16777219(224 + 3) | 想好了再点需要 25 位有效位,舍掉的最低位恰好是一半,要就近取偶。16777218 的尾数末位是 1,16777220 的尾数末位是 0,所以 h = 16777220 = 4B80 0002H。 |
| float f = 0.1 | 想好了再点0.1 化成二进制是无限循环小数 0.000110011…,只能舍入。实际存的是 3DCC CCCDH ≈ 0.100000001,所以浮点数不能用 == 判断相等。 |
| h 改为 16777219,舍入方式改为向零舍入(截断) | 想好了再点只保留 24 位有效位,多出的直接丢掉:224 + 3 → 224 + 2 = 16777218,h = 4B80 0001H(默认的就近舍入得到 16777220)。四种舍入方式见 2.6(程序核对)。 |
| float f = 2−149 | 想好了再点这是单精度能表示的最小正数,是非规格化数:阶码全 0,尾数只有最低位为 1,机器数 0000 0001H,值 = 2−23 × 2−126。比它还小就只能下溢为 0(2.6 非规格化数)。 |
丢分点清单
- 无符号变量的“值”写成了负数(2011-43 的 z1 是 144,不是 −112)。
- 十六进制没写满位数,或者忘了写 H。
- 把无符号加法的进位(CF)当成溢出(OF),或者反过来。
- “能否共用一个加法器”只答“能”,不给理由(要说出“模 2n 加法相同、减法变加补码”)。
- float 有效位写成 23 位(忘了隐藏的 1);阶码忘了加 127。
- 乘法溢出只看“有没有进位”——要看 2n 位乘积的高 n 位。
- 符号扩展和零扩展用反:看原来的类型,不看目标类型。
本类其他真题(先做再看解答)
- 2017-43:f(n) = 2n+1 − 1 的 int 版和 float 版——死循环、溢出成 −1、float 舍入、∞。本类最经典的一道,必做。
- 2020-43:没有乘法指令怎么做乘法、三种乘法实现谁快、乘法溢出判断。
- 2025-44:补码除法器的初值、除法异常的两种情况、异常响应。
- 夹在别的大题里的:2021-43(3)(4)(16 位减法和乘法是否溢出、偏移量符号扩展)、2019-45(4)(5)(阶乘溢出、imul 的 OF)、2022-43(1)(OF 的逻辑表达式)、2013-44(3)(无符号比较用哪些标志)。
11.3计组 ② 指令格式、寻址方式与机器级代码
先读:3.1 指令格式 3.2 寻址方式 3.3 看懂汇编 3.4 CISC 与 RISC 2.7 大小端
- 3.1 地址码个数、访存次数与寻址范围 —— 指令格式类小问
- 3.1 扩展操作码万能式 —— “最多还能有多少条指令”
- 3.2 十种寻址方式全表 —— 算 EA、访存次数
- 3.2 堆栈 push / pop —— SP 的变化
- 3.3 x86 寄存器与 Intel / AT&T 对照 —— 看懂题目给的汇编
- 3.3 常用指令与条件转移表 —— lea、test、jle / ja 这类细节
- 3.3 过程调用逐步栈变化 —— 栈帧、返回地址、参数位置(2017-44、2019-45)
这类题长什么样
计组大题里出现最多的一类(18 年里有 10 年考)。题目给一张指令格式图或一段带地址和机器码的汇编代码,然后问“能有几条指令、几个寄存器”“这条指令的机器码是什么”“跳到哪里”“是大端还是小端”“这个寄存器里放的是哪个变量”。
| 常见问法 | 考过的地方 |
|---|---|
| 最多多少条指令 / 多少个寄存器 / 多少种操作 | 2010-43(1),2015-44(1),2021-43(2),2022-43(3),2024-43(1) |
| MAR、MDR、IR、ALU、寄存器、存储单元各多少位 | 2010-43(1),2021-43(1),2026-43(1) |
| 按字节还是按字编址 / 数组元素多大 | 2013-44(1),2014-44(1)(2) |
| 写出机器码 / 由机器码说出功能 | 2010-43(3),2015-44(2),2021-43(3),2024-43(5)(6),2024-44(3),2026-43(3) |
| 相对寻址:目标地址、偏移量、跳转范围、推断公式 | 2010-43(2),2013-44(1)(2),2014-44(3),2019-45(3),2023-44(2) |
| 是什么寻址方式 | 2023-44(2)(3) |
| 大端还是小端 / 从内存表读数 | 2019-45(3),2023-44(3),2024-44(2) |
| 下一条指令地址、代码总长度 | 2017-44(2),2019-45(3),2023-44(1) |
| 读汇编:寄存器里是哪个变量、哪条是条件转移、调用几次 | 2014-44(2),2019-45(1)(2),2023-44(3),2024-44(1) |
| RISC 还是 CISC | 2017-44(1) |
| 执行后寄存器/存储单元的内容 / 写一段指令 | 2010-43(3),2026-43(3)(4) |
做这类题必须会的 7 件事
① 位数 → 个数。k 位的字段能区分 2k 种东西:操作码 k 位 → 最多 2k 条指令;寄存器号 r 位 → 最多 2r 个寄存器。几种格式共用同一个操作码字段时,被别的格式占掉的编码要减掉(2021-43:6 位操作码本来 64 种,000000 被 R 型占了,I、J 型只剩 63 种)。扩展操作码也是同一个道理:短操作码留出几个编码当“扩展标志”,剩下的才能直接用。
② 各个寄存器的位数。
| 部件 | 位数等于 | 例 |
|---|---|---|
| MAR、PC | 地址的位数 = log2(存储单元个数),或地址线条数 | 128KB 按字(2B)编址 → 216 个单元 → 16 位 |
| MDR | 一次读写的数据宽度(存储字长 / 数据线宽度) | 数据线 8 位 → MDR 8 位(2021-43) |
| IR | 一条指令的长度 | 16 位定长指令 → 16 位 |
| ALU、通用寄存器 | 机器字长 | 16 位机 → 16 位 |
③ 编址单位决定 PC 怎么加。按字节编址时,下一条指令地址 = 本条地址 + 本条指令的字节数;按字编址(单字长指令)时 + 1。反过来,从“相邻两条指令地址差几”就能判断编址单位:32 位指令地址差 4 → 按字节编址。
④ 寻址方式速查。A 是指令里的形式地址,EA 是真正的操作数地址。
| 寻址方式 | 操作数在哪 | x86 / RISC 里长什么样 |
|---|---|---|
| 立即 | 就在指令里 | mov [..], 0Ah 的 0Ah;addi 的 imm |
| 直接 | EA = A | mov eax, [00422000h] |
| 寄存器 | 在寄存器 R 里 | add eax, ecx |
| 寄存器间接 | EA = (R) | (R4)、[eax]、0(r4) |
| 相对 | EA = (PC) + A,PC 已指向下一条 | jmp、jxx、call、bne 的偏移 |
| 基址 | EA = (基址寄存器) + A | [ebp−8]、lw r5, 0(r4) |
| 变址 | EA = (变址寄存器) × 比例 + A | [ecx+edx*4+00422000h](基址 + 比例变址 + 位移) |
⑤ 相对寻址的三个公式(最常考)。下一条地址 = 本条地址 + 本条长度;目标地址 = 下一条地址 + 偏移 × 单位;偏移 = (目标 − 下一条地址) ÷ 单位,写成补码。“单位”是 1(字节)还是指令长度,由题目公式或反推决定。
⑥ 大端还是小端。在机器码或内存表里找一个值已知的多字节字段(偏移量、立即数、数组首地址),看它的最低字节是不是排在最前面(最低地址):是 → 小端(x86 都是小端);否 → 大端。从内存表读一个 int 时,小端要把 4 个字节倒过来拼。
⑦ 读汇编的套路。不用会写汇编,只要会“认”:
| 看到 | 意思 |
|---|---|
mov、lw/load、sw/store | 传送 / 从内存取 / 存到内存 |
shl、sll、slli x, k | 左移 k 位 = × 2k;数组下标 × 元素大小时常见(左移 2 位 → 元素 4 字节) |
cmp / test + jle/jge/jne/bne… | 比较后按标志条件转移 → if 或循环条件 |
jmp | 无条件转移 |
call / ret | 调用函数(压入返回地址再转移)/ 返回(弹出返回地址) |
push ebp; mov ebp, esp | 函数开头建立栈帧;[ebp+8] 是第一个参数,[ebp−4] 等是局部变量 |
RISC 还是 CISC:指令长度不一(1、2、3、5、11 字节都有)、运算指令能直接访问内存 → CISC;定长指令、只有 load/store 访存 → RISC。
解题模板
- 画格式。把格式图抄下来,每个字段标上位数;题目给的机器码全部写成二进制,按字段竖线切开。
- 数量和位宽。个数 = 2位数(共享编码记得减);MAR/PC 看地址,MDR 看数据线,IR 看指令长,ALU 看字长。
- 地址计算先求“下一条”。下一条 = 本条 + 本条长度;再加偏移(补码、注意单位)。
- 反推类问题用已知的一对地址验算。题目里一般有“某条跳转指令 → 它的目标”,用它反推或验证公式,再用在别的指令上。
- 读汇编先给寄存器起名字。对照 C 代码写出“R2 = i,R3 = A 的首地址……”,地址表达式和 C 的数组地址公式逐项对齐。
- 执行结果画小表。寄存器一列、内存一列,逐条指令改表,最后回答“谁变了、变成什么”。
- 和虚存/Cache 连着考时,把地址切成页号 + 页内偏移(4KB 页 → 去掉最后 3 个十六进制位就是页号),这一部分见 ⑥。
已知 f(n)=n!=n×(n−1)×(n−2)×⋯×2×1 ,计算 f(n) 的 C 语言函数 f1 的源程序(阴影部分)及其在 32 位计算机 M 上的部分机器级代码如下:
int f1(int n) {
1 00401000 55 push ebp
... ... ...
if(n>1)
11 00401018 83 7D 08 01 cmp dword ptr [ebp+8],1
12 0040101C 7E 17 jle f1+35h (00401035)
return n*f1(n-1);
13 0040101E 8B 45 08 mov eax, dword ptr [ebp+8]
14 00401021 83 E8 01 sub eax, 1
15 00401024 50 push eax
16 00401025 E8 D6 FF FF FF call f1 ( 00401000)
... ... ...
19 00401030 0F AF C1 imul eax, ecx
20 00401033 EB 05 jmp f1+3Ah (0040103a)
else return 1;
}
21 00401035 B8 01 00 00 00 mov eax,1
... ... ...
26 00401040 3B EC cmp ebp, esp
... ... ...
30 0040104A C3 ret
其中,机器级代码行包括行号、虚拟地址、机器指令和汇编指令,计算机 M 按字节编址,int 型数据占 32 位。请回答下列问题:
(1) 计算 f(10) 需要调用函数 f1 多少次?执行哪条指令会递归调用 f1?
(2) 上述代码中,哪条指令是条件转移指令?哪几条指令一定会使程序跳转执行?
(3) 根据第 16 行的 call 指令,第 17 行指令的虚拟地址应是多少?已知第 16 行的 call 指令采用相对寻址方式,该指令中的偏移量应是多少(给出计算过程)?已知第 16 行的 call 指令的后 4 字节为偏移量,M 是采用大端方式还是采用小端方式?
(4) f(13)=6227020800,但 f1(13) 的返回值为 1932053504,为什么两者不相等?要使 f1(13) 能返回正确的结果,应如何修改 f1 的源程序?
(5) 第 19 行的 imul 指令(带符号整数乘)的功能是 R[eax]←R[eax]×R[ecx],当乘法器输出的高、低 32 位乘积之间满足什么条件时,溢出标志 OF=1?要使 CPU 在发生溢出时转异常处理,编译器应在 imul 指令后应加一条什么指令?
先在纸上自己做一遍,再点这里展开满分解答
考点3.3 过程调用、转移指令3.2 相对寻址2.7 大小端2.5 乘法溢出训练营 ②
- f1 是递归求阶乘:n > 1 时返回 n × f1(n − 1),否则返回 1。
- 每行代码:行号 | 虚拟地址 | 机器码 | 汇编。机器码有几个字节,指令就多长;下一条指令的地址 = 本条地址 + 本条长度。
- x86 的 call、jmp、jle 用相对寻址:偏移量是相对于“下一条指令的地址”算的(执行时 PC 已经指向下一条)。
- int 是 32 位,最大 231 − 1 = 2147483647。
计算 f(10) 依次调用 f1(10)、f1(9)、……、f1(1),共 10 次(f1(1) 时 n > 1 不成立,直接返回 1,不再调用)。递归调用由第 16 行的 call 指令完成。
条件转移指令:第 12 行 jle(小于等于时转移)。
一定会使程序跳转的指令:第 16 行 call、第 20 行 jmp、第 30 行 ret(调用、无条件转移、返回都一定改变执行顺序)。
ret 也是“一定跳转”(跳回调用处),常被漏掉。
call 在 00401025H,机器码 E8 D6 FF FF FF 共 5 字节 → 第 17 行在 0040102AH。call 的目标是 f1 的入口 00401000H,偏移 = 目标 − 下一条指令地址。偏移量的 4 个字节在机器码里是 D6 FF FF FF,而偏移的值是 FFFF FFD6H:最低字节 D6 排在最前 → 小端。
call 指令地址 00401025H,长 5 字节,第 17 行指令的虚拟地址 = 00401025H + 5 = 0040102AH。
执行 call 时 PC 已指向下一条指令 0040102AH,转移目标为 f1 的首地址 00401000H,偏移量 = 00401000H − 0040102AH = −2AH,32 位补码为 FFFF FFD6H。
机器码中偏移量字段依次为 D6 FF FF FF,低位字节 D6H 存放在低地址,所以 M 采用小端方式。
可以用第 12 行验算公式:jle 在 0040101CH,长 2 字节,偏移 17H,0040101EH + 17H = 00401035H,正是 else 分支第 21 行的地址 ✓。
f(13) = 6227020800 > 231 − 1 = 2147483647,超出了 int 型(32 位)的表示范围,乘法运算发生了溢出,只保留了乘积的低 32 位,所以结果不对。
修改:将 f1 的返回值类型(以及参与乘法的变量)改为 long long(64 位整数)或 double 型。
32 位 × 32 位得到 64 位乘积,结果放回 32 位的 eax。只有当高 32 位只是低 32 位的“符号扩展”时,截断才不丢信息。
当乘积的高 32 位中每一位都与低 32 位的最高位相同(即 64 位乘积的高 33 位全 0 或全 1)时不溢出;否则乘积不能用 32 位表示,OF = 1。
编译器应在 imul 后加一条溢出自陷指令(如 x86 的 INTO),OF = 1 时触发异常,转入操作系统的溢出处理程序。
- 下一条指令地址 = 本条地址 + 机器码字节数;相对偏移 = 目标 − 下一条地址。
- 从机器码看大小端:找一个已知值的多字节字段(偏移、立即数、地址),看低字节是否在前。
- 有符号乘法溢出:高半部分不是低半部分的符号扩展。
模拟大题:一个循环函数的机器级代码(按 408 风格自编)
机器级代码题的真题(2017-44、2019-45、2021-43 等)都给一段反汇编,问 C 语句对应哪几条指令、转移目标怎么算、栈里放了什么、某条指令访存几次。下面这段代码按 gcc(-m32 -O0)的编译结果整理,每条指令的机器码都用汇编器重新核对过,偏移量都是真的,只把起始地址放到了 0040 1000H。不同版本的 gcc 给 s、i 分配的栈位置可能对调,传参也可能改用 push 指令——考场上一律以题目给出的代码为准。
在 IA-32 机器上,C 函数如下(int 型占 4B,小端方式):
static int sum(int a[], int n) {
int s = 0;
for (int i = 0; i < n; i++)
s += a[i];
return s;
}
int g(int *p) { return sum(p, 10); }
反汇编结果如下(Intel 格式,每行依次为:地址、机器码、汇编指令):
sum: 00401000 55 push ebp 00401001 89 E5 mov ebp, esp 00401003 83 EC 10 sub esp, 0x10 00401006 C7 45 FC 00 00 00 00 mov dword ptr [ebp-0x4], 0 0040100D C7 45 F8 00 00 00 00 mov dword ptr [ebp-0x8], 0 00401014 EB 18 jmp 0040102E 00401016 8B 45 F8 mov eax, dword ptr [ebp-0x8] 00401019 8D 14 85 00 00 00 00 lea edx, [eax*4+0x0] 00401020 8B 45 08 mov eax, dword ptr [ebp+0x8] 00401023 01 D0 add eax, edx 00401025 8B 00 mov eax, dword ptr [eax] 00401027 01 45 FC add dword ptr [ebp-0x4], eax 0040102A 83 45 F8 01 add dword ptr [ebp-0x8], 1 0040102E 8B 45 F8 mov eax, dword ptr [ebp-0x8] 00401031 3B 45 0C cmp eax, dword ptr [ebp+0xc] 00401034 7C E0 jl 00401016 00401036 8B 45 FC mov eax, dword ptr [ebp-0x4] 00401039 C9 leave 0040103A C3 ret g: 0040103B 55 push ebp 0040103C 89 E5 mov ebp, esp 0040103E 83 EC 08 sub esp, 0x8 00401041 C7 44 24 04 0A 00 00 00 mov dword ptr [esp+0x4], 0xa 00401049 8B 45 08 mov eax, dword ptr [ebp+0x8] 0040104C 89 04 24 mov dword ptr [esp], eax 0040104F E8 AC FF FF FF call sum 00401054 C9 leave 00401055 C3 ret
(1) 局部变量 s、i 和参数 a、n 分别存放在哪里(用相对 ebp 的形式表示)?“s += a[i]”“i++”“i < n”分别对应哪几条指令?(3 分)
(2) 调用 sum(p, 10) 时,地址为 0040 1034H 的 jl 指令共执行几次?其中几次发生跳转?jl 的机器码是 7C E0,转移目标 0040 1016H 是怎样算出来的?(3 分)
(3) 若把 i 和 n 都声明为 unsigned int,cmp 指令和 jl 指令分别要不要改?若要改,改成什么?(2 分)
(4) call 指令的机器码是 E8 AC FF FF FF,它的转移目标是怎样算出来的?执行 call 时 CPU 做了哪两件事?设 sum 执行完“mov ebp, esp”后 ebp = 0019 FF00H,写出返回地址、g 的 ebp 旧值、参数 n 所在单元的地址,以及执行完“sub esp, 0x10”后 esp 的值。(2 分)
(5) 地址为 0040 1027H 的“add dword ptr [ebp-0x4], eax”执行时(不含取指令)要访问主存几次?地址为 0040 1019H 的 lea 指令呢?从本段代码看,IA-32 属于 CISC 还是 RISC?举出两个依据。(2 分)
先在纸上自己做一遍,再点这里展开满分解答
考点3.3 x86 汇编3.3 过程调用与栈帧3.2 寻址方式全表3.4 CISC 与 RISC
- 栈帧的固定格局:[ebp] 是调用者 ebp 的旧值,[ebp+4] 是返回地址,[ebp+8] 是第 1 个参数,[ebp+0xC] 是第 2 个参数,[ebp−4]、[ebp−8]…… 是局部变量。
- 相对转移:目标地址 = 下一条指令的地址 + 偏移量(偏移量是补码,机器码按小端存放)。
- -O0 下 for 循环的典型形状:先 jmp 到条件判断处;循环体在上面,条件判断在下面;条件成立就往回跳。
s 在 [ebp−4],i 在 [ebp−8](从两条赋 0 的 mov 可以看出),a 在 [ebp+8],n 在 [ebp+0xC]。
“s += a[i]”对应 0040 1016H ~ 0040 1027H 的 6 条指令:取 i,算 i × 4,取 a,相加得 &a[i],读 a[i],加到 s 上。
“i++”对应 0040 102AH 的 add dword ptr [ebp−8], 1。
“i < n”对应 0040 102EH ~ 0040 1034H 的 mov、cmp、jl 三条指令。
n = 10,i 从 0 取到 10,每次都要判断一次,所以 jl 共执行 11 次。i = 0 ~ 9 时条件成立,跳转 10 次;i = 10 时不跳,顺序执行 0040 1036H。
jl 是相对转移,偏移量 E0H 是 8 位补码,值为 −20H(−32)。下一条指令地址 = 0040 1034H + 2 = 0040 1036H,目标地址 = 0040 1036H − 20H = 0040 1016H。
偏移量相对的是“下一条指令的地址”(执行 jl 时 PC 已经指向下一条),不是 jl 自己的地址。同样,0040 1014H 的 jmp(EB 18):0040 1016H + 18H = 0040 102EH。
cmp 不用改:比较就是做减法并设置标志位,有符号数和无符号数的减法电路相同。
jl 要改为 jb(低于则转移)。jl 按有符号数比较,根据 SF ⊕ OF = 1 转移;jb 按无符号数比较,根据 CF = 1 转移。
偏移量按小端读作 FFFF FFACH,即 −54H(−84)。下一条指令地址 = 0040 104FH + 5 = 0040 1054H,目标 = 0040 1054H − 54H = 0040 1000H,即 sum 的入口。
执行 call 时,CPU 先把返回地址 0040 1054H 压栈,再把 EIP 改为 0040 1000H。
ebp = 0019 FF00H 时:g 的 ebp 旧值在 [ebp] = 0019 FF00H 单元;返回地址 0040 1054H 在 0019 FF04H;参数 n(= 10)在 [ebp+0xC] = 0019 FF0CH。执行完 sub esp, 0x10 后,esp = 0019 FF00H − 10H = 0019 FEF0H。
g 里的两条 mov 把 10 放到 [esp+4]、把 p 放到 [esp]:参数按从右到左的顺序放,第 1 个参数离返回地址最近。
add dword ptr [ebp−4], eax 的目的操作数在主存中:先读 [ebp−4],相加后再写回 [ebp−4],共访存 2 次。
lea 只计算地址 eax × 4 + 0 并送入 edx,不访存(0 次)。
IA-32 属于 CISC。依据:① 指令长度不固定,本段中有 1、2、3、4、5、7、8 字节的指令;② 运算指令可以直接对主存操作数运算(如 add [ebp−4], eax),不只有 load/store 能访存;③ 寻址方式复杂,如比例变址 [eax*4+0x0]。
判断访存次数时只看“数据”:取指令本身也访存,但题目一般说明“不含取指令”。mov eax, [eax] 访存 1 次,lea edx, [eax*4] 不访存。
- 看 -O0 代码先认栈帧:ebp 往上是参数和返回地址,往下是局部变量。
- 相对转移一律用“下一条指令地址 + 补码偏移”算,机器码按小端读。
- cmp 与有无符号无关,区分有无符号靠条件转移:jl/jg 看 SF、OF,jb/ja 看 CF。
举一反三:条件一变,答案怎么变
以上面的模拟题为基准,每行只改一个条件。先自己想出新答案和理由,再点开核对。
| 条件怎么变 | 新答案和理由 |
|---|---|
| 调用 sum(p, 0) | 想好了再点开头的 jmp 直接跳到条件判断处,jl 执行 1 次、不跳转,循环体一次也不执行。 |
| 循环体里多了 10 字节的指令,jl 仍紧跟在循环体后面 | 想好了再点开头的 jmp 要多往前跳 10 字节:18H + 0AH = 22H。jl 要多往回跳 10 字节:E0H − 0AH = D6H(−42)。 |
| for 循环改成 do-while 循环 | 想好了再点开头没有那条 jmp,先执行一次循环体,再判断条件是否往回跳。 |
| 问 call 指令中偏移量字段的值 | 想好了再点FFFF FFACH,即 −84。机器码中按小端存放,字节顺序是 AC FF FF FF。 |
| sum 放在 g 的后面 | 想好了再点转移目标在 call 的下一条指令之后,call 的偏移量变成正数。计算方法不变:目标 = 下一条指令地址 + 偏移量。 |
| 用 -O1 优化编译 | 想好了再点局部变量都放在寄存器里,不再访问栈;循环改成用指针遍历数组,条件判断变成 cmp eax, ecx 和 jne,访存次数大大减少。 |
丢分点清单
- 相对寻址的偏移从“本条指令地址”算——应该从下一条指令地址算(除非题目公式另有规定)。
- 机器码字节数数错,导致下一条地址全错(一个字节 = 两个十六进制数字)。
- 按字编址和按字节编址混用:按字编址时自增 1、PC + 1。
- 负的偏移没有写成补码,或写成 8 位/16 位补码但题目要 32 位。
- 代码总长度忘了加上最后一条指令的长度。
- 共用操作码字段时忘了减去被占用的编码。
- MDR 按字长算,但题目给了数据线宽度(按数据线算)。
- 读小端内存时忘了倒序,或者数错了内存表的列。
本类其他真题(先做再看解答)
- 2010-43:格式图 → 指令条数、寄存器数、MAR/MDR;相对寻址范围;写机器码并执行(寄存器间接自增)。
- 2013-44:条件转移指令,检测位 C/Z/N;目标地址 (PC) + 2 + 2 × OFFSET;无符号比较用哪些标志。
- 2014-44:一段循环的机器码:编址单位、元素大小、由地址反推 bne 的偏移公式、流水线阻塞。
- 2017-44:x86 代码:CISC 判断、代码长度、cmp 后的 CF、float 能不能用 shl。
- 2021-43:R/I/J 三种格式:ALU/IR/MAR/MDR 位数、操作种类(63)、解码 01B2H 并判溢出、符号扩展。
- 2023-44:x86 代码:下一条地址、相对寻址计算、立即寻址、ecx 是什么、小端、取指是否缺页。
- 2024-43:RISC-V 风格的 add/slli/lw:寄存器数、标志位、Ext 与 ALUctr、解码 lw 并算地址。
- 2024-44:sum += a[i] 的 4 条指令:寄存器分配、小端内存表读数、页号、写 slli 的机器码。
- 2026-43:R/I/M 三种 16 位格式:操作码能否重码、执行一条加法、自己写出 y = 16x − 5 的 4 条指令。
11.4计组 ③ 数据通路与控制信号:一拍一拍写微操作
先读:1.3 一条指令怎么执行 4.1 几种周期 4.2 数据通路 4.3 控制器
- 4.1 四个机器周期的数据流 —— 取指 / 间址 / 中断周期写法
- 4.2 单总线四类基本操作与排节拍三规矩 —— 一拍一拍写控制信号
- 4.2 单周期专用数据通路与控制信号表 —— 2024-43、2026-44 这类多路选择器取值
- 4.3 硬布线控制器设计步骤 —— 控制器概念小问
- 4.3 微程序控制器组成与综合例 —— 微指令字段位数、控存容量
这类题长什么样
题目画一张 CPU 内部的连线图(数据通路),图上每根虚线是一个控制信号。然后让你:按节拍写出某条指令要做的事和要发的控制信号;解释某个部件为什么存在;补连线;算控制信号的位数。看上去图很吓人,其实每一问都只用到几条固定规则。
| 常见问法 | 考过的地方 |
|---|---|
| 按节拍写出执行阶段的功能和控制信号 | 2009-44,2015-44(3)(4),2022-43(4) |
| 多路选择器式通路:控制信号取几 | 2024-43(2)(3)(4),2026-44(3)(4) |
| 为什么要设暂存器(T / Y / Z)、哪些寄存器程序员可见 | 2015-43(1),2022-43(2) |
| 控制信号至少几位 | 2015-43(2),2026-44(2) |
| 部件名称、补连线、哪些端点接控制部件 | 2013-44(4),2015-43(3)~(6),2026-44(1) |
| 寄存器编号接译码器还是多路选择器、控制部件的输入是什么 | 2022-43(3)(5) |
| 标志位的逻辑表达式 | 2022-43(1) |
做这类题必须会的 6 件事
① 认识图上的“角色”。
| 部件 | 干什么 | 程序员能看见吗 |
|---|---|---|
| PC | 下一条指令的地址 | 能 |
| IR | 正在执行的指令;操作码送控制器,寄存器号、立即数等字段送通路 | 不能 |
| MAR / MDR | 访存时放地址 / 放数据,是 CPU 和主存之间的“收发室” | 不能 |
| 通用寄存器 R0 ~ Rn | 放操作数和结果 | 能 |
| 暂存器(Y、Z、T、A、AC) | 单总线时给 ALU 临时存输入或输出 | 不能 |
| 标志寄存器(PSW / FR) | 存 ZF、SF、CF、OF | 能(条件转移要用) |
| 多路选择器 MUX | 多个输入选一个输出,选择信号 = 输入端编号 | — |
| 三态门 | 控制一个部件的输出接不接到总线上 | — |
| 译码器 | 把 k 位编号变成 2k 根线中的一根(写寄存器时选中目标) | — |
| 扩展器 | 把指令里的短立即数/偏移扩成字长(零扩展或符号扩展) | — |
② 控制信号的名字就是“开哪个门”。Xin:总线上的数打入 X;Xout:X 的内容送上总线;MemR / Read:读主存;MemW / Write:写主存;Add、ALUop = …:ALU 做什么运算。写“某件事”的信号,就是“源 out,目的 in”。
③ 单总线的三条规则。一条总线像一条单车道,每个时钟周期只能过一辆车:
④ 取指令三拍,几乎每题都一样。
| 节拍 | 功能 | 控制信号 |
|---|---|---|
| 1 | MAR ← (PC) | PCout,MARin |
| 2 | MDR ← M(MAR);PC ← (PC) + 1(或 + 2、+ 4) | MemR(Read),MDRinE;PC+1 |
| 3 | IR ← (MDR) | MDRout,IRin |
PC 增量有两种做法:PC 自己带计数功能(信号 PC+1);或者借 ALU 算 (PC) + 常数(2015-43 的 MUX 输入端 2、2026-44 的 ALUBSrc 选常数 2),这时 PC 增量会占用 ALU 和总线,要单独排节拍。
⑤ 访存固定两拍。读:MAR ← 地址(Xout, MARin);MDR ← M(MAR)(MemR, MDRinE)。写:MDR ← 数据(Xout, MDRin);M(MAR) ← (MDR)(MemW, MDRoutE)。只要中间没改过 MAR,写回同一地址时不用再送一次地址。
⑥ 控制器从哪来、控制信号几位。控制信号都由控制器 CU 产生,CU 的输入是:IR 中的操作码(做什么)、标志寄存器(条件是否满足)、时钟和节拍(什么时候做)。一个部件有 n 种操作,控制它的信号至少 ⌈log2n⌉ 位。微程序控制器里,同一字段内互斥的 n 个信号编码成 ⌈log2(n + 1)⌉ 位(多出的 1 个编码表示“一个都不发”)——这一点选择题常考(详见 4.3)。
解题模板
- 读图。找出总线、每个寄存器的 in/out 信号、暂存器接在 ALU 哪一端、每个 MUX 各输入端的编号、MDR 连内总线和外部总线的信号名各是什么。
- 把指令拆成几件事。一般是:取操作数(要访存的先送地址、再读)→ 运算 → 送结果(写寄存器,或写回主存)。先确认结果写到哪里:“(R1)”是寄存器 R1,“((R1))”是 R1 指向的主存单元。
- 每件事写成微操作。“目的 ← 源”,旁边写信号“源out,目的in”,运算再加 ALU 的操作信号。
- 排节拍。单总线每拍一个 out;访存按题目给的周期数;读主存时总线空闲,可以顺手做一次寄存器传送。
- 检查。MDRinE 和 MDRin、MDRout 和 MDRoutE 有没有用对;ALU 的一端是否已经在暂存器里;结果是否先进了 Z/AC。
- 按题目的表格格式写。节拍 | 功能 | 有效控制信号,一行一拍。
某计算机字长 16 位,采用 16 位定长指令字结构,部分数据通路结构如下图所示,图中所有控制信号为 1 时表示有效、为 0 时表示无效。例如控制信号 MDRinE 为 1 表示允许数据从 DB 打入 MDR,MDRin 为 1 表示允许数据从内总线打入 MDR。假设 MAR 的输出一直处于使能状态。加法指令“ADD (R1), R0”的功能为 (R0) + ((R1)) → (R1),即将 R0 中的数据与 R1 的内容所指主存单元的数据相加,并将结果送入 R1 的内容所指主存单元中保存。
下表给出了上述指令取指和译码阶段每个节拍(时钟周期)的功能和有效控制信号,请按表中描述方式用表格列出指令执行阶段每个节拍的功能和有效控制信号。
| 时钟 | 功能 | 有效控制信号 |
|---|---|---|
| C1 | MAR ← (PC) | PCout, MARin |
| C2 | MDR ← M(MAR),PC ← (PC) + 1 | MemR, MDRinE, PC+1 |
| C3 | IR ← (MDR) | MDRout, IRin |
| C4 | 指令译码 | 无 |
先在纸上自己做一遍,再点这里展开满分解答
- 单总线:CPU 内部只有一条内总线,每个节拍总线上只能有一个部件往外送数(一个 out)。
- ALU 有两个输入:一个来自暂存器 A,另一个直接来自内总线;结果先打入 AC,再由 AC 送回总线。
- MDR 有两个入口:MDRinE 从外部数据总线 DB 打入(读内存时用),MDRin 从内总线打入(写内存前用);出口也有两个:MDRout 送内总线,MDRoutE 送 DB(写内存时用)。
- “ADD (R1), R0”:(R0) + ((R1)) → (R1),即 结果写回 R1 所指的主存单元,不是写进 R1。
先把指令拆成三件事:① 以 (R1) 为地址读出操作数;② 与 (R0) 相加;③ 把和写回同一个地址。再按“每拍总线上只有一个 out”排节拍:ALU 的一个输入要提前放进 A,结果要先进 AC。
| 时钟 | 功能 | 有效控制信号 |
|---|---|---|
| C5 | MAR ← (R1) | R1out,MARin |
| C6 | MDR ← M(MAR) | MemR,MDRinE |
| C7 | A ← (R0) | R0out,Ain |
| C8 | AC ← (MDR) + (A) | MDRout,Add,ACin |
| C9 | MDR ← (AC) | ACout,MDRin |
| C10 | M(MAR) ← (MDR) | MDRoutE,MemW |
① 写回时 MAR 中仍是 (R1)(中间没有改过 MAR),不必再送一次地址。② 从 DB 进 MDR 用 MDRinE,从内总线进 MDR 用 MDRin,出去同理——这两对信号不能写混。③ C6 读主存时内总线空闲,也可以把 C7 的 A ← (R0) 并到 C6 里做,少一个节拍,同样正确。
- 写控制信号的格式:“目的 ← 源”对应“源out,目的in”。
- 读内存两拍:送地址(MARin)、读(MemR + MDRinE);写内存两拍:数据进 MDR、写(MemW + MDRoutE)。
- 单总线:每拍一个 out;ALU 一端先进暂存器,结果先进 AC/Z。
模拟大题:自增寻址、条件转移与微指令格式(按 408 风格自编)
数据通路题的真题大多问一条“运算 + 访存”指令的执行节拍(2009-44、2015-44、2022-43)。下面这道题用本节上面“单总线 CPU 的三条规则”图里那种单总线 CPU,考三件真题问得少、但最能检验是否真懂的事:节拍数的下界怎么论证、条件转移指令怎样由标志位控制、微指令每个字段几位。
某 16 位计算机的 CPU 采用单总线结构,内部有 PC、IR、MAR、MDR、通用寄存器 R0~R3、暂存器 Y 和 Z。
- ALU:A 端固定接 Y,B 端接内总线,结果送 Z。ALU 能做 Add(A + B)、Sub(A − B)、Inc(B + 1)、And、Or 五种运算。
- 主存:按字编址,读、写各用 1 个节拍。MDR 与内总线之间的控制信号为 MDRin / MDRout,与外部数据总线之间的为 MDRinE / MDRoutE。
- IR:指令中的 8 位偏移量 disp 经符号扩展后,可由信号 IRout 送上内总线。
- 取指:取指阶段需 3 个节拍,并已由 PC 自带的计数功能完成 PC + 1。
(1) 指令“ADD R0, (R1)+”的功能是:((R1)) + (R0) → R0,然后 (R1) + 1 → R1。按“节拍 | 功能 | 有效控制信号”的格式写出它的执行阶段。(4 分)
(2) 这条指令的执行阶段最少需要几个节拍?说明理由,并写出节拍最少的方案。(3 分)
(3) 指令“BZ disp”的功能是:若 ZF = 1,则 (PC) + disp → PC,否则什么也不做。写出 ZF = 1 时执行阶段的微操作和控制信号。分别说明硬布线控制器和微程序控制器是怎样根据 ZF 决定是否执行这些微操作的。(2 分)
(4) 采用字段直接编码的水平型微指令,控制字段分组如下:内总线输出信号 8 个(PCout、IRout、MDRout、Zout、R0out~R3out),任一时刻最多一个有效;寄存器输入信号 10 个(PCin、IRin、MARin、MDRin、Yin、Zin、R0in~R3in),可以同时有效;ALU 操作 5 个,互斥;主存读、写 2 个,互斥;PC+1 信号 1 个。顺序控制部分:控存共 100 条微指令,用断定法给出下址;条件测试有“无条件、测 ZF、按操作码转移”3 种。问微指令至少多少位?(3 分)
先在纸上自己做一遍,再点这里展开满分解答
考点4.2 单总线四类基本操作4.3 硬布线控制器4.3 微程序控制器训练营 ③ 单总线三条规则
- 单总线:每拍只能有一个 out,in 可以有多个;ALU 的一端必须先放进 Y,结果先进 Z。
- 读主存的那一拍用的是外部总线(MemR、MDRinE),内总线空着,可以顺便做一次寄存器传送。
- (R1)+ 是自增寻址:先用 R1 的内容作地址取操作数,再把 R1 加 1(按字编址,加 1 就是下一个字)。
| 节拍 | 功能 | 有效控制信号 |
|---|---|---|
| C1 | MAR ← (R1) | R1out,MARin |
| C2 | MDR ← M(MAR);Y ← (R0) | MemR,MDRinE;R0out,Yin |
| C3 | Z ← (Y) + (MDR) | MDRout,Add,Zin |
| C4 | R0 ← (Z) | Zout,R0in |
| C5 | Z ← (R1) + 1 | R1out,Inc,Zin |
| C6 | R1 ← (Z) | Zout,R1in |
C2 里读主存用的是外部数据总线,内总线空闲,所以能同时做 R0out、Yin。不要写成 MDRin,从外部总线进 MDR 的信号是 MDRinE。
单总线每拍只能有一个 out,所以“至少要用几次内总线”就是节拍数的下界。数一数哪些数据必须经过内总线。
必须经过内总线的传送有 5 次:① R1 送出(给 MAR,同时可作为 ALU 的 B 输入算 R1 + 1);② R0 送 Y;③ MDR 送 ALU;④ Z 送 R0;⑤ Z 送 R1。每拍只能有一个 out,所以至少 5 个节拍。
| 节拍 | 功能 | 有效控制信号 |
|---|---|---|
| C1 | MAR ← (R1);Z ← (R1) + 1 | R1out,MARin,Inc,Zin |
| C2 | MDR ← M(MAR);R1 ← (Z) | MemR,MDRinE;Zout,R1in |
| C3 | Y ← (R0) | R0out,Yin |
| C4 | Z ← (Y) + (MDR) | MDRout,Add,Zin |
| C5 | R0 ← (Z) | Zout,R0in |
C1 中 R1 的内容只上一次总线,同时送进 MAR 和 ALU(一个 out,多个 in,不冲突);C2 利用读主存时内总线空闲,把 R1 + 1 写回。
C2 之后 Z 被 R1 + 1 用掉了,所以加法要在 C4 重新写 Zin;只要不在同一拍出现两个 out、不在用完 Z 之前覆盖它,顺序可以灵活安排。
| 节拍 | 功能 | 有效控制信号 |
|---|---|---|
| C1 | Y ← (PC) | PCout,Yin |
| C2 | Z ← (Y) + disp | IRout,Add,Zin |
| C3 | PC ← (Z) | Zout,PCin |
取指时 PC 已经加 1,所以转移目标 = 本指令地址 + 1 + disp。ZF = 0 时执行阶段不做任何操作,直接去取下一条指令。
硬布线控制器:ZF 是组合逻辑电路的一个输入,这几个控制信号的逻辑表达式中都含有 ZF 因子,例如 PCin = … + BZ · C3 · ZF。ZF = 0 时这些信号不会有效。
微程序控制器:按操作码转到 BZ 的微程序时,先用一条条件测试字段为“测 ZF”的微指令做判断(也可以把这个测试放在前一条微指令里)。ZF = 1 时转去执行上面三拍对应的微指令,ZF = 0 时转回取指微程序。
互斥的一组信号编码成一个字段:n 个信号要 ⌈log2(n + 1)⌉ 位,“+1”留给“都不发”。可以同时有效的信号不能编码,只能每个 1 位(直接控制)。
总线输出:8 个互斥,⌈log29⌉ = 4 位。寄存器输入:10 个可以同时有效,直接控制,10 位。ALU:5 个互斥,⌈log26⌉ = 3 位。主存读写:2 个互斥,⌈log23⌉ = 2 位。PC+1:1 位。
下址字段:100 条微指令,⌈log2100⌉ = 7 位。条件测试字段:3 种,⌈log23⌉ = 2 位。
微指令至少 4 + 10 + 3 + 2 + 1 + 7 + 2 = 29 位。
编码时一定要给“都不发”留一个码,所以 8 个信号是 ⌈log29⌉ = 4 位,不是 3 位。寄存器输入信号能同时有效,所以不能编进一个字段。
- 单总线下,节拍数的下界 = 必须经过内总线的传送次数。
- 读主存的那一拍内总线空闲,可以顺手做一次寄存器传送。
- 条件转移的“条件”,在硬布线中是逻辑表达式里的一个因子,在微程序中是微指令里的一个条件测试字段。
举一反三:条件一变,答案怎么变
以上面的模拟题为基准,每行只改一个条件。先自己想出新答案和理由,再点开核对。
| 条件怎么变 | 新答案和理由 |
|---|---|
| ADD R0, (R1)(没有自增) | 想好了再点最少 4 拍:C1 R1out, MARin;C2 MemR, MDRinE, R0out, Yin;C3 MDRout, Add, Zin;C4 Zout, R0in。 |
| ADD (R1), R0(结果写回 R1 指向的主存单元) | 想好了再点共 5 拍:C1 R1out, MARin;C2 MemR, MDRinE, R0out, Yin;C3 MDRout, Add, Zin;C4 Zout, MDRin;C5 MemW, MDRoutE。MAR 没有改过,写回时不用再送一次地址。 |
| PC 没有自增功能,要借 ALU 算 PC + 1 | 想好了再点取指仍可以 3 拍:C1 PCout, MARin, Inc, Zin;C2 MemR, MDRinE, Zout, PCin;C3 MDRout, IRin。读主存那一拍的内总线正好用来写回 PC。 |
| 改成三总线或多路选择器式的通路 | 想好了再点不再受“每拍只能一个 out”的限制,一拍里可以同时做几件不冲突的事,节拍数可以减少,但连线和多路选择器更多。 |
| 内总线输出信号改为 7 个 | 想好了再点⌈log28⌉ = 3 位,比原来少 1 位,微指令为 28 位。 |
| 控存改为 200 条微指令 | 想好了再点下址字段要 ⌈log2200⌉ = 8 位。 |
丢分点清单
- 一个节拍里写了两个 out(单总线冲突)。
- ALU 两个输入都“从总线来”,忘了先送暂存器。
- 把 ((R1)) 的结果写进了 R1,而不是写回 R1 指向的主存单元。
- MDR 的内/外两对信号用混(从外部总线进是 MDRinE,从内总线进是 MDRin)。
- 多路选择器的取值写成信号名而不是输入端编号。
- “程序员可见”把 MAR、MDR、IR 也算进去。
- 控制信号位数按操作种数直接写(7 种操作写 7 位),忘了编码只需 ⌈log27⌉ = 3 位。
本类其他真题(先做再看解答)
- 2015-43:单总线 + 移位寄存器 SR:程序员可见寄存器、暂存器 T 的作用、ALUop/SRop 位数、三态门、补连线、MUX 为什么有常数 2。
- 2015-44:接上题:指令编码、按样板填 ① ~ ⑧ 的控制信号、数执行阶段的节拍数。
- 2022-43:单总线 + Y、Z:SF/OF 的逻辑表达式、暂存器作用、rd 接译码器、取指至少几拍、控制器的输入。
- 2026-44:多路选择器式通路:部件名称、ExtOp 位数、取指周期和左移指令的控制信号取值。
- 2024-43(2)~(4):ALUBsrc、Ext、ALUctr 的取值(多路选择器式)。
11.5计组 ④ 指令流水线:画时空图、找冒险、数阻塞
先读:4.4 指令流水线 4.1 几种周期 1.8 性能指标
- 4.4 三种冒险的完整对策 —— “如何解决”类小问
- 4.4 三种约定下数阻塞 —— 先确认题目约定再画图
- 4.4 效率与时空图 —— 吞吐率、加速比、效率
- 4.4 有阻塞时的实际 CPI —— 阻塞对执行时间的影响
这类题长什么样
流水线整道大题只考过一次(2012-44),但作为大题的一问(2014-44(4))和选择题(几乎每两年一道)出现得很频繁。题型非常固定:算时间/吞吐率、找数据冒险、按题目约定画时空图数周期、说出阻塞原因和解决办法。
| 常见问法 | 考过的地方 |
|---|---|
| 无阻塞时 n 条指令要几个周期 / 吞吐率 / 时钟周期取多长 | 2012-44(2),2009-18,2013-18,2018-20 |
| 哪些指令之间有数据冒险 / 哪些会阻塞 | 2014-44(4),2016-19,2019-18,2023-19 |
| 画出执行过程,数总周期 | 2012-44(4) |
| 某条指令为什么阻塞 / 为什么不阻塞 | 2012-44(3),2014-44(4) |
| 哪条指令引起控制冒险 | 2014-44(4) |
| 冒险的解决办法、什么有利于流水线、超标量 | 2010-19,2011-18,2014-16,2017-17,2024-19 |
做这类题必须会的 5 件事
① 五个段各干什么、用什么部件。IF 用 PC 和指令存储器取指令;ID 译码并读寄存器;EX 用 ALU 运算或算地址;M 访问数据存储器(只有 load/store 真正用);WB 把结果写回寄存器(store 和转移指令没有要写的)。记住“ID 读、WB 写”,数据冒险就全靠这两个位置判断。
② 三个公式。k 段流水线、n 条指令、时钟周期 Δt,无阻塞时:
③ 三种冒险。
| 冒险 | 大白话 | 怎么办 |
|---|---|---|
| 结构冒险 | 两条指令同一拍要用同一个部件(典型:一条在 M 段访存,另一条在 IF 取指,都要用存储器) | 后一条等;指令 Cache 和数据 Cache 分开(2014-16) |
| 数据冒险 | 后一条要读的寄存器,前一条还没写回(写后读 RAW) | 阻塞(插气泡);转发/旁路;编译器调整顺序或插 nop |
| 控制冒险 | 遇到转移指令,要等几拍才知道下一条从哪取 | 阻塞;分支预测;延迟转移 |
④ 阻塞几拍,全看题目约定。同样是“I1 写 R1、I2 紧接着读 R1”,约定不同,结果不同:
⑤ 按序发射的连锁反应。前一条卡在 ID,后一条就只能卡在 IF(IF 段被占着,再后面的也进不来)。所以画图时,每条指令的 IF 最早排在“前一条的 ID 那一拍”。
超标量 = 同时开好几条流水线,一个时钟周期能发射多条指令(每段的时间并没有变短,2017-17);动态流水线 = 允许后面不相关的指令“插队”先执行,等前面卡住的指令数据到齐再继续,结果再按原顺序提交。408 只考概念,知道“超标量靠多发射提高并行度、不缩短单段时间”就够了。
解题模板
- 抄约定。在草稿纸上写下:有没有转发?同一拍能否先写后读?分支阻塞几拍?是不是按序发射?
- 列“读写表”。每条指令一行:写哪个寄存器、读哪些寄存器(store 读它要存的寄存器;load 的地址寄存器也是读)。
- 连依赖。对每条指令,找它读的寄存器是前面哪一条最后写的(只看写后读)。
- 从上往下排时空图。每条指令:IF = max(上一条 IF + 1, 上一条 ID);ID = max(IF + 1, 所依赖指令的 WB + 1(按约定 ①));之后 EX、M、WB 各 + 1。
- 数周期、写原因。总周期 = 最后一条指令 WB 所在的拍。解释阻塞时写清“与哪条指令有什么相关、为什么要等”。
某 16 位计算机中,带符号整数用补码表示,数据 Cache 和指令 Cache 分离。下表给出了指令系统中部分指令格式,其中 Rs 和 Rd 表示寄存器,mem 表示存储单元地址,(x) 表示寄存器 x 或存储单元 x 的内容。
| 名称 | 指令的汇编格式 | 指令功能 |
|---|---|---|
| 加法指令 | ADD Rs, Rd | (Rs) + (Rd) → Rd |
| 算术/逻辑左移 | SHL Rd | 2 × (Rd) → Rd |
| 算术右移 | SHR Rd | (Rd) / 2 → Rd |
| 取数指令 | LOAD Rd, mem | (mem) → Rd |
| 存数指令 | STORE Rs, mem | (Rs) → mem |
该计算机采用 5 段流水方式执行指令,各流水段分别是取指(IF)、译码/读寄存器(ID)、执行/计算有效地址(EX)、访问存储器(M)和结果写回寄存器(WB),流水线采用“按序发射,按序完成”方式,没有采用转发技术处理数据相关,并且同一个寄存器的读和写操作不能在同一个时钟周期内进行。请回答下列问题:
(1) 若 int 型变量 x 的值为 −513,存放在寄存器 R1 中,则执行指令“SHR R1”后,R1 的内容是多少(用十六进制表示)?
(2) 若某个时间段中,有连续的 4 条指令进入流水线,在其执行过程中没有发生任何阻塞,则执行这 4 条指令所需的时钟周期数为多少?
(3) 若高级语言程序中某赋值语句为 x = a + b,x、a 和 b 均为 int 型变量,它们的存储单元地址分别表示为 [x]、[a] 和 [b]。该语句对应的指令序列及其在指令流水线中的执行过程如下所示。
I1 LOAD R1, [a] I2 LOAD R2, [b] I3 ADD R1, R2 I4 STORE R2, [x]
| 指令 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| I1 | IF | ID | EX | M | WB | |||||||||
| I2 | IF | ID | EX | M | WB | |||||||||
| I3 | IF | ID | EX | M | WB | |||||||||
| I4 | IF | ID | EX | M | WB |
则这 4 条指令执行过程中,I3 的 ID 段和 I4 的 IF 段被阻塞的原因各是什么?
(4) 若高级语言程序中某赋值语句为 x = x*2 + a,x 和 a 均为 unsigned int 类型变量,它们的存储单元地址分别表示为 [x]、[a],则执行这条语句至少需要多少个时钟周期?要求模仿上图画出这条语句对应的指令序列及其在流水线中的执行过程示意图。
先在纸上自己做一遍,再点这里展开满分解答
- 16 位机,补码;指令 Cache 和数据 Cache 分离(取指和访存不会抢存储器,没有这类结构冒险)。
- 5 段流水:IF → ID(译码并读寄存器)→ EX → M → WB(写寄存器)。
- 三个关键约定:按序发射、按序完成;没有转发;同一寄存器不能在同一周期既写又读。合起来就是:要用某条指令结果的指令,它的 ID 只能排在那条指令 WB 的下一拍。
先写 −513 的 16 位补码,再算术右移一位:整体右移,最高位补原来的符号位。
513 = 0000 0010 0000 0001B,−513 的补码 = 1111 1101 1111 1111B = FDFFH。
SHR 为算术右移,高位补符号位 1:1111 1110 1111 1111B,R1 = FEFFH。
算术右移补符号位、逻辑右移补 0;−513 右移后是 −257(向下取整),不是 −256。
第 1 条指令需要 5 个时钟周期,之后每个周期完成一条,共需 5 + (4 − 1) = 8 个时钟周期。
I3(ADD R1, R2)要读 R1、R2,它们分别是 I1、I2 刚 LOAD 进来的,还没写回。I4 被卡住是因为前面的 I3 没往前走。
I3 的 ID 段被阻塞:I3 要读取的 R1、R2 分别由 I1、I2 写入,存在数据相关(写后读)。没有转发,且同一寄存器不能在同一周期读写,I3 必须等 I2 在第 6 周期写回 R2 后,才能在第 7 周期译码并读寄存器。
I4 的 IF 段被阻塞:流水线按序发射,I3 阻塞在 ID 段,前一条指令没有离开 ID 段,I4 就不能进入 ID,只能停在 IF 段等待。
x × 2 用左移实现(SHL)。指令序列:取 x、取 a、x 左移、相加、存回 x。然后一条一条排:每条指令的 ID 放在它所依赖的指令 WB 的下一拍;它的 IF 放在前一条指令 ID 的同一拍(前一条离开 IF 后才能进)。
I1 LOAD R1, [x] I2 LOAD R2, [a] I3 SHL R1 // 2×(R1) → R1 I4 ADD R1, R2 // (R1)+(R2) → R2 I5 STORE R2, [x]
| 指令 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| I1 | IF | ID | EX | M | WB | ||||||||||||
| I2 | IF | ID | EX | M | WB | ||||||||||||
| I3 | IF | ID | EX | M | WB | ||||||||||||
| I4 | IF | ID | EX | M | WB | ||||||||||||
| I5 | IF | ID | EX | M | WB |
I3 读 R1,等 I1 第 5 周期写回,第 6 周期 ID;I4 读 R1、R2,等 I3 第 9 周期写回,第 10 周期 ID;I5 要存 R2,等 I4 第 13 周期写回,第 14 周期 ID。执行这条语句至少需要 17 个时钟周期。
STORE 也“读”寄存器(要把 R2 的值读出来存进内存),它和前面写 R2 的 ADD 之间同样有数据相关,不要漏。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | 执行前 R1 = FDFFH,算术右移后 R1 = FEFFH | 2 |
| (2) | 至少 4 + (5 − 1) = 8 个时钟周期 | 2 |
| (3) | I3 的 ID 段阻塞:与 I1、I2 有数据相关,要等它们写回 | 1 |
| I4 的 IF 段阻塞:前一条 I3 在 ID 段被阻塞 | 1 |
原卷只给了这些得分点的分值,没有给出本题总分,表中不列合计。
评分说明(原卷参考答案):
- 原卷参考答案第 (4) 问(指令序列与流水线图,至少 17 个时钟周期)没有标注分值。
- 无转发 + 同周期不能读写:依赖者的 ID = 被依赖者的 WB + 1。
- 按序发射:前一条卡在 ID,后一条卡在 IF。
- 画图时一行一条指令、一列一个周期,每行 5 段一个不能少,停顿的格子空着。
模拟大题:换一段循环再练一遍(按 408 风格自编)
这一类整道大题只考过 2012-44 一次。下面这道题是照 2012-44、2014-44(4) 的问法自编的,把本类常考的问法都放进去了:时钟周期和加速比、找相关、按约定画图数周期、转发与 load-use、调整指令顺序。所有周期数都用程序逐条模拟核对过。
某 32 位 RISC 计算机采用 5 段指令流水线:取指(IF)、译码/读寄存器(ID)、执行/计算地址(EX)、访存(M)、写回(WB),各段延迟依次为 300ps、200ps、250ps、300ps、150ps,每个流水段寄存器的延迟为 20ps。指令 Cache 与数据 Cache 分离,流水线按序发射、按序完成。部分指令如下(R0 的内容恒为 0):
| 指令 | 功能 |
|---|---|
| LW Rd, imm(Rs) | M[(Rs) + imm] → Rd |
| ADD Rd, Rs, Rt | (Rs) + (Rt) → Rd |
| ADDI Rd, Rs, imm | (Rs) + imm → Rd |
| BNE Rs, Rt, L | 若 (Rs) ≠ (Rt),则转到 L |
下面的程序段对 int 型数组 a 的 100 个元素求和。开始时 R1 = 数组首地址,R3 = 0,R4 = 100。
loop: I1 LW R2, 0(R1) // R2 ← a[i]
I2 ADD R3, R3, R2 // sum += a[i]
I3 ADDI R1, R1, 4 // R1 指向下一个元素
I4 ADDI R4, R4, -1 // 计数减 1
I5 BNE R4, R0, loop // 没加完就回到 loop
约定 A:没有转发;同一个寄存器的读和写不能在同一个时钟周期内进行;转移指令在 EX 段结束时才能确定是否转移,在此之前不取后续指令。
约定 B:在约定 A 的基础上增加转发。ALU 的运算结果在 EX 段结束时可以直接送给后续指令的 EX 段;LW 读出的数据在 M 段结束时才能送给后续指令的 EX 段。转移指令的处理方式不变。
(1) 该流水线的时钟周期最短是多少?主频是多少?不考虑阻塞时,执行 100 条指令需要多少时间?非流水 CPU 不分段,每条指令的用时等于各段延迟之和。与它相比,执行这 100 条指令的加速比是多少?(2 分)
(2) 列出循环体中所有的写后读(RAW)相关,包括与下一次循环中指令的相关。按约定 A,哪几条指令会因数据相关被阻塞?(2 分)
(3) 按约定 A,模仿 2012-44 画出第 1 次循环的 I1~I5 以及第 2 次循环的 I1 在流水线中的执行过程。每次循环需要多少个时钟周期?循环 100 次共需多少个时钟周期?平均 CPI 是多少?第 2 次循环的 I1 要读 R1,而 R1 是第 1 次循环的 I3 写的,I1 为什么不会因此阻塞?(4 分)
(4) 按约定 B,每次循环需要多少个时钟周期?剩下的阻塞各是什么原因造成的?在不改变程序功能的前提下,怎样调整指令顺序,才能使每次循环再少 1 个时钟周期?(2 分)
先在纸上自己做一遍,再点这里展开满分解答
考点4.4 吞吐率、加速比4.4 三种约定下数阻塞4.4 有阻塞时的 CPI4.4 冒险的对策
- 时钟周期要按最慢的一段取,再加上段寄存器的延迟。非流水 CPU 执行一条指令要 300 + 200 + 250 + 300 + 150 = 1200ps。
- 先列读写表。store 和转移指令没有要写的寄存器;LW 的地址寄存器也算“读”。
指令 写 读 I1 LW R2 R1 I2 ADD R3 R3、R2 I3 ADDI R1 R1 I4 ADDI R4 R4 I5 BNE — R4、R0 - 约定 A 翻译成一句话:要用某条指令结果的指令,ID 排在那条指令 WB 的下一拍;BNE 后面那条指令的 IF 排在 BNE 的 EX 下一拍。
- 约定 B 翻译成一句话:用 ALU 结果的指令,EX 可以紧跟在产生者的 EX 后面,不用等;用 LW 结果的指令,EX 最早排在 LW 的 M 下一拍,也就是 load-use 仍要停 1 拍。
时钟周期 Δt = 最长段延迟 + 段寄存器延迟 = 300 + 20 = 320ps,主频 = 1 / 320ps = 3.125GHz。
无阻塞时 100 条指令的时间 = (5 + 100 − 1) × 320ps = 33 280ps = 33.28ns。
非流水 CPU 每条指令 1200ps,100 条共 120ns。加速比 = 120 ÷ 33.28 ≈ 3.61。
加速比达不到段数 5,有三个原因:Δt 按最慢的一段取,快的段要等;每段多了 20ps 的段寄存器延迟;流水线装入和排空还要多花 4 拍。
拿读写表逐条看:每个“读”的寄存器,是前面哪条指令最后写的?跨循环的相关要把下一次循环接在 I5 后面看。
循环内:I1 → I2(R2),I4 → I5(R4)。
跨循环:I2 → 下一次的 I2(R3),I3 → 下一次的 I1 和 I3(R1),I4 → 下一次的 I4(R4)。
按约定 A,I2 和 I5 会因数据相关被阻塞。I2 要等 I1 写回 R2,I5 要等 I4 写回 R4。跨循环的相关不会引起阻塞,理由见第 (3) 问。
I1 读 R1、I3 随后写 R1,这是读后写(WAR)。按序流水线里 I1 早在 ID 段就读完了 R1,所以既不出错也不阻塞。R0 的内容恒为 0,没有指令写它,不构成相关。
从上往下一条一条排,三条规则:IF 排在前一条指令进入 ID 的那一拍,因为前一条离开 IF 后才能取下一条;ID 取 “IF + 1” 和 “所依赖指令的 WB + 1” 中较晚的一拍;BNE 后面那条指令的 IF 排在 BNE 的 EX + 1。
| 指令 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| I1 | IF | ID | EX | M | WB | |||||||||||||
| I2 | IF | ID | EX | M | WB | |||||||||||||
| I3 | IF | ID | EX | M | WB | |||||||||||||
| I4 | IF | ID | EX | M | WB | |||||||||||||
| I5 | IF | ID | EX | M | WB | |||||||||||||
| I1(第 2 次) | IF | ID | EX | M | WB |
着色的空格表示这条指令在该拍被阻塞,停在原来的段上。
I2 与 I1 有关于 R2 的数据相关,I2 的 ID 推迟到 I1 写回(第 5 周期)后的第 6 周期。I5 与 I4 有关于 R4 的数据相关,I5 的 ID 推迟到第 12 周期。I5 在第 13 周期末才确定转移,所以第 2 次循环的 I1 在第 14 周期取指。
每次循环 = 14 − 1 = 13 个时钟周期,即 5 条指令各 1 拍,加上 I2 阻塞 3 拍、I5 阻塞 3 拍、转移阻塞 2 拍。
前 99 次循环各 13 个周期;最后一次从 I1 的 IF 到 I5 的 WB 共 15 个周期。所以 100 次共 99 × 13 + 15 = 1302 个时钟周期,约 1302 × 320ps ≈ 416.6ns。
平均 CPI = 1302 ÷ 500 ≈ 2.6。也可以按公式算:1 + (3 + 3 + 2) ÷ 5 = 2.6。
第 2 次循环的 I1 在第 15 周期读 R1,而 I3 在第 10 周期就已经把 R1 写回了,所以不会阻塞。原因是 I3 和下一次的 I1 之间隔着 I4、I5,还有转移造成的等待。
“每次循环的周期数”要从本次 I1 的 IF 数到下一次 I1 的 IF。总周期数要数到最后一条指令 WB 所在的那一拍,不是 13 × 100 = 1300。
有了转发,ALU 结果随算随用;只剩 LW 后面紧跟着使用它结果的指令(load-use)要停 1 拍,转移仍然停 2 拍。调整顺序的思路:在 LW 和用它结果的 ADD 之间,塞进一条与它们都无关的指令。
| 指令 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| I1 | IF | ID | EX | M | WB | ||||||||
| I2 | IF | ID | EX | M | WB | ||||||||
| I3 | IF | ID | EX | M | WB | ||||||||
| I4 | IF | ID | EX | M | WB | ||||||||
| I5 | IF | ID | EX | M | WB | ||||||||
| I1(第 2 次) | IF | ID | EX | M | WB |
着色的空格表示这条指令在该拍被阻塞,停在原来的段上。
I2 紧跟在 LW 后面使用 R2,LW 的数据要到 M 段末(第 4 周期)才有,所以 I2 的 EX 只能在第 5 周期,阻塞 1 拍。I5 要用 I4 的结果 R4,I4 的 EX 一结束就能转发,所以 I5 不阻塞。转移仍在 EX 段末才确定,要阻塞 2 拍。
每次循环 = 5 + 1 + 2 = 8 个时钟周期,100 次共 99 × 8 + 10 = 802 个周期。
调整:把 I3 移到 I1 和 I2 之间,顺序变成 I1(LW)、I3(ADDI R1)、I2(ADD)、I4、I5。这样 I2 与 LW 之间隔了一条指令,I2 的 EX 正好在 LW 的 M 之后一拍,不再阻塞,每次循环 7 个时钟周期。程序功能不变:I1 先用旧的 R1 取数,I3 再修改 R1,而 I2 与 R1 无关。
如果把 I3 移到 I1 前面,I1 就要改成 LW R2, −4(R1),否则取到的是下一个元素。改成 I1、I4、I3、I2、I5 也是 7 个周期。剩下的 2 拍转移阻塞,要靠分支预测、延迟转移(把与转移无关的指令放到转移指令后面)或者把比较提前到 ID 段来解决。
- 数阻塞前先把约定抄下来。三种阻塞分开数:数据相关、load-use、转移。
- 每次循环的周期数 = 指令条数 + 各种阻塞的拍数;CPI = 1 + 阻塞拍数 ÷ 指令条数。
- 在 load 和使用它结果的指令之间插一条无关指令,这是编译器消除 load-use 阻塞的标准做法(2024-19 考过“转发不能解决全部数据冒险”)。
举一反三:条件一变,答案怎么变
以上面的模拟题(约定 A 每次循环 13 拍,约定 B 每次循环 8 拍)为基准,每行只改一个条件。先自己想出新答案和理由,再点开核对。
| 条件怎么变 | 新答案和理由 |
|---|---|
| 约定 A 改为“同一寄存器前半周期写、后半周期读” | 想好了再点使用结果的指令,ID 可以和产生者的 WB 在同一拍,每处数据相关少停 1 拍。I2、I5 各停 2 拍,每次循环 5 + 2 + 2 + 2 = 11 拍,100 次共 1102 拍。 |
| 约定 A 中,转移改为在 ID 段结束时确定 | 想好了再点转移阻塞由 2 拍变为 1 拍,每次循环 12 拍,100 次共 1203 拍。 |
| 约定 B 中,转移改为在 ID 段结束时确定 | 想好了再点每次循环 5 + 1 + 1 = 7 拍。 |
| 约定 B 中采用分支预测,且每次都预测正确 | 想好了再点没有转移阻塞,只剩 load-use 的 1 拍,每次循环 6 拍;再把 I3 调到 I1 后面,每次 5 拍,CPI = 1。 |
| IF 段的延迟从 300ps 降到 200ps | 想好了再点Δt 仍由最慢的 M 段(300ps)决定,时钟周期不变,仍为 320ps。要把 IF 和 M 都降到 250ps,Δt 才变为 270ps。 |
| 指令条数由 100 条改为 n 条(无阻塞) | 想好了再点T = (5 + n − 1) × Δt。n 很大时,吞吐率趋近于 1 / Δt,加速比趋近于 1200 ÷ 320 = 3.75。 |
丢分点清单
- 没看约定就按“有转发”算,或者把“不能同一周期读写”当成可以。
- 漏掉 store 对寄存器的“读”,漏掉 load 地址寄存器的“读”。
- 把写后写(WAW)、读后写(WAR)也当成要阻塞(按序流水线里它们不引起阻塞)。
- 阻塞的指令后面那条的 IF 画早了(按序发射时它要等前一条离开 IF)。
- n 条指令写成 k × n 个周期(那是不用流水线的时间)。
- 时钟周期取了各段的平均值,应该取最长的一段。
本类其他真题
- 2014-44(4):一段循环:找出因数据相关阻塞的指令、哪条引起控制冒险、为什么指令 1 不因指令 5 阻塞。
- 选择题:2009-18(时钟周期取最长段)、2013-18(吞吐率)、2016-19 / 2019-18 / 2023-19(找数据冒险)、2018-20(流水线时间)、2010-19 / 2024-19(冒险的解决)、2011-18(什么有利于流水线)、2014-16(Cache 分离)、2017-17(超标量)。
11.6计组 ⑤ Cache:切地址、算容量、算命中率
- 8.3 Cache 效率与两种平均访问时间口径 —— 命中率、平均访问时间
- 8.2 主存与 CPU 连接的完整设计 —— 芯片数、片选、地址范围
- 8.2 交叉存储器连续读 n 个字 —— 读一个 Cache 块要多久
这类题长什么样
Cache 几乎每年都考,单独成题或者和虚拟存储合在一起(那种见 ⑥)。永远是这几问:地址怎么切、Cache 一共多少位、某个地址映射到哪一组、一段数组程序的命中率、写策略和附加位、访问过程。
| 常见问法 | 考过的地方 |
|---|---|
| 地址中哪几位是 Tag / 组号(行号)/ 块内地址 | 2011-44(2),2016-45(1),2019-46,2023-43(3),2025-43(1) |
| Cache 总容量(含 Tag、有效位、脏位、LRU 位)/ 每行有哪些附加位 | 2010-44(1),2018-44(3),2020-44(1) |
| 某地址 / 某块 / 某数组元素映射到哪一组(行) | 2010-44(2),2016-45(2),2018-44(4),2023-43(3),2025-43(2) |
| 数组程序的命中率 / 缺失次数 | 2010-44(3),2014-45(2),2020-44(2),2023-43(4),2025-43(3) |
| 平均访问时间、存储带宽 | 2025-43(3),2012-43,2013-43 |
| 是否命中(Tag 和有效位) | 2011-44(3),2018-44(4) |
| 访问过程(含缺失处理)、写策略 | 2020-44(3),2016-45(4) |
做这类题必须会的 6 件事
① 一个地址切三段,查 Cache 三步走。Cache 按“块”从主存搬数据。一个主存地址被切成:Tag(标记)| 组号 | 块内地址。查的时候:① 用组号选中一组;② 拿 Tag 和这一组里每一行的 Tag 同时比较,相等且有效位为 1 才算命中;③ 用块内地址从块里取出要的那个字节/字。
② 位数怎么算(按字节编址)。
③ 某个地址落在哪一组。块号 = 地址 ÷ 块大小(取整),组号 = 块号 mod 组数,Tag = 块号 ÷ 组数(取整)。在二进制上就是:去掉低位的块内地址,再往上数“组号位数”位就是组号。
④ 数组程序的命中率——先想清楚四件事。
- 元素多大?一个块放几个元素?(块 64B、int 4B → 16 个)
- 首地址是不是块的开头?不是的话数组会多占一个块(2025-43 的 129 块)。
- 每个元素访问几次?
s[k] = 2*s[k]是读 1 次 + 写 1 次,写的时候块已经在了,必命中。 - 访问顺序是按地址顺序(按行)还是跳着走(按列)?跳着走时,要算“同一组要放几个块、放不放得下”。
⑤ 写策略和附加位。写命中时:回写(只写 Cache,换出时才写回主存,要有脏位)或直写(同时写主存,不需要脏位,一般配写缓冲)。写不命中时:写分配(先把块调进来再写,常配回写)或非写分配(直接写主存,常配直写)。替换:直接映射不用选;组相联、全相联用 LRU、FIFO、随机,k 路 LRU 每行 log₂k 位计数。
⑥ 平均访问时间。“缺失损失”是缺失时多花的时间:平均访问时间 = 命中时间 + 缺失率 × 缺失损失。如果题目给的是“访问 Cache 的时间 tc、访问主存的时间 tm”:先访问 Cache、未命中再访问主存时 = h·tc + (1 − h)(tc + tm);同时访问 Cache 和主存时 = h·tc + (1 − h)·tm。
解题模板
- 写出三个基本量。地址位数、块大小、组数(= 数据区 ÷ 块 ÷ 路数)。
- 切地址。从低位往高位:块内地址 → 组号(行号)→ 剩下的是 Tag,标出每段是第几位到第几位。
- 算容量。把每行的附加位逐个列出来(有效位;脏位看写策略;LRU 位看路数),再乘行数。
- 求映射。地址写成二进制切段,或者用“块号 mod 组数”。
- 求命中率。列出“访问次数”和“缺失次数”:顺序访问时缺失次数 = 占用的块数;跳着访问时检查同组块数是否 ≤ 路数,不够就按替换算法模拟。
- 写结论并带理由:对齐了没有、每块几个元素、会不会被替换。
某计算机的主存地址空间大小为 256MB,按字节编址。指令 Cache 和数据 Cache 分离,均有 8 个 Cache 行,每个 Cache 行大小为 64B,数据 Cache 采用直接映射方式。现有两个功能相同的程序 A 和 B,其伪代码如下所示:
// 程序 A
int a[256][256];
.....
int sum_array1()
{
int i, j, sum=0;
for (i=0; i<256; i++)
for (j=0; j<256; j++)
sum += a[i][j];
return sum;
}
// 程序 B
int a[256][256];
.....
int sum_array2()
{
int i, j, sum=0;
for (j=0; j<256; j++)
for (i=0; i<256; i++)
sum += a[i][j];
return sum;
}
假定 int 类型数据用 32 位补码表示,程序编译时 i,j,sum 均分配在寄存器中,数组 a 按行优先方式存放,其首地址为 320(十进制数)。请回答下列问题,要求说明理由或给出计算过程。
(1) 若不考虑用于 cache 一致性维护和替换算法的控制位,则数据 Cache 的总容量为多少?
(2) 数组元素 a[0][31] 和 a[1][1] 各自所在的主存块对应的 Cache 行号分别是多少(Cache 行号从 0 开始)?
(3) 程序 A 和 B 的数据访问命中率各是多少?哪个程序的执行时间更短?
先在纸上自己做一遍,再点这里展开满分解答
考点8.3 Cache 地址划分、容量、命中率8.1 局部性训练营 ⑤
- 主存 256MB = 228 B,按字节编址 → 主存地址 28 位。
- 数据 Cache:8 行,每行(块)64B,直接映射 → 地址切成 Tag | 行号 3 位 | 块内地址 6 位,Tag = 28 − 3 − 6 = 19 位。
- int 4 字节,一个块装 64 / 4 = 16 个 int;数组一行 256 个 int = 1024B = 16 个块。
- 首地址 320 = 5 × 64,正好是块的开头(块对齐)。
主存地址 28 位,块内地址 6 位,行号 3 位,Tag = 28 − 6 − 3 = 19 位。
每行 = 有效位 1 位 + Tag 19 位 + 数据 64 × 8 = 512 位,共 532 位。
总容量 = 8 × 532 = 4256 位 = 532 字节。
“总容量”要算上 Tag 和有效位,不能只答 8 × 64B = 512B(那是“数据区容量”)。
直接映射:行号 = 块号 mod 行数;块号 = 地址 ÷ 块大小(取整)。
a[0][31] 的地址 = 320 + 31 × 4 = 444,块号 = ⌊444 / 64⌋ = 6,行号 = 6 mod 8 = 6。
a[1][1] 的地址 = 320 + (256 × 1 + 1) × 4 = 1348,块号 = ⌊1348 / 64⌋ = 21,行号 = 21 mod 8 = 5。
A 按行访问 = 按地址顺序访问:每个块第一次访问缺失,同一块剩下的元素都命中。B 按列访问:a[i][j] 和 a[i+1][j] 相差 1024B = 16 块,16 mod 8 = 0,落在同一个 Cache 行里,每次都把上一次调入的块挤掉。
程序 A:按行优先顺序访问,数组首地址按块对齐,每块 16 个元素,每块只在访问第 1 个元素时缺失,命中率 = 15 / 16 = 93.75%。
程序 B:按列访问,相邻两次访问的元素相差 256 × 4 = 1024B = 16 个主存块,而 16 mod 8 = 0,它们映射到同一 Cache 行,后一次访问总把前一次调入的块替换掉;等到访问下一列时,该块早已被替换,所以每次访问都缺失,命中率为 0。
程序 A 的执行时间更短。
算命中率前先检查“首地址是否块对齐”:不对齐时数组会多占一个块(见 2025-43)。
- Cache 总容量 = 行数 × (数据 + Tag + 有效位 + 脏位 + 替换位),题目说不计的就不加。
- 顺序访问的命中率 = (每块元素数 − 1) / 每块元素数。
- 跨行访问时,看“相邻两次访问相差几个块”再 mod 组数:落进同一组且装不下就互相挤。
模拟大题:三个数组抢同一组(按 408 风格自编)
真题里的 Cache 程序题大多只有一个数组(2010-44、2020-44、2023-43、2025-43)。多个数组互相“挤”才是命中率题最能考出理解的地方,也最容易举一反三:换映射方式、换相联度、挪一下首地址,结果完全不同。下面的命中率都用 Cache 模拟程序逐次访问核对过。
某计算机主存容量 256MB,按字节编址。Cache 数据区容量 8KB,块大小 32B,采用 2 路组相联映射、LRU 替换算法、回写(write back)和写分配策略。int 型占 4B。数组 a、b、c 都是 int 型,各有 1024 个元素,首地址分别为 0800000H、0802000H、0804000H。变量 i 和 sum 都在寄存器中,不访存。Cache 初始为空。
// 程序 A for (i = 0; i < 1024; i++) sum += a[i] * b[i]; // 程序 B for (i = 0; i < 1024; i++) c[i] = a[i] + b[i];
(1) 主存地址中 Tag、组号、块内地址各占几位?Cache 每行包含哪些信息、共多少位?Cache 的总容量是多少字节(含所有附加位)?(3 分)
(2) 主存地址 1234567H 映射到 Cache 的哪一组?它的 Tag 是多少?(2 分)
(3) 程序 A 的 Cache 命中率是多少?若改为直接映射,数据区容量和块大小不变,命中率又是多少?设命中时的访问时间为 2ns,缺失损失为 60ns,求两种情况下的平均访问时间。(4 分)
(4) 仍用 2 路组相联,程序 B 的命中率是多少?为什么?请给出两种改进方法(一种改 Cache 的组织,一种不改硬件),并说明改进后的命中率。(3 分)
先在纸上自己做一遍,再点这里展开满分解答
考点8.3 映射方式与地址划分8.3 平均访问时间训练营 ⑤ 按行 vs 按列
- 256MB = 228B,所以主存地址 28 位;块 32B,块内地址 5 位。
- 行数 = 8KB ÷ 32B = 256 行,2 路组相联,组数 = 256 ÷ 2 = 128,组号 7 位。
- “组号 + 块内地址”共 12 位,对应 4KB 的地址范围:地址每差 4KB 的整数倍,组号就相同。a、b、c 的首地址两两相差 2000H = 8KB,所以 a[i]、b[i]、c[i] 总是落在同一组,这是本题的关键。
- 一个块放 32 ÷ 4 = 8 个 int;三个数组的首地址都是块的开头(低 5 位为 0)。
主存地址 28 位:块内地址 5 位(第 4~0 位),组号 7 位(第 11~5 位),Tag = 28 − 7 − 5 = 16 位(第 27~12 位)。
每行包含:数据 32B = 256 位,Tag 16 位,有效位 1 位,脏位 1 位(回写需要),LRU 位 1 位(2 路只要 1 位)。每行共 256 + 16 + 1 + 1 + 1 = 275 位。
总容量 = 256 行 × 275 位 = 70 400 位 = 8800B。
“数据区 8KB”只是数据部分。2 路组相联的 LRU 每行 1 位就够了(每组只要记住哪一行较久没用);如果是直写,就不需要脏位。
1234567H 的低 12 位是 567H = 0101 0110 0111B。块内地址是低 5 位 00111B = 7;组号是第 11~5 位 0101011B = 43;Tag 是第 27~12 位 = 1234H。
即映射到 Cache 的第 43 组,可以放在这一组的两行中的任意一行。
每次循环访问 a[i] 和 b[i],它们在同一组。2 路组:一组放得下两个块;直接映射:每“组”只有一行,两个块只能轮流住。
程序 A 共访存 2 × 1024 = 2048 次。
2 路组相联:a[i] 所在的块和 b[i] 所在的块映射到同一组,但一组有 2 行,可以同时存放。每个块第一次访问时缺失,块内其余 7 个元素命中。a、b 各占 1024 × 4 ÷ 32 = 128 块,缺失 256 次,命中率 = (2048 − 256) ÷ 2048 = 87.5%。
直接映射:共 256 行,行号 8 位,“行号 + 块内地址”共 13 位,对应 8KB。a、b 相差 8KB,所以 a[i] 与 b[i] 映射到同一行。访问顺序为 a[0]、b[0]、a[1]、b[1]……,每次都把对方的块替换出去,每次访问都缺失,命中率 = 0。
平均访问时间:2 路组相联 = 2 + (1 − 87.5%) × 60 = 9.5ns;直接映射 = 2 + 1 × 60 = 62ns。
判断两个地址是否冲突,要看“组号(行号)+ 块内地址”共几位:2 路组相联时是 12 位(4KB),直接映射时是 13 位(8KB)。不要只看组号的位数。
每次循环访问 a[i]、b[i]、c[i](读、读、写),三个块在同一组,而一组只有 2 行。LRU 下循环访问 3 个块、只有 2 个位置:每次要用的恰好是刚被换出的那个。
访存 3 × 1024 = 3072 次。以第 0 组为例:访问 a₀ 块缺失、b₀ 块缺失,组满;c₀ 块缺失,LRU 替换 a₀ 块;下一次访问 a[1] 时 a₀ 块已被换出,缺失并替换 b₀ 块;接着 b[1] 缺失……每次访问都缺失,命中率 = 0(c 的块写入后还会被换出、写回主存,开销更大)。
改进方法一(改 Cache):改为 4 路组相联(容量不变)。一组能同时放下 a、b、c 的三个块,每块只在第一次访问时缺失。缺失 3 × 128 = 384 次,命中率 = (3072 − 384) ÷ 3072 = 87.5%。
改进方法二(不改硬件):让 c 与 a、b 错开,例如把 c 的首地址改为 0804020H(错开一个块,32B)。这样 c[i] 所在的块落在下一组,每组最多同时需要 2 个块,放得下。命中率也是 87.5%。
写操作 c[i] = … 在写分配策略下,写不命中也要先把块调入 Cache,所以每个 c 的块也要缺失 1 次。另外,“错开首地址”是消除冲突缺失的通用办法,叫“数组填充(padding)”:例如在直接映射下把 b 的首地址改为 0802020H,程序 A 的命中率也能回到 87.5%。
- 先算“组号 + 块内地址”覆盖多大的地址范围(本题 4KB)。首地址相差这个范围整数倍的数组,同下标元素一定在同一组。
- 同时要用的块数 ≤ 路数,就只有第一次访问缺失;超过路数,LRU 下会反复互相替换,命中率可以降到 0。
- 提高相联度、错开首地址(填充)都能消除这种冲突缺失。
举一反三:条件一变,答案怎么变
以上面的模拟题(2 路组相联,程序 A 命中率 87.5%)为基准,每行只改一个条件。先自己想出新答案和理由,再点开核对。
| 条件怎么变 | 新答案和理由 |
|---|---|
| 块大小改为 64B(容量、2 路不变) | 想好了再点组数 64,组号 6 位,块内地址 6 位,Tag 仍为 16 位。每块 16 个 int,程序 A 命中率 = 15/16 = 93.75%。 |
| 写策略改为直写 | 想好了再点不需要脏位,每行 256 + 16 + 1 + 1 = 274 位。 |
| 改为 4 路组相联(容量不变) | 想好了再点组数 64,组号 6 位,Tag 17 位;LRU 每行 2 位,每行 256 + 17 + 1 + 1 + 2 = 277 位。程序 A 命中率不变(87.5%),程序 B 变为 87.5%。 |
| 改为全相联 | 想好了再点没有组号,Tag = 28 − 5 = 23 位。任何块都能放进任意一行,程序 A、B 都不会发生冲突缺失,命中率都是 87.5%。 |
| 数组改为 double 型(8B) | 想好了再点每块只能放 4 个元素,程序 A 命中率 = 3/4 = 75%。 |
| 程序 A 改为 i 从 1023 递减到 0 | 想好了再点命中率不变,仍是 87.5%:每个块还是只在第一次被访问时缺失。 |
| 程序 B,替换算法改为 FIFO | 想好了再点命中率仍为 0(程序模拟核对)。每组循环访问 a、b、c 三个块、只有 2 行,FIFO 每次换出的也恰好是下一个要用的块;这种循环访问下 FIFO 和 LRU 一样差(8.3 替换算法)。 |
| 程序 B,写策略改为直写 + 非写分配 | 想好了再点写 c[i] 不命中时不调入 Cache,直接写主存,c 的块从不进 Cache;a、b 两个块一组放得下。读 2048 次缺失 256 次,写 1024 次全部不命中,命中率 = (3072 − 256 − 1024) ÷ 3072 ≈ 58.3%(程序模拟核对;8.3 写策略)。 |
| 本题 Cache 作为 L1,再加一级 L2:L1 缺失时访问 L2(10ns),L2 命中率 90%,L2 缺失再访问主存(60ns) | 想好了再点程序 A 的平均访问时间 = 2 + (1 − 87.5%) × (10 + 10% × 60) = 2 + 0.125 × 16 = 4ns,比没有 L2 时的 9.5ns 少了一半多(8.3 多级 Cache)。 |
预测大题:双核“伪共享”与 MESI 拓展
多核与 Cache 一致性在考纲里只是“了解”,真题还没出过大题。下面这道把它和本类题的地址划分、第 7 篇的竞争条件串在一起,作为预测。缺失和作废次数都用 MESI 模拟程序核对过。
某双核处理器的两个核各有一个私有的 L1 数据 Cache:容量 32KB,块大小 64B,8 路组相联,回写、写分配,用 MESI 协议保持一致性;两个核共享主存,物理地址 32 位,按字节编址。int 型占 4B。有全局变量:
struct { int x; int y; } s; // s.x 的地址为 1000 0040H
// 线程 T1 在核 0 上运行:
for (i = 0; i < 1000; i++) s.x++;
// 线程 T2 在核 1 上运行:
for (i = 0; i < 1000; i++) s.y++;
i 在寄存器中。假设两个线程的 s.x++ 和 s.y++ 严格交替执行(T1 一次、T2 一次……),每次 ++ 都是先读一次、再写一次。开始时两个 Cache 中都没有 s。
(1) 物理地址中 Tag、组号、块内地址各占几位?s.x、s.y 分别映射到哪一组?它们在不在同一个 Cache 块中?(3 分)
(2) T1 第一次执行 s.x++ 后,核 0 中该块处于 MESI 的什么状态?接着 T2 第一次执行 s.y++:读的时候发生了什么?写的时候又发生了什么?之后两个核中该块各处于什么状态?(3 分)
(3) 按题设的交替顺序执行完两个循环,两个核一共发生多少次读缺失?一共发出多少次作废(invalidate)请求?(2 分)
(4) 两个线程访问的明明是不同的变量,为什么会这样?不改硬件,怎样修改程序消除这个问题?修改后读缺失和作废各多少次?(2 分)
(5) 若两个线程执行的都是 s.x++(同一个变量,共 2000 次),MESI 协议能否保证最后 s.x = 2000?为什么?(1 分)
先在纸上自己做一遍,再点这里展开满分解答
- 32KB ÷ 64B = 512 行,8 路 → 64 组:组号 6 位,块内 6 位。
- 一致性是按块维护的:一个核要写某个块,就得让其他核里这个块的副本作废,不管它们用的是块里的哪个字节。
- MESI:M 已修改(只有我有、和主存不一致),E 独占(只有我有、和主存一致),S 共享(多个核都有),I 无效。
块内地址 6 位(第 5~0 位),组号 6 位(第 11~6 位),Tag = 32 − 6 − 6 = 20 位(第 31~12 位)。
s.x = 1000 0040H:块内地址 00H,组号 = 000001B = 1,Tag = 10000H。s.y = 1000 0044H:块内地址 04H,组号也是 1,Tag 相同。两者都在 1000 0040H ~ 1000 007FH 这一个 64B 的块里,在同一个 Cache 块中。
T1 读 s.x:核 0 缺失,其他核没有这个块,从主存调入,状态为 E;写 s.x:E → M,不用通知别的核。
T2 读 s.y:核 1 缺失,发现核 0 中该块为 M,核 0 把块写回(提供数据),两个核的副本都变为 S。T2 写 s.y:核 1 发出作废请求,核 0 的副本变为 I,核 1 的变为 M。
从第 2 次开始,每次轮到一个核做 ++ 时,它的副本都刚被对方作废(I),所以每次读都缺失,对方把 M 块写回,两边变 S;接着写时再把对方作废。
读缺失共 2000 次(每次 ++ 一次);作废请求共 1999 次(只有 T1 的第一次写是 E → M,不用作废),同时 M 块被写回 1999 次(程序模拟核对)。
原因:x 和 y 落在同一个 Cache 块里,而一致性按块维护,两个核轮流写同一块,块在两个 Cache 之间来回“搬家”,这叫伪共享(false sharing)。
改进:在 x 和 y 之间填充,让它们落在不同的块里,例如 struct { int x; char pad[60]; int y; } s;(y 的偏移为 64)。修改后每个核只在第一次访问时读缺失,之后块一直在自己的 Cache 中处于 M 状态:读缺失共 2 次,作废 0 次。
这和 8.3 的“数组填充”是同一个思路:改数据的位置,让不该挤在一起的东西分开。
不能保证。s.x++ 由“读、加 1、写”三步组成,MESI 只保证所有核看到的对同一块的写是一致的,不保证这三步不被打断。两个核可能先后读到同一个旧值,各自加 1 后写回,丢掉一次更新,结果小于 2000。要用锁或原子指令(如 TSL、xchg)实现互斥(第 7 篇)。
- 一致性按块维护:不同变量只要在同一块里,多个核轮流写也会互相作废(伪共享)。
- MESI 中写一个 S 状态的块要先作废其他副本;读一个别人处于 M 的块要先让对方写回。
- Cache 一致性解决“看到的值是否一致”,互斥解决“读改写是否被打断”,两件事不能互相代替。
丢分点清单
- “Cache 总容量”只算了数据区。
- 回写忘了脏位、组相联忘了 LRU 位;直写却加了脏位。
- 按字编址的主存按字节算地址位数(或反过来)。
- 没检查首地址是否块对齐,块数少算一个。
- 把“读 + 写”算成一次访问,或者把写也算成缺失。
- 按列访问一律写命中率 0,没算同组能不能放下。
- 判断命中只比 Tag 不看有效位(2018-44 的坑)。
本类其他真题
- 2020-44:8 路组相联、直写、LRU:Tag 和 LRU 位数、是否要修改位、数组缺失次数、一次取指的完整 Cache 访问过程。
- 2023-43:4 路组相联 + 分页:数组占几页、缺页地址、时间局部性、组号、行列两种循环的命中率。
- 2025-43:8 路组相联 + 分页:虚拟地址哪几位可作索引、组号、不对齐时的缺失率、平均访问时间、缺页次数。
- 和虚拟存储合考的(见 ⑥):2011-44、2014-45、2016-45、2018-44、2019-46。
11.7计组 ⑥ 虚拟存储 + TLB + Cache:一次访存走到底
先读:8.5 分页 8.7 虚拟内存 8.8 一次访存的全过程 8.3 Cache
- 8.5 TLB 与页表同时查的有效访问时间 —— EAT 计算
- 8.5 分页变换步骤与四个易错点 —— 越界、页表项长度
- 8.6 分页与分段对比、段页式变换 —— 段页式访存次数
这类题长什么样
这是计组和操作系统“握手”最多的一类题:给出虚拟地址、页大小、TLB 和 Cache 的组织方式,有时还给页表、TLB、Cache 的部分内容,然后让你把一个虚拟地址一路翻译下去,判断每一步命中还是缺失。它和操作系统的分页大题(⑩)考的是同一件事,只是这里更关心硬件(TLB、Cache 的结构)。
| 常见问法 | 考过的地方 |
|---|---|
| 虚拟地址 / 物理地址各几位,哪几位是虚页号、页框号 | 2011-44(1),2016-45(1),2018-44(1),2021-44(1) |
| TLB 是什么映射、标记是什么、组号是哪几位、用 SRAM 还是 DRAM | 2011-44(4),2016-45(1),2018-44(2),2021-44(2) |
| 给一个虚拟地址:在不在主存、物理地址、Cache 是否命中 | 2011-44(3)(4),2018-44(4) |
| 只可能在 Cache 的哪一组命中(组号在页内偏移中) | 2019-46,2018-44(4),2023-43(3),2025-43(1) |
| TLB 的 LRU 替换模拟、表项位数 | 2021-44(3)(4) |
| 哪条指令可能缺页、访问 TLB / 磁盘几次 | 2014-45(3),2023-44(4) |
| Cache 缺失和缺页谁开销大、为什么页面总是回写 | 2016-45(3)(4) |
做这类题必须会的 5 件事
① 一次访存的完整流程。下面这张图要能默画出来:蓝色是硬件自动完成的,绿色是操作系统接手的。
② 位数关系。虚页号 = 虚拟地址位数 − 页内偏移位数;页框号 = 物理地址位数 − 页内偏移位数。Cache 切的是物理地址(Tag 位数按物理地址算)。
③ TLB 就是“页表的 Cache”。它的“地址”是虚页号:全相联时标记 = 整个虚页号;k 路组相联时,虚页号的低位是组号,高位是标记(2011-44(4)、2021-44)。每个 TLB 表项 = 标记 + 页框号 + 有效位(+ 修改位、访问位等)。TLB 追求速度,用 SRAM;每一项一个比较器 = 全相联。
④ 什么时候可以直接用虚拟地址算 Cache 组号。当“组号 + 块内地址”的位数 ≤ 页内偏移位数时,组号整个落在页内偏移里,而页内偏移在地址转换时不变,所以虚拟地址和物理地址的组号相同(2019-46、2023-43、2025-43)。这样 CPU 可以一边查 TLB、一边用组号选 Cache 组,速度更快。
⑤ 各种“次数”。TLB 命中:只需访存取数据(Cache 未命中时)。TLB 未命中、页在主存:先访存读页表项,再访存取数据(两级页表再多一次)。缺页:处理完后重新执行这条指令,TLB 要再查一次(2014-45(3):TLB 至少 1001 次)。一段数据全在同一页时缺页 1 次;跨 n 页就是 n 次。
解题模板
- 画两根地址条。虚拟地址:虚页号 | 偏移;物理地址:页框号 | 偏移,同时在物理地址上再切 Tag | 组号 | 块内。
- 给的是十六进制虚拟地址时,先切出虚页号(4KB 页就是去掉最后 3 个十六进制位)。
- 查 TLB。全相联直接比虚页号;组相联先算组号和标记。命中 → 页框号;有效位为 0 或找不到 → 查页表。
- 查页表。有效位 1 → 页框号;0 → 缺页(页不在主存)。
- 拼物理地址,查 Cache。写成二进制切段,找组,比 Tag,看有效位。
- 每一步的结论都写理由:“TLB 第 0 组中有标记为 012H 且有效的项,所以命中”。
某计算机存储器按字节编址,虚拟(逻辑)地址空间大小为 16MB,主存(物理)地址空间大小为 1MB,页面大小为 4KB;Cache 采用直接映射方式,共 8 行;主存与 Cache 之间交换的块大小为 32B。系统运行到某一时刻时,页表的部分内容和 Cache 的部分内容分别如题 44-a 图、题 44-b 图所示,图中页框号及标记字段的内容为十六进制形式。
| 虚页号 | 有效位 | 页框号 |
|---|---|---|
| 0 | 1 | 06 |
| 1 | 1 | 04 |
| 2 | 1 | 15 |
| 3 | 1 | 02 |
| 4 | 0 | — |
| 5 | 1 | 2B |
| 6 | 0 | — |
| 7 | 1 | 32 |
| 44-a 图 页表的部分内容 | ||
| 行号 | 有效位 | 标记 |
|---|---|---|
| 0 | 1 | 020 |
| 1 | 0 | — |
| 2 | 1 | 01D |
| 3 | 1 | 105 |
| 4 | 1 | 064 |
| 5 | 1 | 01D |
| 6 | 0 | — |
| 7 | 1 | 27A |
| 44-b 图 Cache 的部分内容 | ||
请回答下列问题:
(1) 虚拟地址共有几位,哪几位表示虚页号?物理地址共有几位,哪几位表示页框号(物理页号)?
(2) 使用物理地址访问 Cache 时,物理地址应划分成哪几个字段?要求说明每个字段的位数及在物理地址中的位置。
(3) 虚拟地址 001C60H 所在的页面是否在主存中?若在主存中,则该虚拟地址对应的物理地址是什么?访问该地址时是否 Cache 命中?要求说明理由。
(4) 假定为该机配置一个 4 路组相联的 TLB,该 TLB 共可存放 8 个页表项,若其当前内容(十六进制)如题 44-c 图所示,则此时虚拟地址 024BACH 所在的页面是否在主存中?要求说明理由。
| 组号 | 有效位 / 标记 / 页框号(4 路) | |||
|---|---|---|---|---|
| 0 | 0 / — / — | 0 / — / — | 1 / 001 / 15 | 1 / 012 / 1F |
| 1 | 1 / 013 / 2D | 1 / 008 / 7E | 0 / — / — | 0 / — / — |
| 44-c 图 TLB 的部分内容 | ||||
先在纸上自己做一遍,再点这里展开满分解答
考点8.5 分页地址变换8.3 Cache 地址划分8.8 TLB → 页表 → Cache训练营 ⑥
- 虚拟地址空间 16MB = 224 → 虚拟地址 24 位;主存 1MB = 220 → 物理地址 20 位。
- 页 4KB → 页内偏移 12 位;虚页号 = 24 − 12 = 12 位,页框号 = 20 − 12 = 8 位。
- Cache 直接映射 8 行,块 32B:物理地址切成 Tag | 行号 3 位 | 块内 5 位。
- TLB 4 路组相联、共 8 项 → 8 / 4 = 2 组,虚页号的最低 1 位是 TLB 组号,其余 11 位是 TLB 标记。
虚拟地址空间 16MB = 224B,虚拟地址 24 位;页大小 4KB,页内地址 12 位,第 23 ~ 12 位为虚页号。
主存 1MB = 220B,物理地址 20 位,第 19 ~ 12 位为页框号。
块大小 32B → 块内地址 5 位,第 4 ~ 0 位;Cache 共 8 行,直接映射 → 行号 3 位,第 7 ~ 5 位;标记(Tag)= 20 − 3 − 5 = 12 位,第 19 ~ 8 位。
先切虚拟地址:高 12 位是虚页号,低 12 位偏移。查页表得页框号,拼上偏移得物理地址;再把物理地址按 (2) 的字段切开查 Cache。
001C60H 的虚页号 = 001H,页内地址 = C60H。页表中虚页 1 的有效位为 1,页面在主存中,页框号 04H。
物理地址 = 04H ∥ C60H = 04C60H。
04C60H = 0000 0100 1100 | 011 | 0 0000B:Tag = 04CH,行号 = 3。Cache 第 3 行有效位为 1,但标记为 105H ≠ 04CH,Cache 不命中。
TLB 共 8 项、4 路组相联,共 2 组,虚页号最低 1 位为组号,高 11 位为标记。
024BACH 的虚页号 = 024H = 0000 0010 0100B,组号 = 0,标记 = 0000 0010 010B = 012H。
TLB 第 0 组中有有效位为 1、标记为 012H 的表项(页框号 1FH),TLB 命中,所以该页面在主存中。
TLB 标记不是整个虚页号 024H——组相联时要去掉组号位,剩下的 11 位是 012H。直接拿 024H 去比较就会误判为不命中。
- 地址变换两步走:虚页号查页表得页框号,物理地址 = 页框号 ∥ 页内偏移。
- 组相联 TLB 和组相联 Cache 一样:虚页号低位是组号,高位是标记。
- TLB 命中就说明页在主存(TLB 只存有效页的表项)。
模拟大题:两级页表 + 组相联 TLB + 物理 Cache(按 408 风格自编)
这类题的真题(2011-44、2016-45、2018-44、2021-44)都是单级页表。下面这道把两级页表也放进来,同一台机器上走两个虚拟地址:一个从 TLB 一路命中到 Cache,另一个 TLB 缺失、查两级页表后缺页。最后再问页表占多少空间,这是两级页表最常问的“为什么”。
某计算机的虚拟地址 32 位,物理地址 30 位,按字节编址,页大小 4KB。采用两级页表:虚拟地址 = 页目录号(10 位)| 页表号(10 位)| 页内偏移(12 位),页目录项和页表项各占 4B,页目录的起始物理地址放在 PDBR 中。TLB 共 16 项,采用 4 路组相联映射。Cache 数据区 32KB,采用 8 路组相联映射,块大小 64B,用物理地址访问。
当前 PDBR = 0020 0000H。TLB 第 1 组、第 3 组的内容如下(“—”表示与本题无关):
| TLB 组号 | 标记 | 有效位 | 页框号 |
|---|---|---|---|
| 1 | 00201H | 1 | — |
| 1 | 00300H | 1 | — |
| 3 | 00100H | 1 | 0A5F1H |
| 3 | 00101H | 0 | 1B2C0H |
Cache 第 41 组中有一行的 Tag 为 0A5F1H,有效位为 1。页目录第 2 项的内容是:存在位 1,页表所在页框号 00345H。该页表第 1 项的存在位为 0。
(1) TLB 标记、TLB 组号分别是虚拟地址的哪几位?Cache 的 Tag、组号、块内地址分别是物理地址的哪几位?TLB 每项至少多少位(只含标记、有效位和页框号)?(3 分)
(2) 为什么可以在查 TLB 的同时,直接用虚拟地址的低位去选 Cache 的组?(1 分)
(3) CPU 读虚拟地址 0040 3A6CH 中的数据。说明 TLB 和 Cache 是否命中,并给出物理地址。(3 分)
(4) CPU 读虚拟地址 0080 1234H 中的数据。说明访问过程,给出访问的页目录项和页表项的物理地址,以及最后的结果。缺页处理完后重新执行这条指令,这条指令一共要查几次 TLB、读几次页表项(页目录项也算)?设缺页处理程序不修改 TLB,页表项不在 Cache 中。(3 分)
(5) 某进程只用了两段虚拟地址:0000 0000H ~ 003F FFFFH(代码和数据),以及 BFC0 0000H ~ BFFF FFFFH(栈)。它的页目录和页表共占多少空间?如果改用单级页表(每项 4B)呢?(2 分)
先在纸上自己做一遍,再点这里展开满分解答
考点8.5 TLB8.5 分页与两级页表8.8 一次访存的全过程训练营 ⑩ 两级页表
- 页 4KB,页内偏移 12 位;虚页号 20 位;页框号 = 30 − 12 = 18 位。
- TLB 16 项、4 路,所以有 4 组。虚页号的低 2 位是 TLB 组号,高 18 位是 TLB 标记。
- Cache 行数 = 32KB ÷ 64B = 512,8 路,所以有 64 组。块内地址 6 位,组号 6 位,Tag = 30 − 12 = 18 位,正好等于页框号。
虚拟地址:虚页号为第 31~12 位。其中 TLB 组号为第 13~12 位(2 位),TLB 标记为第 31~14 位(18 位)。
物理地址:块内地址为第 5~0 位(6 位),Cache 组号为第 11~6 位(6 位),Tag 为第 29~12 位(18 位)。
TLB 每项至少 = 标记 18 + 有效位 1 + 页框号 18 = 37 位。
组相联 TLB 的标记要去掉组号位,不是整个虚页号(20 位)。Cache 的 Tag 按物理地址(30 位)算,不按虚拟地址算。
Cache 的“组号 + 块内地址”共 6 + 6 = 12 位,恰好等于页内偏移的位数。页内偏移在地址转换时保持不变,所以虚拟地址和物理地址的低 12 位相同,用虚拟地址的第 11~6 位就能选出 Cache 组。查 TLB 与选 Cache 组可以同时进行,拿到页框号后再比较 Tag。
虚页号 = 00403H,二进制最低两位为 11,所以 TLB 组号 = 3,TLB 标记 = 00403H >> 2 = 00100H。
TLB 第 3 组中有标记为 00100H、有效位为 1 的项,TLB 命中,页框号 = 0A5F1H。
物理地址 = 0A5F1H 拼接 A6CH = 0A5F 1A6CH。
A6CH = 1010 0110 1100B:Cache 组号 = 第 11~6 位 101001B = 41,Tag = 0A5F1H。Cache 第 41 组中有 Tag 为 0A5F1H 且有效的行,Cache 命中。
TLB 第 3 组中标记为 00101H 的项有效位为 0,就算标记相同也不能算命中。
先切出页目录号和页表号,再按“基址 + 索引 × 4”走两次。
0080 1234H:页目录号 = 002H,页表号 = 001H,偏移 = 234H。虚页号 = 00801H,TLB 组号 = 1,标记 = 00200H。TLB 第 1 组中没有标记为 00200H 的项,TLB 缺失。
页目录项地址 = PDBR + 2 × 4 = 0020 0008H。该项存在位为 1,页表起始地址 = 00345H × 4KB = 0034 5000H。
页表项地址 = 0034 5000H + 1 × 4 = 0034 5004H。该项存在位为 0,发生缺页异常,由操作系统把该页调入内存并修改页表项。
次数:第一次执行时查 TLB 1 次(缺失)、读页目录项和页表项 2 次,然后缺页。处理完后重新执行这条指令,再查 TLB 1 次(缺失,因为处理程序没有装入 TLB)、再读页目录项和页表项 2 次(这次有效,并把该项装入 TLB)。共查 TLB 2 次,读页表项 4 次。
缺页属于故障,处理完返回后要重新执行这条指令,所以地址转换要再做一遍(2014-45(3) 的考法)。
每张二级页表有 1024 项,管 1024 × 4KB = 4MB 的虚拟地址。0000 0000H ~ 003F FFFFH 正好是页目录第 0 项管的 4MB,BFC0 0000H ~ BFFF FFFFH 正好是第 767 项(BFC0 0000H >> 22 = 2FFH)管的 4MB。所以只需要 2 张二级页表,再加 1 张页目录,共 3 页 = 12KB。
单级页表需要 220 项 × 4B = 4MB,而且必须连续存放。
两级页表省空间,是因为没用到的 4MB 区域不需要分配二级页表。如果进程把整个 4GB 都用满,两级页表反而多出一个页目录(4MB + 4KB)。
- TLB 的“地址”是虚页号,Cache 的“地址”是物理地址,两者分开切。
- “组号 + 块内地址”的位数 ≤ 页内偏移时,可以用虚拟地址直接选 Cache 组。
- 两级页表的每一步都是“基址 + 索引 × 表项大小”;任何一级存在位为 0,都会产生缺页异常。
举一反三:条件一变,答案怎么变
以上面的模拟题为基准,每行只改一个条件。先自己想出新答案和理由,再点开核对。
| 条件怎么变 | 新答案和理由 |
|---|---|
| Cache 改为 32KB、4 路组相联 | 想好了再点组数 128,组号 7 位,“组号 + 块内地址”共 13 位,比页内偏移多 1 位。组号的最高位来自页框号,不能只用虚拟地址选组,要等 TLB 给出页框号。 |
| 页大小改为 8KB(仍为 32 位虚拟地址) | 想好了再点页内偏移 13 位,虚页号 19 位:TLB 组号 2 位,标记 17 位。原 Cache 的“组号 + 块内地址”是 12 位,不超过 13 位,仍可并行查。 |
| TLB 改为全相联 | 想好了再点没有组号,标记就是整个虚页号(20 位),查找时与 16 项同时比较。 |
| 物理地址改为 32 位 | 想好了再点页框号 20 位,Cache 的 Tag 20 位,TLB 每项 18 + 1 + 20 = 39 位。 |
| 改用单级页表 | 想好了再点TLB 缺失时只要读 1 次页表项,但每个进程的页表有 220 项 × 4B = 4MB,而且要连续存放。 |
| 缺页处理程序返回前把新页表项装入 TLB | 想好了再点重新执行时 TLB 命中,第 (4) 问变为共查 TLB 2 次、读页表项 2 次。 |
| 改用反置页表(每个物理页框一项,每项 8B) | 想好了再点物理地址 30 位、页 4KB → 218 个页框,反置页表 = 218 × 8B = 2MB,全系统只要一张,与进程个数、虚拟地址空间大小无关;但查表要按(进程号,虚页号)搜索(常用哈希),(5) 那种“按进程算页表大小”的问法就不成立了(8.5 反置页表)。 |
丢分点清单
- 组相联 TLB 拿整个虚页号当标记去比较(要去掉组号位)。
- Cache 的 Tag 按虚拟地址位数算(应按物理地址)。
- 有效位为 0 的 TLB 项 / Cache 行当成命中。
- 缺页后忘了“重新执行指令”,TLB 次数少算一次。
- 把缺页说成硬件处理,或把 Cache 缺失说成要操作系统处理。
- 十六进制地址直接心算切段,切错位置。
本类其他真题
- 2014-45:接 2014-44 的循环:R2 的终值、指令 Cache 命中率、哪条指令可能溢出/缺页、读磁盘和查 TLB 的次数。
- 2016-45:全相联 TLB + 2 路组相联 Cache:A ~ G 各字段位数、块号 4099 的组号和 Tag、缺页 vs Cache 缺失开销、为什么页面回写。
- 2018-44:看图说话:物理地址位数、TLB 映射方式和存储器类型、Cache 附加位和总容量、一个地址是否命中。
- 2019-46:同一页吗、指令 Cache 的地址划分、call 指令只能在哪组命中。
- 2021-44:2 路组相联 TLB:虚页号、TLB 标记和组号、LRU 替换谁、地址变长后表项多几位。
11.8计组 ⑦ I/O 性能、总线与存储带宽:CPU 到底被占了多少
先读:1.8 性能指标 9.1 总线 9.2 总线事务 9.4 三种 I/O 方式 5.4 中断响应 10.1 磁盘
- 9.1 总线性能指标辨析 —— 时钟周期、传输周期、带宽
- 9.4 DMA 与 CPU 共享主存、DMA 与中断对比 —— 周期挪用、响应时机
- 9.4 四种 I/O 控制方式总表 —— CPU 干预频率
- 8.2 交叉存储器带宽 —— 存储带宽小问
这类题长什么样
一类“算账”题:给出主频、CPI、设备速率、每次中断或 DMA 的开销,问 CPU 被这个设备占用了百分之几、能不能用某种方式、总线/主存带宽够不够、磁盘访问要多久。公式只有几个,难点全在“单位翻译”和“一次传多少”。
| 常见问法 | 考过的地方 |
|---|---|
| 中断 / 查询 / DMA 方式下 CPU 时间占比 | 2009-43,2018-43 |
| 最多隔多久查询一次、能不能用中断方式 | 2018-43(1)(2) |
| 完成一批传送的总时间和 CPU 时间、中断响应做什么 | 2016-44(2) |
| MIPS、每秒 Cache 缺失次数、主存带宽、DMA 请求次数 | 2012-43 |
| 总线带宽、突发传送时间、程序执行时间 | 2013-43 |
| 多体交叉存储器带宽 | 2012-43(4),2013-43(3) |
| 串行通信一帧几位、每秒几个字符 | 2016-44(1) |
| 磁盘地址字段位数、扇区访问时间、DMA 与 CPU 谁优先 | 2022-44,2012-43(3) |
做这类题必须会的 6 件事
① 单位先翻译。频率和速率里的 K、M、G 是 103、106、109(500MHz = 5 × 108 次/秒,2MB/s = 2 × 106 B/s);存储容量、块、页里的 K、M 是 210、220(4KB = 4096B)。时钟周期 = 1 / 主频(500MHz → 2ns);每条指令时间 = CPI × 时钟周期;MIPS = 主频 ÷ (CPI × 106)。
② 三种 I/O 方式,CPU 花时间的地方不一样。
③ 一个万能公式。
④ 中断和 DMA 的过程要会说。中断响应(硬件,隐指令):关中断 → 保存断点(PC)和程序状态 → 识别中断源、取得服务程序入口送 PC。中断服务程序:保护现场 → (开中断)→ 传送数据 → 恢复现场 → 开中断 → 中断返回。DMA:预处理(CPU 设置主存起始地址、传送字数、方向,启动设备)→ 数据传送(DMA 控制器接管总线,周期挪用:每满一个接口缓冲请求一次总线)→ 后处理(传完发中断,CPU 做校验等收尾)。DMA 和 CPU 抢总线时 DMA 优先:设备数据不及时取走会被覆盖而丢失,CPU 等一下只是慢一点。
⑤ 总线和主存的带宽。
⑥ 串行通信与磁盘(计组视角)。异步串行一帧 = 1 位起始位 + 数据位 + 校验位 + 停止位(起始位题目不说也要算)。磁盘地址 = 柱面号 | 磁头号 | 扇区号,各取 ⌈log2个数⌉ 位;访问一个扇区 = 平均寻道 + 转半圈 + 转过一个扇区的时间(磁盘调度、文件分配等操作系统部分见 ⑫)。
解题模板
- 翻译单位。主频 → 每秒周期数、周期长度;速率 → 每秒字节数(106);块/页大小 → 字节数(210)。
- 找“一次传多少”。中断:接口一次交给 CPU 的数据量(32 位 = 4B);DMA:一个数据块;周期挪用:接口缓冲寄存器大小。
- 求每秒次数和每次开销。写出“每秒 × 次,每次 × 个周期”两句。
- 求占比或做比较。占比 = 次数 × 开销 ÷ 主频;可行性 = 比较“处理时间”和“数据间隔”。
- 过程题按步骤列:中断响应三件事、DMA 三个阶段。
假定计算机的主频为 500MHz,CPI 为 4。现有设备 A 和 B,其数据传输率分别为 2MBps 和 40MBps,对应 I/O 接口中各有一个 32 位数据缓冲寄存器。请回答下列问题,要求给出计算过程。
(1) 若设备 A 采用定时查询 I/O 方式,每次输入/输出都至少执行 10 条指令。设备 A 最多间隔多长时间查询一次才能不丢失数据?CPU 用于设备 A 输入/输出的时间占 CPU 总时间的百分比至少是多少?
(2) 在中断 I/O 方式下,若每次中断响应和中断处理的总时钟周期数至少为 400,则设备 B 能否采用中断 I/O 方式?为什么?
(3) 若设备 B 采用 DMA 方式,每次 DMA 传送的数据块大小 1000B,CPU 用于 DMA 预处理和后处理的总时钟周期数为 500,则 CPU 用于设备 B 输入/输出的时间占 CPU 总时间的百分比最大是多少?
先在纸上自己做一遍,再点这里展开满分解答
- 主频 500MHz → 每秒 5 × 108 个周期,1 个周期 2ns;CPI = 4。
- 设备 A 2MB/s、设备 B 40MB/s;接口里各有一个 32 位(4B)数据缓冲寄存器——设备每送满 4B,CPU 就得把它取走,否则下一个 4B 会把它覆盖。
- 所以“多久必须处理一次” = 4B ÷ 数据率:A 是 4B ÷ 2MB/s = 2μs,B 是 4B ÷ 40MB/s = 0.1μs。
接口缓冲寄存器 32 位,设备 A 每 4B ÷ 2MB/s = 2μs 装满一次,为不丢失数据,最多间隔 2μs 必须查询一次。
每秒至少查询 2MB/s ÷ 4B = 5 × 105 次,每次至少 10 × 4 = 40 个时钟周期,CPU 占比至少 = 5 × 105 × 40 ÷ (500 × 106) = 4%。
比较两个时间:处理一次中断要多久?下一个数据多久就到?处理时间比间隔还长,数据必然被覆盖。
不能。每次中断响应和处理至少需要 400 × 2ns = 0.8μs;而设备 B 每 4B ÷ 40MB/s = 0.1μs 就会送来一个新的 32 位数据。中断还没处理完,缓冲寄存器中的数据已经被覆盖多次,会丢失数据。(或:每秒需中断 107 次,需要 107 × 400 = 4 × 109 个周期,超过 CPU 每秒 5 × 108 个周期。)
每秒 DMA 传送次数最多为 40MB/s ÷ 1000B = 4 × 104 次,每次 CPU 开销 500 个周期,CPU 占比最大 = 4 × 104 × 500 ÷ (500 × 106) = 4%。
- 处理间隔上限 = 接口缓冲大小 ÷ 数据率。
- 能否用中断:单次中断处理时间必须小于数据到来的间隔。
- 三种方式的占比都用同一个公式:每秒次数 × 每次周期数 ÷ 主频。
模拟大题:同一台机器上的中断和 DMA(按 408 风格自编)
这类真题(2009-43、2012-43、2016-44、2018-43、2022-44)的核心永远是“每秒多少次 × 每次多少周期 ÷ 每秒总周期”。下面这道自编题把中断方式和 DMA 方式放在同一个磁盘上比较,加上“来不来得及”的判断和周期挪用,是本类问法的完整组合。
某计算机的 CPU 主频为 1GHz,系统总线宽 32 位,总线时钟周期为 10ns,每个总线周期传送 32 位。系统中有打印机和磁盘两台外设。
- 打印机:采用中断方式,每秒打印 2000 个字符,每个字符请求一次中断。每次中断的开销(响应 + 服务程序)为 400 个时钟周期。
- 磁盘:数据传输率为 5.12MB/s(1MB = 106B),扇区大小为 512B,接口中有一个 32 位数据缓冲寄存器。
(1) CPU 用于打印机 I/O 的时间占整个 CPU 时间的百分比是多少?(2 分)
(2) 若磁盘也用中断方式,每次中断传送 32 位,每次中断的开销为 400 个时钟周期。CPU 用于磁盘 I/O 的时间占比是多少?会不会丢失数据?若每次中断的开销为 1000 个时钟周期呢?(3 分)
(3) 磁盘改用 DMA 方式,每个扇区启动一次 DMA,CPU 为每次 DMA 做预处理和后处理共需 1000 个时钟周期。CPU 用于磁盘 I/O 的时间占比是多少?(2 分)
(4) DMA 采用周期挪用方式,每传送 32 位占用一个总线周期。DMA 占用了总线时间的百分之多少?DMA 与 CPU 同时请求总线时,应让谁优先?为什么?DMA 传送结束时,DMA 控制器还要向 CPU 发什么请求?做什么用?(3 分)
先在纸上自己做一遍,再点这里展开满分解答
考点9.4 DMA 与 CPU 共享主存9.4 四种 I/O 方式对比9.1 总线性能指标5.4 中断响应
- 主频 1GHz:每秒 109 个时钟周期,1 个周期 1ns。
- CPU 占比 = 每秒 I/O 次数 × 每次的周期数 ÷ 每秒总周期数。中断方式是每次传一个数据中断一次;DMA 方式是每传一块数据才让 CPU 管一次。
- 速率里的 M 是 106,扇区大小 512B 是 29。5.12 × 106 ÷ 512 = 10 000 个扇区每秒,这个数题目是凑好的。
每秒中断 2000 次,每次 400 个时钟周期。CPU 占比 = 2000 × 400 ÷ 109 = 0.08%。
先算每秒要中断几次;再算两次数据到达的间隔,和每次中断的开销比一比:开销比间隔长,就来不及把数据取走。
每次中断传 4B,每秒中断次数 = 5.12 × 106B/s ÷ 4B = 1.28 × 106 次。
CPU 占比 = 1.28 × 106 × 400 ÷ 109 = 51.2%。
两次数据准备好的间隔 = 4B ÷ 5.12MB/s = 781.25ns,相当于 781.25 个时钟周期。每次中断只用 400 个周期,下一个 32 位数据到来之前处理得完,不会丢失数据,但 CPU 一半以上的时间都花在磁盘上了。
若每次中断开销为 1000 个周期:1000 > 781.25,上一次中断还没处理完,缓冲寄存器里的数据就被下一个数据覆盖,会丢失数据(占比算出来是 128% > 100%,也说明不可能)。
“会不会丢数据”要比较每次中断的开销和两次数据到达的间隔,不能只看占比小于 100%。
每秒传送 5.12 × 106 ÷ 512 = 104 个扇区,即每秒 104 次 DMA。
CPU 占比 = 104 × 1000 ÷ 109 = 1%。
与中断方式的 51.2% 相比大幅下降:数据由 DMA 控制器直接在磁盘接口和主存之间传送,CPU 只在每块数据的开始和结束时参与。
每秒传送 1.28 × 106 个 32 位数据,每个占用一个 10ns 的总线周期。DMA 每秒占用总线 1.28 × 106 × 10ns = 12.8ms,占总线时间的 1.28%。
同时请求总线时,DMA 优先。磁盘是高速设备,数据来了必须及时写入主存,否则会被后来的数据覆盖而丢失;CPU 晚一个存储周期访存,只是稍慢一点,不会出错。
一个扇区传送完,DMA 控制器向 CPU 发出中断请求,CPU 在中断服务程序中做后处理:检查传送是否正确,决定是继续传下一块还是结束,并唤醒等待这次 I/O 的进程。
DMA 方式中也有中断,但只在“一整块传完”时中断一次,而不是每个数据中断一次。DMA 请求在每个存储周期(总线周期)结束时响应;中断请求在指令执行结束时响应。
- CPU 占比 = 次数/秒 × 周期/次 ÷ 主频;中断按“数据”数次数,DMA 按“块”数次数。
- “来不来得及”要比较每次处理的时间和数据到达的间隔。
- 周期挪用占的是总线周期,不是 CPU 的时钟周期。DMA 的优先级高于 CPU,因为高速设备的数据不能等。
举一反三:条件一变,答案怎么变
以上面的模拟题为基准,每行只改一个条件。先自己想出新答案和理由,再点开核对。
| 条件怎么变 | 新答案和理由 |
|---|---|
| 主频改为 500MHz | 想好了再点每秒的周期数减半,各项占比翻倍:打印机 0.16%;磁盘用中断方式为 102.4%,不可能完成;DMA 为 2%。 |
| 接口数据寄存器改为 16 位(中断方式,每次 400 周期) | 想好了再点每秒中断 2.56 × 106 次,两次数据的间隔只有约 390.6 个周期,小于 400,来不及,会丢数据。 |
| DMA 每次传送 4KB(8 个扇区) | 想好了再点每秒 5.12 × 106 ÷ 4096 = 1250 次 DMA,CPU 占比 = 1250 × 1000 ÷ 109 = 0.125%。 |
| 总线周期改为 20ns | 想好了再点DMA 占用的总线时间 = 1.28 × 106 × 20ns = 2.56%。 |
| 改用程序查询方式 | 想好了再点CPU 要不停地查询设备状态,查询速度还必须跟得上数据到达的速度,CPU 几乎全被占用。这种方式只适合低速设备。 |
| 问每传送一个扇区,CPU 要处理几次中断 | 想好了再点DMA 方式下只有 1 次:一块数据传完时 DMA 控制器发中断,CPU 做后处理。 |
| DMA 改用“停止 CPU 访存”方式:每个扇区传送期间 DMA 一直占用主存 | 想好了再点磁盘每秒送来 5.12MB ÷ 512B = 104 个扇区,每个扇区要 100μs 才送完。磁盘连续工作时 104 × 100μs = 1s,CPU 始终不能访存;而周期挪用只占 1.28% 的总线时间(程序核对)。这就是停止 CPU 访存方式的缺点(9.4 三种方式)。 |
丢分点清单
- 速率里的 M 当成 220(或反过来把 4KB 当 4000B)——题目两种都出现时要分清。
- 中断次数按“字节”算了(应按每次传送的单位,如 4B)。
- DMA 开销也按每个字算(应按每个数据块)。
- 串行通信漏掉起始位。
- 2016-44 类题目:把“总时间”和“CPU 时间”混为一谈。
- 多体交叉带宽按单体算,或者突发传送把每个体的 40ns 都加上。
- “DMA 优先”只写结论不写理由(数据会丢失)。
本类其他真题
- 2009-43:中断方式和 DMA 方式的 CPU 占比(11.1 里的答题示范就是它的第 1 问)。
- 2012-43:MIPS、缺失次数、主存带宽、缺页次数、DMA 请求次数、DMA 优先级、四体交叉带宽。
- 2013-43:CPU/总线周期、总线带宽、突发传送时间、含 Cache 缺失的执行时间。
- 2016-44:异步串行一帧、中断方式传 1000 个字符的总时间与 CPU 时间、中断响应。
- 2022-44:磁盘地址字段、扇区平均访问时间、DMA 请求次数与优先级。
11.9操作系统 ⑧ PV 同步互斥:从读题到写出不死锁的代码
先读:7.1 为什么并发会出错 7.2 实现互斥 7.3 信号量 7.4 PV 大题五步法 7.6 死锁
- 7.3 记录型信号量代码与取值范围 —— value 的含义
- 7.4 经典模板补全(水果盘、吸烟者、读写公平、哲学家三写法) —— 大题基本都是它们的变形
- 7.7 银行家算法请求例 —— 试分配 + 安全性检查的写法
这类题长什么样
操作系统大题里考得最多的一类:18 年里有 13 道(几乎每年一道)。题目讲一个生活场景(信箱、银行、博物馆、植树、哲学家吃饭),要求“添加信号量和 P、V 操作,写出完整过程,说明信号量含义和初值”;另有少数题问“某段互斥代码对不对”。评分看三样:信号量定义和初值、每个进程的完整代码、关系是否正确(不死锁、并发度够、信号量不多余)。
| 题型 | 考过的地方 |
|---|---|
| 生产者—消费者及变形(多类产品、多生产多消费、两个缓冲区) | 2009-45,2014-47,2015-45 |
| 容量 + 通道 / 服务台(“最多 n 个”“一次只能一个”“叫号”) | 2011-45,2013-45 |
| 前驱关系(操作 A 在 B 之后) | 2020-45,2022-46 |
| 最大并发的互斥(读者—写者思想) | 2017-46 |
| 哲学家进餐 | 2019-43 |
| 流水作业 + 共享工具,信号量尽可能少 | 2024-46,2025-45 |
| 分析互斥实现是否正确(关中断、swap) | 2021-45,2023-45 |
做这类题必须会的 5 件事
① 信号量的值是什么意思。value > 0:还有 value 个资源可用;value = 0:刚好用完,没人在等;value < 0:没有资源,并且有 |value| 个进程在排队等。P(wait)= “申请一个,不够就排队睡觉”;V(signal)= “还回一个,有人排队就叫醒一个”。
② 信号量只有三种用法,初值一眼能看出来。
③ 经典模型地图——看到这些字眼,就知道套哪个。
| 题目里出现 | 对应模型 | 信号量怎么设 | 真题 |
|---|---|---|---|
| 缓冲区、放入/取出、满了等、空了等 | 生产者—消费者 | empty = n,full = 0,mutex = 1 | 2009-45,2014-47 |
| 缓冲区里有几类东西,不同的人取不同的 | 多类产品 | 每类一个同步量(odd、even),生产者放完按类 V | 2009-45 |
| 某人要“连续”做 k 次别人才能做 | 加一层同类互斥 | mutex2 = 1 包住 k 次循环 | 2014-47 |
| 两个缓冲区、互相往对方那里放 | 两套生产者—消费者 | Full_A/Empty_A/mutex_A 和 B 各一套,初值按题目给 | 2015-45 |
| 最多容纳 n 人,出入口一次一人 | 容量 + 通道 | empty = n;mutex = 1 只包“过门” | 2013-45 |
| 座位、叫号、服务员没人时休息 | 服务台(睡眠理发师) | 座位 empty = n;顾客→服务员 full = 0;服务员→顾客 service = 0 | 2011-45 |
| A、B 完成后才能做 C | 前驱图 | 每条(跨进程的)边一个,初值 0,前 V 后 P | 2020-45,2022-46 |
| 有人只读、有人要写;“最大程度并发” | 读者—写者 | 读读不互斥;每对“读者—写者”一个 mutex,写者全拿 | 2017-46 |
| 围成一圈,每人要左右两个资源 | 哲学家进餐 | 每个资源一个 mutex;再限制同时拿的人数 ≤ n − 1 | 2019-43 |
| 几道工序接力 + 几个人共用一件工具 | 流水作业 | 相邻工序之间一个“半成品数”;共享工具一个 mutex;上限一个计数量 | 2025-45 |
| “定义尽可能少的信号量” | 按实际约束设 | 只有先后 → 一个同步量;只怕同时 → 一个互斥量;独占的资源不设 | 2024-46,2025-45 |
④ 两个没真考过大题、但必须会写的经典模板。
读者—写者(读者优先)
int count = 0; // 正在读的人数
semaphore rw = 1; // 读写互斥
semaphore mutex = 1; // 保护 count
writer() {
P(rw); 写; V(rw);
}
reader() {
P(mutex);
if (count == 0) P(rw); // 第一个读者上锁
count++;
V(mutex);
读;
P(mutex);
count--;
if (count == 0) V(rw); // 最后一个读者解锁
V(mutex);
}
缺点:读者源源不断时写者会饿死。加一个 semaphore w = 1,读者和写者进门前都先 P(w)(读者拿到 rw 后立刻 V(w)),就成了“读写公平”版。
吸烟者(拓展,经典教材题)
semaphore offer1 = 0, offer2 = 0, offer3 = 0;
semaphore finish = 0; // 抽完一支
provider() {
while (1) {
随机放两种材料 i;
V(offer_i); // 通知缺这两种的人
P(finish); // 等他抽完
}
}
smoker_i() {
while (1) {
P(offer_i);
卷烟、抽烟;
V(finish);
}
}
本质是“一个生产者、三类产品、每类一个消费者,且一次只放一份”,和 2009-45 的“奇偶”是一回事。
⑤ 分析互斥实现对不对(2021-45、2023-45 这类)。检查三件事:互斥(会不会两人同时进)、能前进(会不会谁都进不去、或关着中断死循环卡死系统)、原子性(“测试 + 设置”是不是一步完成,普通函数不行)。另外记住:开/关中断是特权指令,用户程序不能用。
解题模板(五步法)
- 找进程、写骨架。有几类进程?每类是循环(while(1))还是只做一次(顾客、参观者)?把题目给的动作按顺序抄下来。
- 逐句找约束。对每一句话问:是“不能同时”(互斥)、“先……才能……”(同步)、还是“最多/有 n 个”(资源)?列成清单。
- 一个约束一个信号量。写清名字、含义、初值(初值按题目给的初始状态:信箱初始有 x 封 → Full = x)。题目要求尽可能少时,删掉只有一个进程用的资源。
- 放 P、V。互斥:P、V 紧夹临界区;同步:前 V 后 P;资源:用前 P、用完 V(谁消耗谁 P,谁产生谁 V)。
- 检查四遍。① 每个进程里先 P 资源/同步量,再 P(mutex);② 拿着一个信号量去等另一个时,会不会形成环(死锁);③ 临界区外的动作(生产、消费、参观)有没有被错误锁住(并发度);④ 逐句对照题意,每个约束都落实了。
三个人一起植树,甲挖坑,乙放树苗入坑并填土,丙负责为新种树苗浇水。步骤依次为:挖树坑,放树苗,填土和浇水。现在有铁锹和水桶各一个,铁锹用于挖树坑,填土。水桶用于浇水。当树坑数量小于 3 时,甲才可以挖树坑。设初始坑 = 0,铁锹水桶均可用,定义尽可能少的信号量,用 wait() 和 signal() 操作描述植树过程中三人的同步互斥关系,并说明所用信号量的作用及其初值。
先在纸上自己做一遍,再点这里展开满分解答
考点7.4 PV 五步法(流水作业 + 共享工具)7.6 死锁训练营 ⑧
- 流程:甲挖坑 → 乙放树苗、填土 → 丙浇水。
- “树坑数量小于 3 时甲才可以挖” → 挖好但还没填土的坑最多 3 个 → pit = 3(还能再挖几个坑)。
- 乙要有挖好的坑才能放树苗 → hole = 0(已挖好、待种的坑数)。
- 丙要有种好(填好土)的树苗才能浇水 → tree = 0(待浇水的树苗数)。
- 铁锹只有一把,甲挖坑、乙填土都要用 → shovel = 1(互斥)。
- 水桶只有丙一个人用,没人跟他抢 → 不需要信号量(题目要求尽可能少)。
semaphore pit = 3; // 还允许挖的树坑数(坑数小于 3 才能挖),初值 3
semaphore hole = 0; // 已挖好、待放树苗的坑数,初值 0
semaphore tree = 0; // 已种好(填土)、待浇水的树苗数,初值 0
semaphore shovel = 1; // 铁锹的互斥使用,初值 1
甲() {
while (1) {
wait(pit); // 坑数小于 3 才能挖
wait(shovel);
挖树坑;
signal(shovel);
signal(hole); // 多了一个待种的坑
}
}
乙() {
while (1) {
wait(hole); // 有坑才能种
放树苗;
wait(shovel);
填土;
signal(shovel);
signal(pit); // 坑被填上,坑数减少,甲又可以挖
signal(tree); // 通知丙浇水
}
}
丙() {
while (1) {
wait(tree); // 有种好的树苗才浇水
用水桶浇水;
}
}
① 甲必须先 wait(pit) 再 wait(shovel):反过来时,坑已满 3 个的情况下甲拿着铁锹等坑位,乙要铁锹填土才能腾出坑位,二者互相等待 → 死锁。② 给水桶设信号量不算错,但不满足“尽可能少”。③ signal(pit) 放在填土之后:坑被填上才不再算“树坑”。
- 流水作业:相邻两道工序之间各设一个“产品数”信号量(hole、tree)。
- “数量小于 k 才能做” → 初值 k 的资源信号量,做完的一方 P,消耗掉的一方 V。
- 只有一个进程使用的资源不需要信号量。
模拟大题:单车道桥(按 408 风格自编)
PV 大题最常见的“举一反三”,就是把读者—写者换个场景:同方向的车 = 同一类读者(可以一起上桥),相反方向 = 互斥。再加一个“最多 k 辆”的容量限制,就把计数器、方向锁、资源计数三种信号量用法放进了一道题。后面两问考的是 PV 题最容易丢分的两件事:饥饿和死锁。
一座东西向的单车道桥:同一方向的车可以一辆接一辆地同时在桥上行驶,相反方向的车不能同时在桥上;由于承重限制,桥上最多同时有 k 辆车。每辆车是一个进程,只过一次桥。
(1) 用信号量和 P、V 操作描述东来车辆和西来车辆的同步过程,说明每个信号量的含义和初值。(5 分)
(2) 你的算法会不会让某一方向的车长时间过不了桥?若会,怎样修改,使车辆大体按到达的先后顺序过桥?(2 分)
(3) 有同学把 P(cap) 写在了“进桥”那段 P(mE) 和 V(mE) 之间,又把下桥时的 V(cap) 挪到了“cntE−−”那段 P(mE) 之后。这样会出现什么问题?(1 分)
先在纸上自己做一遍,再点这里展开满分解答
考点7.4 PV 大题五步法7.5 经典问题7.6 死锁训练营 ⑧ 读者—写者模板
- 桥是一个要互斥使用的资源,但互斥的是两个方向,不是每辆车:这就是读者—写者里“读者之间不互斥”的做法,只不过两边都是“读者”。
- 每个方向一个计数器:第一辆上桥的车“抢桥”,最后一辆下桥的车“还桥”;计数器本身要互斥修改。
- 最多 k 辆:一个初值为 k 的资源信号量,上桥前 P,下桥后 V。
int cntE = 0, cntW = 0; // 已取得通行方向的东来、西来车辆数
semaphore mE = 1, mW = 1; // 互斥访问 cntE、cntW
semaphore bridge = 1; // 桥的使用权(哪个方向在用)
semaphore cap = k; // 桥上的空位数
东来车辆() {
P(mE);
if (cntE == 0) P(bridge); // 东向第一辆:抢桥
cntE++;
V(mE);
P(cap); // 占一个空位
过桥;
V(cap);
P(mE);
cntE--;
if (cntE == 0) V(bridge); // 东向最后一辆:还桥
V(mE);
}
西来车辆() { // 与东来对称,cntE、mE 换成 cntW、mW
P(mW);
if (cntW == 0) P(bridge);
cntW++;
V(mW);
P(cap);
过桥;
V(cap);
P(mW);
cntW--;
if (cntW == 0) V(bridge);
V(mW);
}
cntE 只能在 mE 的保护下读和改:“判断 cntE == 0”和“cntE++”必须在同一个临界区里,否则两辆东来的车可能都以为自己是第一辆,都去 P(bridge),第二辆就会被自己方向的车挡住。
会。只要东来的车源源不断,cntE 就一直不为 0,东向始终不还桥,西来的车会一直等在 P(bridge) 上,发生饥饿。
修改:增加信号量 semaphore queue = 1;,让所有车在“取得方向”这一段之前先排队:
东来车辆() {
P(queue); // 按到达顺序排队
P(mE);
if (cntE == 0) P(bridge);
cntE++;
V(mE);
V(queue);
P(cap); 过桥; V(cap);
P(mE);
cntE--;
if (cntE == 0) V(bridge);
V(mE);
} // 西来车辆对称修改
西来的车到达后,拿着 queue 等在 P(bridge) 上。之后到达的东来车辆都被挡在 P(queue) 外,不能再上桥。桥上的东来车辆走完后还桥,西来的车就能通过。
这和本节“读者—写者”模板下面讲的“读写公平”改法完全一样:所有进程进门前先 P 同一个排队信号量,拿到通行权后立刻 V 它。
会死锁。桥上已有 k 辆东来的车时,新来的东来车辆拿着 mE 阻塞在 P(cap) 上。桥上的车下桥时要先 P(mE) 才能修改 cntE、之后才执行 V(cap),而 mE 被等待者占着,于是它们也阻塞,永远不会执行 V(cap)。双方互相等待,形成死锁。
如果只挪了 P(cap)、下桥时仍是“先 V(cap) 再 P(mE)”,则不会死锁:桥上的车不用拿 mE 就能还出空位(程序穷举所有交错执行核对过)。但拿着 mE 等空位期间,同方向后来的车连计数都做不了,下桥的车也要等着改 cntE,并发度明显下降。
PV 题的一条通用规矩:拿着互斥信号量时,不要去 P 一个可能阻塞的资源信号量(生产者—消费者里“先 P(empty) 再 P(mutex)”也是这个道理)。
- “同类可以一起、异类互斥”都用计数器 + 第一个上锁、最后一个解锁的模板。
- 有容量限制就再加一个初值为 k 的资源信号量。
- 检查两件事:会不会有一方饿死(加排队信号量);有没有拿着 mutex 去等别的信号量(会死锁)。
举一反三:条件一变,答案怎么变
以上面的模拟题(单车道桥)为基准,每行只改一个条件。先自己想出新答案和理由,再点开核对。
| 条件怎么变 | 新答案和理由 |
|---|---|
| 桥上的车数不限 | 想好了再点去掉 cap 的 P、V,其余不变。 |
| 桥上一次只能有 1 辆车(不管方向) | 想好了再点只要一个 semaphore bridge = 1。每辆车 P(bridge)、过桥、V(bridge),计数器都不需要。 |
| 两个方向的车必须一辆一辆交替过桥(东边先) | 想好了再点用两个同步信号量 turnE = 1、turnW = 0。东来车辆:P(turnE)、过桥、V(turnW);西来车辆:P(turnW)、过桥、V(turnE)。 |
| 只有从东往西一个方向,最多 k 辆 | 想好了再点只要一个资源信号量 cap = k。 |
| 阅览室:读者可以同时阅读,管理员整理时不能有读者 | 想好了再点读者按本题“东来车辆”的写法(计数器,第一个进来的上锁,最后一个出去的解锁);管理员 P(room)、整理、V(room)。 |
丢分点清单
- 没写信号量的含义或初值(这是单独的得分点)。
- 先 P(mutex) 再 P(资源),缓冲区满/空时死锁。
- 把不访问共享资源的动作(生产、消费、参观、思考)也放进临界区,降低并发。
- 初值写错:题目给了初始数量(x、y、M − x)却写成 0 或满。
- 只执行一次的进程写成 while 循环,或循环进程漏了 while。
- “最大并发”题只用一把锁,读—读也互斥了。
- “尽可能少”题设了多余的信号量(水桶、同一线程内的先后)。
- V 放得太晚(2022-46 把 V 放到 D 之后),无谓地降低并发。
本类其他真题
- 2009-45:一个生产者、奇数/偶数两类消费者。
- 2011-45:银行叫号:座位、取号机、双向同步。
- 2013-45:博物馆:容量 500 + 出入口互斥。
- 2014-47:多生产者多消费者 + 一个消费者连续取 10 件。
- 2015-45:A、B 信箱辩论:两套生产者—消费者交叉,初值按 x、y、M、N。
- 2017-46:三个线程读写复数 y、z,要求最大程度并发。
- 2019-43:n 个哲学家、m 个碗。
- 2020-45 / 2022-46:前驱关系;后者只为跨线程的边设信号量。
- 2021-45:用开/关中断实现 wait、signal 的两种方法谁对。
- 2023-45:用 swap 指令实现互斥:改错、能否用普通函数代替。
- 2024-46:缓冲区 B 的三种操作,信号量尽可能少。
11.10操作系统 ⑨ CPU 调度:画甘特图、算周转、设计优先数
先读:6.2 进程状态 6.6 调度的时机与标准 6.7 调度算法
- 6.7 各算法考点细节 —— RR 入队约定、多级反馈规则、优先级设置
- 6.7 五种算法同题对比 —— 画甘特图、算周转与带权周转
- 6.6 周转、带权周转、响应时间 —— 指标定义
这类题长什么样
调度作为大题只出过两次(2016-46 设计优先数、2026-45 画甘特图数调度次数),但它是操作系统选择题里出题最多的单个考点。两类问法:给一组进程和一种算法,算周转/等待时间或调度次数;问某种算法的性质(会不会饥饿、是不是抢占、对谁有利)。
| 常见问法 | 考过的地方 |
|---|---|
| 画甘特图,算平均周转 / 等待时间、调度次数、首次调度时刻 | 2026-45,2012-29,2016-24,2017-23,2018-24,2019-27,2022-25,2023-29,2024-30 |
| 为什么会饥饿,设计动态优先数 | 2016-46 |
| 时间片、时钟中断间隔改变的影响 | 2026-45(2) |
| 算法的特点(公平、饥饿、适合谁、要用哪些数据结构) | 2009-24,2010-26,2011-23,2013-31,2014-23,2017-27,2020-26,2021-25,2025-25 |
| 什么时候会发生调度、时钟中断做什么 | 2012-30,2018-29,2021-27 |
做这类题必须会的 5 件事
① 四个时间指标。
② 六种算法一张表。
| 算法 | 怎么选下一个 | 抢占? | 饥饿? | 一句话特点 |
|---|---|---|---|---|
| FCFS 先来先服务 | 最早到达的 | 否 | 否 | 公平但对短作业不利 |
| SJF 短作业优先 | 运行时间最短的 | 非抢占;抢占版叫 SRTN | 会(长作业) | 平均周转时间最短 |
| 优先级 | 优先级最高的 | 两种都有 | 会(静态时) | 动态提高等待者的优先级(老化)可避免饥饿 |
| HRRN 高响应比 | 响应比最大的 | 否 | 否 | 兼顾短作业和等待久的作业 |
| RR 时间片轮转 | 就绪队列队首,用完时间片排队尾 | 是(时间片到) | 否 | 分时系统用;时间片太大退化为 FCFS,太小切换开销大 |
| 多级反馈队列 | 高优先级队列优先,每级时间片不同,用完降级 | 是 | 可能(长进程沉底) | 综合了前几种的优点 |
③ 什么时候调度、什么时候不能调度。会发生调度:当前进程运行结束、阻塞(等 I/O)、时间片用完、(抢占式下)有更高优先级进程就绪、中断/系统调用返回用户态之前。不能调度:正在处理中断时、正在执行原语或操作内核临界区时、正在进行进程切换时。另外,没有就绪进程时 CPU 运行闲逛进程(idle),它的优先级最低,一有进程就绪就让出 CPU。
④ 题目规则永远优先。2026-45 规定“只有时钟中断时才抢占”,12ms 到的高优先级进程也要等到 20ms;“优先数”小优先还是“优先级”大优先;同级按到达先后还是按进入就绪队列先后;时间片用完的进程与新到达的进程同时进队谁在前(408 常见约定:新到达的在前)。
⑤ 饥饿与老化。饥饿 = 某个就绪进程长时间得不到 CPU(它不是死锁,别的进程在正常推进)。静态优先级、SJF 都可能让某些进程饥饿;解决办法是让等待时间参与优先级计算,等得越久优先级越高(2016-46)。
解题模板
- 抄规则。算法、是否抢占、何时可抢占、优先级方向、同级怎么排、时间片多长、时间片用完怎么处理。
- 列到达表。进程、到达时刻、运行时间、优先级。
- 找决策点,逐个推。每个决策点写出“就绪队列:进程(优先级,进队时刻,剩余时间)”,按规则选一个,写下它运行到下一个决策点。
- 画甘特图。标出每段的起止时刻,核对总时长 = 各进程运行时间之和(加上空闲时间)。
- 算指标。列表:完成时刻、周转、带权周转、等待;再求平均。
- 数次数。调度次数 = 甘特图上“换人/开始”的次数(同一进程接着运行不算);中断次数按题目规定的间隔数。
(本题满分 7 分)系统采用优先级(优先级越大表示优先级越高)与时间片轮转调度算法,仅当发生时钟中断时才触发抢占 CPU 操作,时钟中断间隔为 10ms。进程首次进入就绪队列时,其时间片为 50ms。若进程因时间片用完而返回就绪队列,其优先级值减 1;若进程被更高优先级进程抢占而返回就绪队列,其优先级值保持不变。当多个进程优先级相同时,先进入就绪队列的进程优先被调度。四个进程的到达时刻、初始优先级与 CPU 运行时间如下表所示:
| 进程 | 到达就绪队列时间(ms) | 优先级 | CPU 运行时间(ms) |
|---|---|---|---|
| P1 | 10 | 3 | 95 |
| P2 | 10 | 4 | 20 |
| P3 | 12 | 2 | 40 |
| P4 | 14 | 5 | 60 |
(1) 从 10ms 开始进程调度,直至所有进程调度结束,此时中断次数与 CPU 调度次数分别为多少?P1、P2、P3、P4 各自的首次调度发生在哪个时刻?(5 分)
(2) 若时间片由 50ms 改为 100ms,CPU 调度次数将增大、不变还是减少?若时钟中断间隔由 10ms 改为 1ms,系统开销将增大、不变还是减少?(2 分)
先在纸上自己做一遍,再点这里展开满分解答
考点6.7 优先级 + 时间片轮转、画甘特图5.3 时钟中断训练营 ⑨
说明:2026 年真题的官方答案尚未公开,下面按题目给出的规则逐步推演。
- 优先级数值越大越优先;同优先级按进入就绪队列的先后。
- 只在时钟中断时刻(10、20、30……ms)才检查抢占;进程运行完则立即调度下一个。
- 时间片 50ms:时间片用完 → 优先级 − 1,回到就绪队列尾;被更高优先级抢占 → 优先级不变,回队尾。
- P1(到达 10,优先级 3,95ms)、P2(10,4,20ms)、P3(12,2,40ms)、P4(14,5,60ms)。
从 10ms 开始一步一步推,每一步写清“此刻就绪队列里有谁、各自优先级、进队时刻”。
| 时刻 | 事件 | 调度 |
|---|---|---|
| 10 | P1(3)、P2(4) 到达,CPU 空闲 | ① 选 P2 |
| 12、14 | P3(2)、P4(5) 到达,不是时钟中断,不抢占 | — |
| 20 | 时钟中断:P4(5) > P2(4),抢占;P2 已运行 10、剩 10,优先级仍 4,进队(20) | ② 选 P4 |
| 30~60 | 时钟中断,无更高优先级 | — |
| 70 | P4 时间片用完(剩 10),优先级 5 → 4,进队(70);P2(4, 20) 与 P4(4, 70) 同级,P2 先进队 | ③ 选 P2 |
| 80 | P2 运行完 | ④ 选 P4(4) |
| 90 | P4 运行完 | ⑤ 选 P1(3) |
| 140 | P1 时间片用完(剩 45),优先级 3 → 2,进队(140);P3(2, 12) 先进队 | ⑥ 选 P3 |
| 180 | P3 运行完 | ⑦ 选 P1 |
| 225 | P1 运行完,全部结束 | — |
甘特图:10~20 P2,20~70 P4,70~80 P2,80~90 P4,90~140 P1,140~180 P3,180~225 P1。
CPU 调度次数 = 7(10、20、70、80、90、140、180 时刻)。
时钟中断每 10ms 一次,从 10ms 到所有进程结束(225ms)共发生于 10、20、…、220ms,中断次数 = 22。
首次调度时刻:P1 为 90ms,P2 为 10ms,P3 为 140ms,P4 为 20ms。
中断次数的统计口径(是否计入 10ms 那一次、是否算到最后一次调度为止)题面没有说死,考场上写清“统计的是 10ms 至 225ms 之间的时钟中断,共 22 次”,按步骤给分。
时间片 100ms 时:10 调度 P2;20 P4 抢占;P4 运行 60ms 在 80 完成,调度 P2;90 P2 完成,调度 P1;P1 的 95ms 不超过时间片,185 完成,调度 P3。共 5 次,调度次数减少(时间片变长,因时间片用完而引起的调度变少)。
时钟中断间隔改为 1ms:中断次数增加为原来的 10 倍,每次中断都要执行时钟中断处理程序,系统开销增大。
- 抢占式调度也可能只在特定时刻(时钟中断)检查抢占——一定按题目规则来。
- 画甘特图时,每个决策点都写出“就绪队列(优先级、进队时刻)”,同级比进队时刻。
- 时间片越长,调度次数越少;时钟中断越频繁,系统开销越大。
模拟大题:同一组进程,三种算法(按 408 风格自编)
调度出过的两道大题,一道设计优先数(2016-46),一道画优先级 + 时间片的甘特图(2026-45)。选择题里最常考的是 HRRN、SRTN 和 RR 这几种算法的计算,所以这道题用同一组进程把三种算法各推一遍,最后比较它们的性质。所有结果都用程序模拟核对过。
单 CPU 系统中有 5 个进程,到达时刻和 CPU 运行时间如下表所示(单位 ms),进程在运行过程中不做 I/O,忽略进程切换的开销。
| 进程 | P1 | P2 | P3 | P4 | P5 |
|---|---|---|---|---|---|
| 到达时刻 | 0 | 1 | 2 | 3 | 6 |
| 运行时间 | 8 | 4 | 9 | 5 | 2 |
(1) 采用非抢占式高响应比优先(HRRN)调度。写出每次调度时各就绪进程的响应比,给出调度顺序,求平均周转时间和平均带权周转时间。(3 分)
(2) 采用抢占式最短剩余时间优先(SRTN)调度:新进程到达时,若它的运行时间小于当前进程的剩余时间,就抢占 CPU;剩余时间相同时,先到达的优先。画出甘特图,求平均周转时间和平均等待时间。(3 分)
(3) 采用时间片轮转(RR)调度,时间片为 3ms。进程到达后插入就绪队列尾;若新进程到达与某进程时间片用完发生在同一时刻,新到达的进程先进队。画出甘特图,求平均周转时间。CPU 调度共发生多少次(从 0 时刻第一次调度算起,同一进程连续运行不算新的调度)?P5 的首次调度发生在哪个时刻?(3 分)
(4) 本题中哪种算法的平均周转时间最短?SRTN 有什么缺点?HRRN 为什么不会出现这个缺点?RR 的时间片取多大时会退化为先来先服务?(1 分)
先在纸上自己做一遍,再点这里展开满分解答
考点6.7 每种算法的考点细节6.7 一组进程五种算法对比6.6 调度的标准
- 周转时间 = 完成时刻 − 到达时刻;带权周转时间 = 周转时间 ÷ 运行时间;等待时间 = 周转时间 − 运行时间。
- 响应比 R = 1 + 已等待时间 ÷ 运行时间。HRRN 是非抢占的,只在当前进程运行完时才算响应比。
- SRTN 在每个新进程到达的时刻都要比一次;RR 按“时间片用完”和“进程结束”两种事件推进。RR 在同一时刻进队的先后顺序题目已经规定:新到达的在前。
0 时刻只有 P1,它先运行到 8。之后每当一个进程结束,就对所有已到达的进程算一次响应比,选最大的。
| 决策时刻 | 各就绪进程的响应比 R = 1 + 等待 ÷ 运行 | 选中 |
|---|---|---|
| 0 | 只有 P1 | P1(0~8) |
| 8 | P2:1 + 7/4 = 2.75;P3:1 + 6/9 ≈ 1.67;P4:1 + 5/5 = 2;P5:1 + 2/2 = 2 | P2(8~12) |
| 12 | P3:1 + 10/9 ≈ 2.11;P4:1 + 9/5 = 2.8;P5:1 + 6/2 = 4 | P5(12~14) |
| 14 | P3:1 + 12/9 ≈ 2.33;P4:1 + 11/5 = 3.2 | P4(14~19) |
| 19 | 只剩 P3 | P3(19~28) |
调度顺序:P1 → P2 → P5 → P4 → P3。
| 进程 | P1 | P2 | P3 | P4 | P5 |
|---|---|---|---|---|---|
| 完成时刻 | 8 | 12 | 28 | 19 | 14 |
| 周转时间 | 8 | 11 | 26 | 16 | 8 |
| 带权周转时间 | 1 | 2.75 | ≈ 2.89 | 3.2 | 4 |
平均周转时间 = (8 + 11 + 26 + 16 + 8) ÷ 5 = 13.8ms;平均带权周转时间 = (1 + 2.75 + 2.89 + 3.2 + 4) ÷ 5 ≈ 2.77。
等待时间要用“决策时刻 − 到达时刻”,例如 8 时刻 P2 已经等了 8 − 1 = 7ms。HRRN 不抢占:P2 在 1 时刻到达时 P1 照样运行到 8。
每个到达时刻(1、2、3、6)和每个完成时刻都比较一次“剩余时间”,最小的运行。
1 时刻:P2(4)< P1 剩余 7,P2 抢占。2、3 时刻:P3(9)、P4(5)都不小于 P2 的剩余时间,P2 继续运行。5 时刻:P2 完成,在 P1(7)、P3(9)、P4(5)中选 P4。6 时刻:P5(2)< P4 剩余 4,P5 抢占。8 时刻:P5 完成,选 P4(剩余 4)。12 时刻:P4 完成,选 P1(7)。19 时刻:选 P3。
甘特图:0~1 P1,1~5 P2,5~6 P4,6~8 P5,8~12 P4,12~19 P1,19~28 P3。
| 进程 | P1 | P2 | P3 | P4 | P5 |
|---|---|---|---|---|---|
| 完成时刻 | 19 | 5 | 28 | 12 | 8 |
| 周转时间 | 19 | 4 | 26 | 9 | 2 |
| 等待时间 | 11 | 0 | 17 | 4 | 0 |
平均周转时间 = 60 ÷ 5 = 12ms;平均等待时间 = 32 ÷ 5 = 6.4ms。
抢占时比较的是新进程的运行时间和当前进程的剩余时间,不是和它的总运行时间比。5 时刻选的是 P4(5),不是更早到达的 P1(剩余 7)。
每个决策点写出“就绪队列(从队头到队尾)”。3 时刻 P4 到达、P1 时间片用完,6 时刻 P5 到达、P2 时间片用完,这两处都按“新到达的先进队”排。
| 时刻 | 事件 | 就绪队列(队头 → 队尾) | 运行 |
|---|---|---|---|
| 0 | P1 到达 | P1 | P1(0~3) |
| 3 | P2、P3 已在队中;P4 到达;P1 用完时间片(剩 5) | P2、P3、P4、P1 | P2(3~6) |
| 6 | P5 到达;P2 用完时间片(剩 1) | P3、P4、P1、P5、P2 | P3(6~9) |
| 9 | P3 剩 6 | P4、P1、P5、P2、P3 | P4(9~12) |
| 12 | P4 剩 2 | P1、P5、P2、P3、P4 | P1(12~15) |
| 15 | P1 剩 2 | P5、P2、P3、P4、P1 | P5(15~17)完成 |
| 17 | — | P2、P3、P4、P1 | P2(17~18)完成 |
| 18 | — | P3、P4、P1 | P3(18~21) |
| 21 | P3 剩 3 | P4、P1、P3 | P4(21~23)完成 |
| 23 | — | P1、P3 | P1(23~25)完成 |
| 25 | — | P3 | P3(25~28)完成 |
完成时刻:P1 = 25,P2 = 18,P3 = 28,P4 = 23,P5 = 17。周转时间依次为 25、17、26、20、11,平均周转时间 = 99 ÷ 5 = 19.8ms。
CPU 调度发生在 0、3、6、9、12、15、17、18、21、23、25 时刻,共 11 次;P5 的首次调度在 15ms。
如果把规则看反,3 时刻让 P1 排在 P4 前面,后面整张表都会错。进程在时间片中途完成(例如 P5 在 17 结束)要立即调度下一个,不用等满 3ms。
平均周转时间:SRTN 12ms < HRRN 13.8ms < RR 19.8ms,SRTN 最短。
SRTN 的缺点:总是先运行剩余时间短的进程。如果不断有短进程到达,长进程(如 P3)会一直得不到 CPU,产生饥饿。另外,它要预先知道运行时间,抢占也会带来更多切换。
HRRN 不会饥饿:响应比 R = 1 + 等待时间 ÷ 运行时间,一个进程等得越久,R 越大,最终一定会超过新到达的短进程而被选中。所以它既照顾短进程,又不会让长进程无限等待。
RR 的时间片 ≥ 最长的运行时间(本题 9ms)时,每个进程都能一次运行完,退化为 FCFS。时间片太小,则进程切换过于频繁,开销变大。
- 非抢占算法只在“有进程结束”时决策;抢占算法在“有进程到达”时也要决策。
- RR 的关键是写出每一步的队列,同一时刻进队的先后顺序一定按题目规定。
- 作为参考,非抢占 SJF 做同一组进程,平均周转时间是 13.4ms,介于 SRTN 和 HRRN 之间。运行时间已知、忽略切换开销时,SRTN 能使平均周转时间和平均等待时间最短。
举一反三:条件一变,答案怎么变
以上面的模拟题(5 个进程)为基准,每行只改一个条件。先自己想出新答案和理由,再点开核对。
| 条件怎么变 | 新答案和理由 |
|---|---|
| RR(3ms),但时间片用完的进程先于同一时刻到达的新进程进队 | 想好了再点3 时刻队列变为 P2、P3、P1、P4……,平均周转时间 19.6ms,仍调度 11 次。 |
| RR 时间片改为 2ms | 想好了再点平均周转时间 18.2ms,调度 14 次,切换更频繁。 |
| RR 时间片改为 4ms | 想好了再点平均周转时间 18.6ms,调度 9 次。 |
| RR 时间片 ≥ 9ms | 想好了再点每个进程一次运行完,退化为 FCFS:P1、P2、P3、P4、P5 依次运行,平均周转时间 16.6ms,调度 5 次。可见 RR 的平均周转时间不一定比 FCFS 短,它的优势是响应快、公平。 |
| 改为非抢占 SJF | 想好了再点0~8 运行 P1,之后依次是 P5、P2、P4、P3,平均周转时间 13.4ms。 |
| 问平均等待时间 | 想好了再点等待时间 = 周转时间 − 运行时间,所以平均等待时间 = 平均周转时间 − 平均运行时间(本题为 5.6ms)。例如 SRTN:12 − 5.6 = 6.4ms。 |
| 改为先来先服务(FCFS) | 想好了再点顺序 P1(0~8)、P2(8~12)、P3(12~21)、P4(21~26)、P5(26~28),周转时间 8、11、19、23、22,平均 16.6ms(程序核对)。只要 2ms 的 P5 排在长进程后面等了 20ms,这就是护航效应(6.7)。 |
丢分点清单
- 把“优先数”当“优先级”,方向弄反。
- 非抢占算法中途换进程,或抢占算法没在新进程到达时检查。
- RR 中新到达进程和被剥夺进程的入队顺序与题目约定不一致。
- 周转时间用“完成时刻”直接当周转(忘了减到达时刻)。
- 设计优先数公式时 waitTime 的符号写反。
- 把饥饿说成死锁。
本类其他真题
- 2016-46:静态优先数为什么饥饿;用 nice、cpuTime、waitTime 设计动态优先数。
- 选择题(点小节标题下的题号做):2012-29、2016-24、2017-23、2018-24、2019-27、2022-25、2023-29、2024-30 算时间;2009-24、2010-26、2011-23、2013-31、2014-23、2017-27、2020-26、2021-25、2025-25 算法特点;2012-30、2018-29、2021-27 调度时机。
11.11操作系统 ⑩ 分页、多级页表与页面置换
先读:8.5 分页 8.7 虚拟内存与置换算法 8.8 一次访存的全过程
- 8.7 OPT / FIFO / LRU 同序列对比 —— 缺页次数与置换次数
- 8.7 缺页中断与一般中断、调入策略 —— 缺页处理过程小问
- 8.5 TLB 与页表同时查 —— 有效访问时间
这类题长什么样
操作系统大题的第二主力(9 道)。给出地址结构和页表(或页目录起始地址),让你:切地址、算页表大小、算页表项的地址、做地址变换、算访问时间、按某种置换算法决定淘汰谁。和 ⑥ 的区别:⑥ 关心 TLB 和 Cache 的硬件结构,⑩ 关心页表本身和置换策略。
| 常见问法 | 考过的地方 |
|---|---|
| 页大小、页表最大多大、页目录和页表共占几页 | 2013-46(1),2015-46(1)(2) |
| 页号 / 页目录号 / 页表索引是多少(或写表达式) | 2010-46(1),2013-46(2),2017-45(2),2020-46(1),2024-45 |
| 页表项(页目录项)的物理地址、页框号、物理地址 | 2009-46(2),2013-46(3),2020-46(1),2024-45 |
| 有 TLB、缺页时每次访问要多长时间 | 2009-46(1) |
| 按 FIFO / LRU / CLOCK / 题目自定义策略置换后的页框 | 2009-46(2),2010-46(2)(3),2012-45 |
| 拼出虚拟地址、访问几个二级页表、代码占几页 | 2015-46(3),2017-45(1),2018-45(1) |
| PDBR 存什么、进程/线程切换时变不变、改进 CLOCK 要哪些位 | 2018-45(2)(3) |
| 连续性、局部性 | 2020-46(2)(3),2012-45(4) |
做这类题必须会的 6 件事
① 切地址:先看偏移几位。页大小 2k → 页内偏移 k 位,剩下的高位是页号。4KB 页(k = 12)时十六进制最后 3 位就是偏移,很方便;1KB、4MB 这种 k 不是 4 的倍数时,必须展开成二进制切(2010-46、2024-45)。物理地址 = 页框号 ∥ 页内偏移(拼接,不是相加)。
② 页表有多大、表项在哪。
③ 二级页表怎么走——跟着一个真实地址走一遍。
④ 有效访问时间。TLB 命中:tTLB + t内存;TLB 未命中、页在内存:tTLB + t内存(查页表)+ t内存(取数);缺页:再加缺页处理时间,处理完重新执行这条指令(又一次 tTLB + t内存)。
⑤ 置换算法和分配策略。
| 算法 | 淘汰谁 | 要点 |
|---|---|---|
| OPT 最佳 | 以后最长时间不用的 | 缺页最少,但要预知未来,只用来比较 |
| FIFO | 最早装入的(看装入时刻) | 可能出现 Belady 异常(页框多了缺页反而多) |
| LRU | 最久没被访问的(看最近访问时刻) | 效果接近 OPT,需要硬件支持(计数器或栈) |
| CLOCK | 指针扫描:访问位 1 → 清 0 跳过;遇到 0 → 淘汰 | 全是 1 时转一圈回到起点淘汰 |
| 改进 CLOCK | 先找 (访问位 0, 修改位 0);找不到再找 (0, 1),扫描时把访问位清 0;还不行重复 | 页表项要有访问位和修改位(2018-45) |
分配策略:固定分配局部置换(页框数不变,只在自己的页里换,2009-46、2010-46)、可变分配全局置换、可变分配局部置换。驻留集太小 → 频繁缺页,抖动;工作集 = 最近一段时间内访问过的页的集合,驻留集应不小于工作集。
⑥ 几个常考的“为什么”。页表基址寄存器(PDBR)里是物理地址;进程切换时换它,同一进程的线程切换不换。数组在虚拟地址空间连续,物理上不必连续。页表本身也可以放在虚拟地址空间里(2024-45),这时它也有自己的页号和页表项。
解题模板
- 写地址结构。页大小 → 偏移位数 → 页号(各级索引)位数;在草稿上画出竖线分段。
- 十六进制地址 → 二进制 → 切段。4KB 页可以直接去掉最后 3 位;其他情况一律二进制。
- 查表。表项地址 = 基址 + 索引 × 表项大小;看有效位;有效 → 页框号;无效 → 缺页 → 按规定算法选淘汰页(列出每个页的装入时刻/最近访问时刻/访问位)。
- 拼物理地址。页框号 ∥ 偏移,再换回十六进制。
- 算时间。按“TLB → 页表 → 缺页处理 → 重新执行”逐项相加,每一项写出来源。
请求分页管理系统中,假设某进程的页表内容如下表所示:
| 页号 | 页框(Page Frame)号 | 有效位(存在位) |
|---|---|---|
| 0 | 101H | 1 |
| 1 | 0 | |
| 2 | 254H | 1 |
页面大小为 4KB,一次内存的访问时间是 100ns,一次快表(TLB)的访问时间是 10ns,处理一次缺页的平均时间 10⁸ ns(已含更新 TLB 和页表的时间),进程的驻留集大小固定为 2,采用最近最少使用置换算法 (LRU) 和局部淘汰策略。假设
① TLB 初始为空;
② 地址转换时先访问 TLB,若 TLB 未命中,再访问页表(忽略访问页表之后的 TLB 更新时间);
③ 有效位为 0 表示页面不在内存,产生缺页中断,缺页中断处理后,返回到产生缺页中断的指令处重新执行。
设有虚地址访问序列 2362H、1565H、25A5H,请问:
(1) 依次访问上述三个虚地址,各需多少时间?给出计算过程。
(2) 基于上述访问序列,虚地址 1565H 的物理地址是多少?请说明理由。
先在纸上自己做一遍,再点这里展开满分解答
考点8.5 快表与有效访问时间8.7 缺页与 LRU训练营 ⑩
- 页 4KB → 页内偏移 12 位 = 3 个十六进制位,所以 2362H 这类 16 位地址的最高一个十六进制位就是页号:2362H → 页 2,1565H → 页 1,25A5H → 页 2。
- TLB 10ns,访存 100ns,缺页处理 108ns(已包含更新 TLB 和页表)。
- 先查 TLB,未命中再查页表(页表在内存,要访存一次);缺页处理完重新执行该指令(再查一次 TLB,这次命中)。
- 驻留集固定 2 个页框,局部 LRU 置换;当前内存里有页 0(101H)和页 2(254H)。
2362H:页号 2。TLB 初始为空,未命中 10ns;访问页表 100ns,页 2 有效(页框 254H);按物理地址访存 100ns。共 10 + 100 + 100 = 210ns(之后页 2 的表项装入 TLB)。
1565H:页号 1。TLB 未命中 10ns;访问页表 100ns,有效位为 0,缺页;缺页处理 108ns(含更新 TLB 和页表);返回后重新执行该指令,TLB 命中 10ns,访存 100ns。共 10 + 100 + 108 + 10 + 100 = 100 000 220ns。
25A5H:页号 2,TLB 命中 10ns,访存 100ns,共 110ns。
缺页那一次最容易漏掉“重新执行时的 TLB 10ns + 访存 100ns”。
访问 1565H 时页 1 缺页,驻留集已满(页 0、页 2),采用局部 LRU 置换:页 2 刚被访问过,页 0 最久未被访问,淘汰页 0,页 1 装入页 0 腾出的页框 101H。
物理地址 = 页框号 101H ∥ 页内偏移 565H = 10 1565H。
- 有效访问时间:TLB 命中 = tTLB + tmem;未命中 = tTLB + 2tmem;缺页再加处理时间和重新执行的时间。
- 物理地址 = 页框号拼上页内偏移(不是相加),十六进制直接写在一起。
模拟大题:1KB 页 + 四种置换算法(按 408 风格自编)
分页题的真题多用 4KB 页,十六进制最后 3 位就是偏移。下面这道题故意用 1KB 页,偏移 10 位,不是 4 的倍数,必须展开成二进制切。然后在同一张页表上让 FIFO、LRU、CLOCK、改进型 CLOCK 各选一个淘汰页,四个算法的依据一次对比清楚,最后再考 Belady 现象。结果都用程序模拟核对过。
某系统采用请求分页,逻辑地址 16 位,页大小 1KB,按字节编址。给进程 P 分配了 4 个页框,采用固定分配、局部置换。当前时刻为 260,P 的页表如下(页 1、5、6 等其余页都不在内存;访问位由操作系统定期清零):
| 页号 | 存在位 | 页框号 | 装入时刻 | 最近访问时刻 | 访问位 A | 修改位 M |
|---|---|---|---|---|---|---|
| 0 | 1 | 07H | 60 | 230 | 0 | 1 |
| 2 | 1 | 12H | 130 | 150 | 1 | 0 |
| 3 | 1 | 2CH | 26 | 250 | 1 | 1 |
| 4 | 1 | 05H | 20 | 240 | 0 | 0 |
(1) 逻辑地址中页号和页内偏移各占几位?进程的页表最多有多少项?(1 分)
(2) 求逻辑地址 0E7FH 对应的物理地址。(2 分)
(3) 访问逻辑地址 1A3CH 时会发生什么?分别用 FIFO、LRU、CLOCK 和改进型 CLOCK 算法,应淘汰哪一页?CLOCK 类算法的替换指针当前指向页 3,扫描顺序为 3 → 0 → 2 → 4 → 3。按 LRU 算法,1A3CH 的物理地址是多少?(4 分)
(4) 另有一个进程的页面访问序列为 1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5。分别给它 3 个和 4 个页框,用 FIFO 算法各缺页几次(开始时页框为空)?这说明什么?LRU 会不会出现这种现象?(2 分)
先在纸上自己做一遍,再点这里展开满分解答
考点8.5 分页地址变换8.7 页面置换算法训练营 ⑩ 切地址
- 页 1KB = 210B,偏移 10 位,页号 16 − 10 = 6 位。十六进制的边界在 4 位上,10 位偏移会切在一个十六进制数字中间,必须写成二进制再切。
- 四个算法各看表中的一列:FIFO 看“装入时刻”,LRU 看“最近访问时刻”,CLOCK 看访问位 A,改进型 CLOCK 看 (A, M)。
页内偏移 10 位(第 9~0 位),页号 6 位(第 15~10 位)。页表最多 26 = 64 项。
0E7FH = 0000 11 | 10 0111 1111B,页号 = 000011B = 3,偏移 = 10 0111 1111B = 27FH。
页 3 在内存,页框号 2CH。物理地址 = 2CH × 1KB + 27FH = 1011 00 | 10 0111 1111B = B27FH。
不能直接把 0E7FH 的最后 3 个十六进制位当偏移:那是 4KB 页的切法。1KB 页的偏移是 10 位,E 的二进制 1110 中,高 2 位属于页号,低 2 位属于偏移。
1A3CH = 0001 10 | 10 0011 1100B,页号 = 000110B = 6,偏移 = 23CH。页 6 不在内存,发生缺页。P 已用满 4 个页框,采用局部置换,所以要在 P 自己的页中淘汰一页。
FIFO:淘汰最早装入的页,装入时刻最小的是页 4(20),淘汰页 4。
LRU:淘汰最久没有被访问的页,最近访问时刻最小的是页 2(150),淘汰页 2。
CLOCK:从页 3 开始扫描。页 3 的 A = 1,置 0 后跳过;页 0 的 A = 0,淘汰页 0。页 0 的 M = 1,要先把它写回磁盘。
改进型 CLOCK:第一轮找 (A, M) = (0, 0) 的页,且不改动访问位。页 3 为 (1, 1),页 0 为 (0, 1),页 2 为 (1, 0),都不符合;页 4 为 (0, 0),淘汰页 4,不用写回。
按 LRU,页 6 装入页 2 腾出的页框 12H,物理地址 = 12H × 1KB + 23CH = 4800H + 23CH = 4A3CH。
改进型 CLOCK 第一轮只找 (0, 0),不清访问位;第一轮找不到,第二轮才找 (0, 1),并把经过的页的 A 清 0。它比 CLOCK 多考虑修改位,优先淘汰没被改过的页,省去一次写回磁盘。
FIFO,3 个页框:缺页 9 次。FIFO,4 个页框:缺页 10 次。
页框变多了,缺页次数反而增加,这就是 Belady 异常。FIFO 只按装入顺序淘汰,与页面的使用情况无关,所以会出现这种现象。
LRU 不会出现。同一个时刻,n 个页框中的页一定包含在 n + 1 个页框中的页里(LRU 是“栈式算法”),所以页框越多,缺页次数只会减少或不变。本序列用 LRU:3 个页框缺页 10 次,4 个页框缺页 8 次。
- 页大小不是 16 的幂(1KB、2KB、4MB)时,一律展开成二进制再切。
- FIFO 看装入时刻,LRU 看最近访问时刻,CLOCK 看 A 位,改进型 CLOCK 看 (A, M),而且第一轮不改 A 位。
- 只有 FIFO 会出现 Belady 异常;OPT 和 LRU 都不会。
举一反三:条件一变,答案怎么变
以上面的模拟题为基准,每行只改一个条件。先自己想出新答案和理由,再点开核对。
| 条件怎么变 | 新答案和理由 |
|---|---|
| 页大小改为 2KB | 想好了再点偏移 11 位,页号 5 位。0E7FH = 00001 | 110 0111 1111B,页号 1,偏移 67FH。 |
| CLOCK 的指针改为指向页 2 | 想好了再点页 2 的 A = 1,置 0 后跳过;页 4 的 A = 0,淘汰页 4。 |
| 改为可变分配、全局置换 | 想好了再点可以淘汰其他进程的页,P 的页框数会随之变化;缺页时 P 的页框数可能增加。 |
| Belady 那个序列,FIFO 给 5 个页框 | 想好了再点序列里只有 5 个不同的页面,只在每页第一次访问时缺页,共 5 次。 |
| 访问 0E7FH 是一次写操作 | 想好了再点页 3 的访问位 A 置 1,修改位 M 也置 1(本来已是 1)。以后淘汰页 3 时要先写回磁盘。 |
| 逻辑地址改为 32 位(页 1KB,页表项 4B) | 想好了再点页号 22 位,单级页表有 222 项 × 4B = 16MB。一页只能放 256 个表项(8 位索引),需要 ⌈22 ÷ 8⌉ = 3 级页表。 |
| 系统采用页面缓冲算法(PBA),FIFO 淘汰的页 4 不久又被访问 | |
| (4) 的序列改用 OPT 算法,3 个和 4 个页框 | 想好了再点OPT 分别缺页 7 次和 6 次(程序模拟核对):页框多了缺页减少,OPT 不会出现 Belady 异常。 |
| (4) 的序列,第 8 次访问时窗口 Δ = 4 的工作集 | 想好了再点第 5~8 次访问是 1、2、5、1,工作集 = {1, 2, 5},大小为 3(8.7 工作集)。 |
丢分点清单
- 页不是 4KB 时仍按十六进制位切。
- 物理地址用“页框号 + 偏移”做加法,而不是拼接(或者页框号忘了乘页大小)。
- 页表项地址忘了乘表项大小(× 4)。
- 缺页那次访问忘了“重新执行”的时间。
- FIFO 看成最近访问时刻、LRU 看成装入时刻(两者正好相反)。
- CLOCK 扫描时没有把访问位清 0,或者指针起点不对。
- 二级页表占用页数只算了页目录。
本类其他真题
- 2010-46:页 1KB,逻辑地址 17CAH 的页号;FIFO 和 CLOCK 分别置换后的物理地址。
- 2012-45:自定义的“周期扫描回收 + 空闲链”置换策略,按时间线推页框号。
- 2013-46:一级页表大小、二级页表的索引表达式、代码段的两个页表项地址与页框号。
- 2015-46:二级页表共占多少页、两个地址用了几张二级页表。
- 2017-45:代码占几页、访问页目录和页表的第几项、scanf 时的进程状态。
- 2018-45:拼虚拟地址、PDBR、改进型 CLOCK 需要的位。
- 2020-46:二维数组元素的页目录项和页表项物理地址(上图)。
- 2024-45:4MB 大页、页表放在虚拟地址空间中:页表项的虚、实地址和页框号。
11.12操作系统 ⑪ 文件系统:目录、inode、混合索引与读盘次数
先读:10.4 文件、FCB 与 inode 10.5 目录 10.6 物理结构 10.7 空闲空间 10.8 全局结构 10.9 open + read
- 10.4 文件基本操作 —— open / read 各做什么、要读几次盘
- 10.5 目录查找读盘次数例 —— FCB 与 inode 两种目录项
- 10.6 三种分配方式对比 —— 访问第 i 块要读几次盘
- 10.6 混合索引通用公式 —— 最大文件、间接级数
- 10.7 成组链接法 —— 空闲块分配与回收
这类题长什么样
文件系统大题 7 道,套路非常集中:选哪种物理结构并说理由、索引结构能支持多大的文件、读某个字节要读几次盘、目录文件里写的是什么、硬链接、FAT、删除/创建要改哪些元数据。
| 常见问法 | 考过的地方 |
|---|---|
| 连续 / 链接 / 索引哪种合适,FCB 里要哪些字段 | 2011-46 |
| 块号占几字节、最大文件长度(直接、混合索引、链接、FAT) | 2012-46,2014-46(2),2016-47(2),2018-46(1) |
| 读某个字节 / 最后一个块要读几次盘、用到几级间接 | 2016-47(4),2018-46(3),2022-45(3)(4),2026-46(1) |
| 目录文件的内容、硬链接 | 2016-47(1),2022-45(1)(2) |
| FAT 表项存在哪、FAT 多大 | 2016-47(2)(3) |
| 插入一条记录要访问几次盘 | 2014-46 |
| 最多能存多少文件(inode 数 vs 数据块数) | 2018-46(2),2026-46(1) |
| 删除目录要改哪些元数据、inode 在哪个盘块 | 2026-46 |
| FCB 集中存放还是分散 | 2011-46(2) |
做这类题必须会的 6 件事
① 从文件名到数据的一条链。目录项(文件名 → inode 号)→ inode(属性 + 地址项)→(间接索引块)→ 数据块。目录本身也是一个文件,它的“数据”就是一条条目录项。硬链接 = 两个目录项指向同一个 inode(inode 里有链接计数);软链接 = 一个新文件,内容是另一个文件的路径名。
② 三种物理结构一张表。
| 结构 | FCB / inode 里记什么 | 随机访问 | 文件增长 | 缺点 |
|---|---|---|---|---|
| 连续分配 | 起始块号、块数 | 快(直接算) | 难 | 外部碎片;插入/增长要搬移(2014-46) |
| 链接分配(隐式) | 首块号、末块号 | 慢(顺链读) | 容易 | 每块要放指针;一块坏全断 |
| 链接分配(显式 FAT) | 首块号(其余在 FAT) | 较快(FAT 在内存中查) | 容易 | FAT 占内存;FAT[i] = 块 i 的下一块 |
| 索引分配 | 索引块/地址项 | 快 | 容易 | 索引块占空间;大文件要多级 |
③ 三个万能公式。
④ 数读盘次数的方法。一格一格走那条链,问每一步“这个东西现在在内存里吗”:目录文件在不在?inode 在不在?要经过几层索引块?最后读数据块 1 次。题目常说“FAT 和某目录已在内存”“inode 已读入内存”,这些就不算。
⑤ 空闲空间管理。位示图:一块一位,大小 = 块数 ÷ 8 字节(2010-45:16384 块 → 2KB);块号 b ↔ 第 ⌊b ÷ 字长⌋ 个字、第 b mod 字长 位(注意题目从 0 还是 1 编号)。空闲链表、成组链接(UNIX)也要知道。
⑥ 创建、删除、打开要动哪些东西。创建:分配 inode(改 inode 位示图)→ 初始化 inode → 在父目录中加目录项 →(写数据时分配数据块,改磁盘位示图)。删除:反过来——释放数据块和索引块、释放 inode、删除父目录中的目录项(有硬链接时只减链接计数,减到 0 才真正释放)。打开:按路径逐级查目录,找到 inode 读入内存,建立打开文件表项,返回文件描述符。
解题模板
- 写出三个数:块大小、地址项大小、直接地址项个数 d;算 N。
- 画覆盖范围:直接 [0, d),一级 [d, d+N),二级 [d+N, d+N+N²)……(单位:块)。
- 最大文件 = 总块数 × 块大小;块号位数不够时,还要受磁盘总块数限制。
- 某个偏移:先求逻辑块号,查它落在哪一级,再按“内存里已有什么”数读盘次数。
- 目录/链接题:画出目录树,每个目录文件只写它的直接孩子;inode 号相同即硬链接。
- 元数据操作题:按“数据块 → inode → 目录项”的顺序逐项写,并说明在哪个位示图中改哪一位。
某文件系统的磁盘块大小为 4KB,目录项由文件名和索引节点号构成,每个索引节点占 256 字节,其中包含直接地址项 10 个,一级、二级和三级间接地址项各 1 个,每个地址项占 4 字节。该文件系统中子目录 stu 的结构如题 45(a) 图所示:stu 包含子目录 course 和文件 doc,course 子目录包含文件 course1 和 course2。各文件的文件名、索引节点号、占用磁盘块的块号如题 45(b) 图所示。
| 文件名 | 索引节点号 | 磁盘块号 |
|---|---|---|
| stu | 1 | … |
| course | 2 | … |
| doc | 10 | x |
| course1 | 10 | 30 |
| course2 | 100 | … |
| 题 45(b) 图(按原卷整理) | ||
请回答下列问题。
(1) 目录文件 stu 中每个目录项的内容是什么?
(2) 文件 doc 占用的磁盘块的块号 x 的值是多少?
(3) 若目录文件 course 的内容已在内存,则打开文件 course1 并将其读入内存,需要读几个磁盘块?说明理由。
(4) 若文件 course2 的大小增长到 6MB,为了存取 course2 需要使用该文件索引节点的哪几级间接地址项?说明理由。
先在纸上自己做一遍,再点这里展开满分解答
考点10.5 目录、硬链接10.4 inode10.6 混合索引训练营 ⑪
- 目录项 = (文件名,索引节点号);文件的属性和数据块地址都在 inode 里。
- doc 和 course1 的索引节点号都是 10——同一个 inode,说明它们是同一个文件的两个名字(硬链接)。
- 块 4KB、地址项 4B → 一个间接块放 1024 个块号;inode 有 10 个直接地址项。
stu 包含子目录 course 和文件 doc,其目录项为:(course,2)和(doc,10)。
course1、course2 是 course 目录的内容,不写在 stu 里。
doc 与 course1 的索引节点号相同(都是 10),二者是同一文件的硬链接,共享同一个索引节点,数据块也相同,所以 x = 30。
course 目录在内存中,可直接得到 course1 的索引节点号 10;读入 10 号索引节点所在的磁盘块(1 次),从中得到数据块号 30;再读磁盘块 30(1 次)。共需读 2 个磁盘块。
6MB ÷ 4KB = 1536 个块。每个间接块可存 4KB ÷ 4B = 1024 个块号。10 个直接地址项 + 一级间接 1024 项共可表示 1034 个块 < 1536,剩下的块需要二级间接地址项。所以需要使用一级和二级间接地址项。
- inode 号相同 → 硬链接 → 同一个文件(数据、属性都一样)。
- 判断用到几级:逐级累加能表示的块数,直到覆盖文件的块数。
模拟大题:从路径名到第 5 000 000 个字节(按 408 风格自编)
文件系统的真题分散地考了最大文件、读盘次数、目录内容、FAT、硬链接。下面这道自编题把它们串成一条线:先按路径找到文件,再读文件中间的一个字节,然后换成 FAT 比较,最后问硬链接和软链接。数字都用程序算过。
某文件系统的磁盘块大小为 4KB,块号占 4B。每个文件有一个 inode,其中有 10 个直接地址项、1 个一级间接地址项、1 个二级间接地址项和 1 个三级间接地址项。每个目录文件都只占 1 个磁盘块;目录项中存放文件名和 inode 号;inode 不在内存时,读一个 inode 需要读 1 次磁盘。根目录的 inode 已在内存中。
(1) 该文件系统支持的单个文件最大长度是多少?(写出算式,并估算大小)(2 分)
(2) 进程打开文件 /home/wang/data.bin(此前没有打开过这条路径上的任何文件)。从根目录开始,查找过程中需要读几次磁盘?依次读的是什么?(2 分)
(3) 打开后,进程读该文件偏移量为 5 000 000 处的 1 个字节。该字节在文件的第几个逻辑块(从 0 起)?用到 inode 中的哪个地址项?需要再读几次磁盘?设 inode 已在内存中,间接索引块不在内存中。(3 分)
(4) 若磁盘容量为 64GB,改用 FAT 管理(显式链接),FAT 表项至少占几字节?FAT 共占多少空间?FAT 全部在内存中时,读 (3) 中的字节要读几次磁盘?和混合索引相比,FAT 的随机访问慢在哪里?(2 分)
(5) 执行 ln /home/wang/data.bin /tmp/d(硬链接)后,再删除 /home/wang/data.bin,/tmp/d 还能读吗?若建的是软链接(符号链接),结果呢?(1 分)
先在纸上自己做一遍,再点这里展开满分解答
考点10.6 混合索引10.5 目录查找读盘次数10.6 三种分配方式对比10.5 硬链接与软链接
- 每个索引块能放 N = 4KB ÷ 4B = 1024 个块号。
- 各地址项管的逻辑块范围:直接 0~9;一级间接 10~1033(1024 块);二级间接 1034 ~ 1034 + 1024² − 1;三级间接再往后。
- 路径查找的节奏是“读目录内容 → 找到下一级的 inode 号 → 读那个 inode → 再读它的目录内容……”,根的 inode 已在内存。
N = 4KB ÷ 4B = 1024。最大文件 = (10 + 1024 + 10242 + 10243) × 4KB = 40KB + 4MB + 4GB + 4TB,约 4TB。
实际能存多大,还受磁盘容量和块号位数的限制。块号 4B 最多能表示 232 个块,即 16TB 的磁盘空间,比 4TB 大,所以这里的上限由索引结构决定。
① 读根目录的目录文件(1 块),找到 home 的 inode 号;② 读 home 的 inode;③ 读 home 的目录文件,找到 wang 的 inode 号;④ 读 wang 的 inode;⑤ 读 wang 的目录文件,找到 data.bin 的 inode 号;⑥ 读 data.bin 的 inode,装入内存。
共读磁盘 6 次。
打开文件只读到 inode 为止,不读数据块。每一级目录都要读两次:先读它的 inode,再读它的内容,根目录的 inode 已在内存时只读内容。
逻辑块号 = ⌊5 000 000 ÷ 4096⌋ = 1220,块内偏移 = 5 000 000 − 1220 × 4096 = 2880。
1220 ≥ 1034,落在二级间接地址项管的范围内。在二级间接范围内的序号 = 1220 − 1034 = 186:第一级索引块的第 186 ÷ 1024 = 0 项,第二级索引块的第 186 项。
需读磁盘 3 次:一级索引块(二级间接地址项指向的块)、二级索引块、数据块。
算逻辑块号时要减去前面各级管的块数(10 + 1024),不能直接拿 1220 去除以 1024。
64GB ÷ 4KB = 224 个块,表项至少 24 位 = 3B。FAT 有 224 项,共占 224 × 3B = 48MB。
FAT 在内存中时,从首块号出发沿 FAT 链查找 1220 次,都在内存中完成,只需读数据块 1 次。
慢在:要找第 1220 块,必须从首块开始在 FAT 中顺着链走 1220 步,不能像索引那样直接算出位置。而且 FAT 要常驻内存,磁盘越大,FAT 越大。
硬链接:/tmp/d 的目录项直接指向同一个 inode,inode 中的链接计数由 1 变为 2。删除 /home/wang/data.bin 只删去一个目录项,计数减为 1,inode 和数据块都还在,/tmp/d 仍能读。
软链接:/tmp/d 是一个单独的小文件,里面存的是路径名“/home/wang/data.bin”。原文件删除后路径失效,/tmp/d 不能读(悬空链接)。
- 混合索引:先算逻辑块号,再减去前面各级管的块数,确定用哪一级;读盘次数 = 索引块层数 + 1。
- 路径查找:每一级“读目录内容 + 读下一级 inode”。
- FAT 用内存换磁盘访问:随机访问不读盘,但要在内存中顺链查找,FAT 本身也占内存。
举一反三:条件一变,答案怎么变
以上面的模拟题(块 4KB,块号 4B)为基准,每行只改一个条件。先自己想出新答案和理由,再点开核对。
| 条件怎么变 | 新答案和理由 |
|---|---|
| 块大小改为 1KB(块号仍 4B) | 想好了再点N = 256,最大文件 = (10 + 256 + 2562 + 2563) × 1KB ≈ 16GB。偏移 5 000 000 在逻辑块 4882,4882 − 266 = 4616,属于二级间接(一级索引第 18 项,二级索引第 8 项),仍读盘 3 次。 |
| 块号改为 8B(块 4KB) | 想好了再点N = 512,最大文件约 512GB。逻辑块 1220 − 522 = 698,属于二级间接(第 1 项、第 186 项),读盘 3 次。 |
| inode 不在内存中 | 想好了再点先读 inode,再加 1 次,共 4 次。 |
| 读偏移 30 000 处的字节 | 想好了再点逻辑块 7,在直接地址项中,只读数据块 1 次。 |
| 路径多一级:/home/wang/2026/data.bin | 想好了再点每多一级目录,多读 2 次(目录内容 + 下一级的 inode),共 8 次。 |
| 目录文件占多个块,需要顺序查找 | 想好了再点读目录内容的次数增加。要找的目录项在各块的概率相同时,平均读 (块数 + 1) ÷ 2 块。 |
| inode 改用链接方案:只有 1 个指向索引块的指针,每个索引块的最后一项指向下一个索引块 | 想好了再点每个索引块放 4096 ÷ 4 − 1 = 1023 个块号。偏移 5 000 000 在逻辑块 1220,1220 ÷ 1023 = 1 余 197,在第 2 个索引块中:沿链读 2 个索引块,再读数据块,共 3 次(程序核对;10.6 链接方案)。 |
| 另一个进程也打开了 data.bin | 想好了再点第二次 open 仍按路径查目录,找到的是同一个 inode。系统打开文件表中 data.bin 仍只有一项,打开计数变为 2,inode 已在内存,不用再读;第二个进程在自己的打开文件表中有一项,读写指针从 0 开始,和第一个进程互不影响(10.4 打开文件表)。 |
丢分点清单
- 把 N 算成“块大小 ÷ inode 大小”(应除以地址项大小)。
- 最大文件忘了加直接地址项那一部分,或者多级的层数弄错。
- 数读盘次数时把已经在内存里的 inode / 目录 / FAT 也算进去,或者漏了读 inode 那一次。
- 目录文件内容写了孙子目录的项。
- “最多存多少文件”只看数据块,不看 inode 个数。
- 链接分配最大文件用整个块大小算(每块要扣掉指针)。
- 删除文件只写“删除目录项”。
本类其他真题
- 2011-46:一次写入、不再修改的文件选哪种分配方式;FCB 集中存放的好处。
- 2012-46:4TB 文件系统的块号字节数、直接索引最大文件、连续区 + 索引的最大文件和字段分配。
- 2014-46:连续分配和链接分配下插入第 30 条记录的访盘次数、链接分配的最大文件。
- 2016-47:FAT 文件系统:目录文件内容、FAT 大小、簇号存放位置、读第 5000 字节访问哪些簇。
- 2018-46:inode 混合索引:最大文件、最多存多少个 5600B 文件、F1/F2 取最后簇号的时间比较。
- 2026-46:inode 所在盘块、读偏移 21460 最多读几块、最多文件数、删除目录要改的元数据。
11.13操作系统 ⑫ 磁盘:容量、地址换算、调度与访问时间
先读:10.1 磁盘结构与访问时间 10.2 磁盘调度 10.3 初始化与引导 10.7 位示图
- 10.1 磁盘管理的三个“为什么” —— 簇号换算成柱面 / 盘面 / 扇区(2019-44)
- 10.2 六种调度算法同题对比与口径提醒 —— 移动距离、平均寻道长度
这类题长什么样
磁盘单独成题不多(2010-45、2019-44,另有计组的 2022-44 和引导题 2021-46),但每道都很“送分”:容量、簇号 ↔ 物理地址、调度顺序和移动距离、访问时间、位示图、SSD 用什么调度。公式记住、题目条件读对就能拿满。
| 常见问法 | 考过的地方 |
|---|---|
| 磁盘容量、地址字段位数 | 2019-44(1),2022-44(1) |
| 按某种调度算法的访问顺序、移动磁道数 | 2010-45(2),2019-44(2) |
| 簇号 / 块号 → 柱面、磁头、扇区;由谁完成 | 2019-44(3) |
| 读若干扇区的总时间 / 平均访问时间 | 2010-45(2),2022-44(2) |
| 空闲块用位示图管理 | 2010-45(1) |
| 换成 SSD / Flash 用什么调度 | 2010-45(3) |
| 格式化、分区、引导的顺序 | 2021-46 |
做这类题必须会的 5 件事
① 结构与容量。盘面数 = 磁头数;每个盘面上同一半径的磁道组成一个柱面。容量 = 柱面数 × 磁头数 × 每道扇区数 × 扇区大小。物理地址 = (柱面号, 磁头号, 扇区号),各字段位数 = ⌈log2个数⌉。
② 簇号(块号)↔ 物理地址。编号顺序是“先扇区、再磁头、再柱面”(同一柱面上的块编完才换柱面,减少寻道)。设每道 s 个簇、每柱面 h × s 个簇:柱面号 = ⌊b ÷ (h·s)⌋;余数 r → 磁头号 = ⌊r ÷ s⌋;余数 × 每簇扇区数 = 起始扇区号。这一转换由磁盘驱动程序完成。
③ 访问时间 = 寻道 + 旋转延迟 + 传输。平均旋转延迟 = 转半圈 = (60 ÷ 转速) ÷ 2;传输一个扇区 = 转一圈 ÷ 每道扇区数。多个随机扇区就每个都加一次旋转延迟和传输时间。
④ 调度算法——同一组请求,四种走法。
⑤ SSD、格式化与引导。SSD / Flash 没有机械运动,随机访问任何位置时间相同,用 FCFS 即可;按页读写、按块擦除,需要磨损均衡(由 SSD 自己的控制器/FTL 完成)。准备一块启动盘:物理格式化(划扇区)→ 分区 → 逻辑格式化(建文件系统、根目录)→ 安装操作系统;开机:ROM 引导程序 → MBR 中的磁盘引导程序 → 分区引导程序 → 操作系统初始化(2021-46,见 ⑬)。
解题模板
- 翻译几何参数:柱面数、磁头(盘面)数、每道扇区数、扇区大小、簇 = 几个扇区 → 每道几簇、每柱面几簇。
- 换算地址:簇号 → 柱面 → 磁头 → 扇区,写出每一步的除法和余数。
- 排调度顺序:标出磁头位置和移动方向,按算法列出顺序,逐段写距离再求和。
- 算时间:寻道(距离 × 每道时间,或平均寻道时间)+ 每个请求的旋转延迟 + 每个请求的传输时间。
- 写理由:SSD 为什么 FCFS、为什么 SSTF 会饥饿、为什么由驱动程序转换地址。
某计算机系统中的磁盘有 300 个柱面,每个柱面有 10 个磁道,每个磁道有 200 个扇区,扇区大小为 512B。文件系统的每个簇包含 2 个扇区。请回答下列问题:
(1) 磁盘的容量是多少?
(2) 假设磁头在 85 号柱面上,此时有 4 个磁盘访问请求,簇号分别为 100260、60005、101660 和 110560。若采用最短寻道时间优先 (SSTF) 调度算法,则系统访问簇的先后次序是什么?
(3) 第 100530 簇在磁盘上的物理地址是什么?将簇号转换成磁盘物理地址的过程是由 I/O 系统的什么程序完成的?
先在纸上自己做一遍,再点这里展开满分解答
考点10.1 磁盘容量与地址10.2 SSTF9.5 驱动程序训练营 ⑫
- 300 个柱面,每柱面 10 个磁道(= 10 个磁头),每道 200 个扇区,扇区 512B。
- 1 簇 = 2 个扇区 → 每道 100 簇,每柱面 1000 簇。簇号按“柱面 → 磁道 → 扇区”的顺序连续编号。
- 簇号 → 物理地址:簇号 ÷ 1000 = 柱面号(余数 r);r ÷ 100 = 磁道号(余数 s);扇区号 = s × 2。
容量 = 300 × 10 × 200 × 512B = 307 200 000B = 300 000KB(约 293MB)。
每柱面 10 × 200 ÷ 2 = 1000 个簇,簇号 ÷ 1000 得柱面号:100260 → 100,60005 → 60,101660 → 101,110560 → 110。
从 85 号柱面出发,每次选离当前柱面最近的请求:100(距 15)→ 101(距 1)→ 110(距 9)→ 60(距 50)。
访问簇的次序为:100260、101660、110560、60005。
100530 ÷ 1000 = 100 余 530 → 柱面号 100;530 ÷ 100 = 5 余 30 → 磁道号(磁头号)5;该簇是本磁道的第 30 簇,起始扇区号 = 30 × 2 = 60。
物理地址:柱面号 100、磁头号 5、扇区号 60(该簇占第 60、61 号扇区)。
簇号到磁盘物理地址的转换由磁盘驱动程序完成。
- 簇号 → (柱面, 磁头, 扇区):逐级除以“每柱面簇数”“每道簇数”,余数继续往下算。
- 和具体设备的物理细节打交道的是驱动程序。
模拟大题:一块磁盘从容量问到访问时间(按 408 风格自编)
磁盘单独出的大题只有两道(2010-45、2019-44),但它们的问法都在下面这道自编题里,而且放在同一块磁盘上连着问:容量和地址位数、位示图定位、簇号换算物理地址、两种调度算法、总访问时间。所有数字都用程序算过。
某磁盘的参数如下:
- 柱面:200 个(0~199);磁头:4 个(0~3);每个磁道 64 个扇区(0~63);扇区大小 512B。
- 转速 6000 转/分;磁头每移动一个柱面需要 0.2ms。
文件系统以簇为单位分配空间,1 簇 = 同一磁道上 4 个相邻扇区(2KB)。簇号从 0 开始编号,顺序是:同一磁道内按扇区号,一个磁道编完换同一柱面的下一个磁头,一个柱面编完再换下一个柱面。
系统用位示图记录所有簇的空闲状态。位示图按 32 位字存放,字号和位号都从 0 开始,簇号 = 32 × 字号 + 位号。
(1) 该磁盘的容量是多少 MB?物理地址(柱面号, 磁头号, 扇区号)至少需要多少位?(2 分)
(2) 位示图占多少字节?簇 3000 对应位示图中哪个字的哪一位?(2 分)
(3) 簇 3000 对应的物理地址是什么?写出柱面号、磁头号和扇区号的范围。簇 3007 和簇 3008 呢?为什么要按“先扇区、再磁头、最后柱面”的顺序给簇编号?(2 分)
(4) 磁头刚读完簇 3000,正向柱面号增大的方向移动。请求队列中的柱面号依次为 120、30、60、150、10、85、40,每个请求读 1 个簇。分别用 SSTF 和 LOOK 调度,写出访问顺序和磁头移动的总柱面数。LOOK 即电梯算法,到达该方向上最远的请求就掉头。(2 分)
(5) 按 SSTF 的顺序读完这 7 个簇,共需多少时间?每个簇在磁道上的位置随机,忽略控制器的开销。(2 分)
先在纸上自己做一遍,再点这里展开满分解答
考点10.1 磁盘结构与访问时间10.2 调度算法同题对比10.7 位示图10.1 三个“为什么”
- 1 簇 = 4 个扇区,所以每个磁道有 64 ÷ 4 = 16 簇,每个柱面有 4 × 16 = 64 簇,一共 200 × 64 = 12 800 簇。
- 转速 6000 转/分,转一圈 = 60 ÷ 6000 s = 10ms,平均旋转延迟是半圈,即 5ms。读 1 簇就是读 4 个扇区,用时 4/64 × 10ms = 0.625ms。
- 簇号 → 物理地址:先用 ÷ 64 求柱面号,余数再 ÷ 16 求磁头号,最后的余数 × 4 得到起始扇区号。
容量 = 柱面数 × 磁头数 × 每道扇区数 × 扇区大小 = 200 × 4 × 64 × 512B = 26 214 400B = 25MB。
柱面号 ⌈log2200⌉ = 8 位,磁头号 log24 = 2 位,扇区号 log264 = 6 位,物理地址至少 8 + 2 + 6 = 16 位。
存储容量里的 M 是 220:26 214 400 ÷ 220 = 25。柱面有 200 个,要取 ⌈log2200⌉ = 8 位,不能写成 7 位(27 = 128 < 200)。
簇总数 = 200 × 4 × 64 ÷ 4 = 12 800 个,每个簇占 1 位,位示图大小 = 12 800 ÷ 8 = 1600B(即 400 个 32 位字)。
3000 = 32 × 93 + 24,所以簇 3000 对应第 93 个字的第 24 位(字号、位号都从 0 开始)。
位示图管理的单位是“簇”,不是扇区。按扇区算会得到 6400B,是正确结果的 4 倍。
簇 3000:3000 ÷ 64 = 46 余 56,所以柱面号是 46;56 ÷ 16 = 3 余 8,所以磁头号是 3;8 × 4 = 32,扇区号为 32~35。物理地址为 (46, 3, 32~35)。
簇 3007:3007 = 64 × 46 + 63,63 = 16 × 3 + 15,15 × 4 = 60,物理地址为 (46, 3, 60~63)。
簇 3008:3008 = 64 × 47 + 0,物理地址为 (47, 0, 0~3)。编号连续的两个簇,一个在 46 号柱面的最后一个位置,一个在 47 号柱面的开头。
原因:编号连续的簇先占满同一个磁道,再换同一柱面的其他磁头。换磁头只是电子切换,不用移动磁臂,一个柱面全部用完才移到下一个柱面。这样,连续存放的文件读写时寻道次数最少,速度最快。
SSTF 每次去离当前位置最近的请求,不管方向;LOOK 先沿给定方向(增大)走到最远的请求,再掉头。
SSTF:46 → 40 → 30 → 10 → 60 → 85 → 120 → 150。移动柱面数 = 6 + 10 + 20 + 50 + 25 + 35 + 30 = 176。
LOOK:46 → 60 → 85 → 120 → 150 → 40 → 30 → 10。移动柱面数 = (150 − 46) + (150 − 10) = 104 + 140 = 244。
SSTF 在第一步就朝柱面号减小的方向走了,因为 40 离 46 最近。题目给出的移动方向只对 SCAN、LOOK、C-SCAN 起作用。本例 SSTF 的距离更短,但如果不断有请求落在磁头附近,远处的请求会饥饿。
寻道时间 = 176 × 0.2ms = 35.2ms。
每个请求的旋转延迟 = 转半圈 = (60 ÷ 6000) s ÷ 2 = 5ms;传输 1 簇 = 4 ÷ 64 × 10ms = 0.625ms。
总时间 = 35.2 + 7 × (5 + 0.625) = 35.2 + 39.375 = 74.575ms。
“位置随机”意味着 7 个请求每个都要加一次平均旋转延迟,不能只加一次。这里的传输时间是 4 个扇区的时间,不是 1 个扇区。
- 一上来先把“每道几簇、每柱面几簇、转一圈几 ms”这三个量算出来,后面每一问都要用。
- 簇号换算就是连除两次:先 ÷ 每柱面簇数,再 ÷ 每道簇数;反过来,簇号 = (柱面 × 每柱面簇数) + (磁头 × 每道簇数) + 道内簇序号。
- 访问时间 = 寻道 + 旋转延迟 + 传输;请求有几个,后两项就加几次。
举一反三:条件一变,答案怎么变
以上面的模拟题为基准,每行只改一个条件。先自己想出新答案和理由,再点开核对。
| 条件怎么变 | 新答案和理由 |
|---|---|
| 转速改为 7200 转/分 | 想好了再点转一圈 8.33ms,平均旋转延迟 4.17ms,读一簇 0.52ms。总时间 = 35.2 + 7 × (4.17 + 0.52) ≈ 68.0ms。 |
| 1 簇 = 8 个扇区 | 想好了再点每道 8 簇,每柱面 32 簇。簇 3000 → (93, 3, 0~7)。簇总数 6400,位示图 800B。 |
| 磁头改为向柱面号减小的方向移动,用 LOOK | 想好了再点46 → 40 → 30 → 10 → 60 → 85 → 120 → 150,移动 (46 − 10) + (150 − 10) = 176 个柱面。 |
| 改用 SCAN(走到 199 号柱面才掉头) | 想好了再点(199 − 46) + (199 − 10) = 342 个柱面。 |
| 改用 C-SCAN(到 199 后回到 0,返回距离计入) | 想好了再点46 → 60 → 85 → 120 → 150 →(199 → 0)→ 10 → 30 → 40,移动 153 + 199 + 40 = 392 个柱面。 |
| 换成 SSD | 想好了再点没有寻道时间和旋转延迟,访问时间与位置无关,用 FCFS 即可;要由 SSD 的控制器做磨损均衡。 |
| 簇 3000 中有一个扇区坏了,磁盘控制器用同一磁道上的备用扇区替换了它 | 想好了再点文件系统和驱动程序看到的簇号、物理地址都不变,控制器内部把这个扇区的读写转到备用扇区,对操作系统透明;只是备用扇区和另外 3 个扇区不相邻,读这个簇可能要多等一些旋转时间(10.3 坏块)。 |
预测大题:建在 SSD RAID 5 上的文件系统 拓展
RAID 和 SSD 在真题里只考过选择题。下面这道把它们和位示图、块号放在一起,作为预测。磨损次数用程序模拟了 12 万次随机小写核对。
某服务器用 4 块容量为 512GB 的 SSD(盘号 0~3)组成 RAID 5,条带单元为 4KB。第 k 个条带的校验块放在第 3 − (k mod 4) 号盘上(条带 0 在 3 号盘、条带 1 在 2 号盘、条带 2 在 1 号盘、条带 3 在 0 号盘,依此循环),其余 3 块盘按盘号从小到大依次存放该条带的 3 个数据块。在这个 RAID 5 上建立文件系统,盘块大小为 4KB(与条带单元相同),逻辑块号从 0 开始依次占用各条带的数据块,用位示图管理空闲块。
(1) 文件系统的可用容量是多少?共有多少个盘块?块号至少需要几位?位示图占多少空间?(3 分)
(2) 逻辑块 10 存放在哪块盘上?它所在条带的校验块在哪块盘上?(2 分)
(3) 文件系统改写逻辑块 10(4KB)时,RAID 5 要对 SSD 做哪几次读写?与直接写一块 SSD 相比,多了几次 I/O?(2 分)
(4) 若改用 RAID 4(校验块固定放在 3 号盘),在大量均匀随机的单块写入下,4 块盘被写入的次数之比约为多少?为什么说 RAID 5 对 SSD 的寿命更有利?(2 分)
(5) 2 号盘坏了,读逻辑块 10 要读哪些块?另外,删除文件时文件系统只在位示图中把块标为空闲,SSD 并不知道这些块已经没用了,这会带来什么问题?(2 分)
先在纸上自己做一遍,再点这里展开满分解答
- RAID 5 每个条带有 4 个块:3 个数据块 + 1 个校验块,所以只有 3/4 的空间放数据。
- 校验块 = 同一条带 3 个数据块的异或;任意一块盘坏了,用其余 3 块异或就能算回来。
- SSD 每块的擦写次数有限,写得越多坏得越快。
可用容量 = 3 × 512GB = 1536GB(1.5TB)。盘块数 = 1536GB ÷ 4KB = 1536 × 218 = 384M(402 653 184)个。228 < 384M ≤ 229,块号至少 29 位(实际用 4B 存放)。位示图 384M 位 = 48MB。
每个条带 3 个数据块:10 ÷ 3 = 3 余 1,在条带 3 中的第 2 个数据块。条带 3 的校验块在 3 − 3 = 0 号盘,数据块依次在 1、2、3 号盘上(逻辑块 9、10、11),所以逻辑块 10 在 2 号盘(该盘的第 4 个条带单元)。
① 读 2 号盘上逻辑块 10 的旧数据;② 读 0 号盘上的旧校验块;③ 算新校验 = 旧校验 ⊕ 旧数据 ⊕ 新数据;④ 写 2 号盘的新数据;⑤ 写 0 号盘的新校验。共 2 次读、2 次写,4 次 I/O;直接写一块 SSD 只要 1 次写,多了 3 次。
不用读同一条带的另外两个数据块:异或的性质让“旧校验 ⊕ 旧数据 ⊕ 新数据”直接等于新校验。
每次单块写都要写 1 个数据块和 1 个校验块。RAID 4 的校验全在 3 号盘:设共写 N 次,3 号盘被写 N 次,数据写平均分到 0~2 号盘,各约 N/3 次,比例约为 1 : 1 : 1 : 3(模拟 12 万次:约 4 万、4 万、4 万、12 万)。
RAID 5 的校验块轮流放在各盘,每块盘约被写 N/2 次,4 块盘磨损均匀(模拟:每块约 6 万次)。SSD 的擦写次数有限,RAID 4 的校验盘会先磨坏;RAID 5 相当于在盘与盘之间做了磨损均衡。
读 1 号盘的逻辑块 9、3 号盘的逻辑块 11、0 号盘的校验块,三者异或得到逻辑块 10,共读 3 块。此时阵列处于降级状态,再坏一块就会丢数据,应尽快换盘重建。
删除问题:SSD 仍把这些块对应的页当作有效数据,垃圾回收时还会把它们搬来搬去,增加写放大,降低性能和寿命。解决办法是文件系统删除文件时向 SSD 发 TRIM 命令,告诉它哪些逻辑块已经不用了(拓展)。
- RAID 5:可用容量 (n − 1)/n;小写 = 2 读 2 写;坏一块用其余 n − 1 块异或恢复。
- 校验块轮流放,既避免了校验盘成为写热点,也让各块 SSD 磨损均匀。
- 文件系统的“空闲”和 SSD 的“无效页”是两本账,要靠 TRIM 对上。
丢分点清单
- 簇号换算时忘了“一簇有几个扇区”(2019-44 是 2 个,本节模拟题是 4 个),每道簇数算错。
- 旋转延迟用了一整圈(应是半圈),或者多个扇区只加了一次。
- C-SCAN 返回段的计算口径不写清楚。
- SSTF 两边等距时没按题目规定处理。
- 磁头移动方向题目给了却没用。
本类其他真题
- 2010-45:位示图管理 16384 个块、C-SCAN 读 4 个扇区的总时间、Flash 用什么调度。
- 2022-44(计组):磁盘地址字段位数、扇区平均访问时间、DMA 请求次数。
- 2021-46:启动顺序、做启动盘的步骤(见 ⑬)。
11.14操作系统 ⑬ 过程分析题:谁在执行、CPU 在什么态、进程在什么状态
先读:3.6 进程的地址空间 5.4 中断的完整接力 5.6 系统调用 6.2 进程状态 9.5 I/O 软件层次 9.6 一次 scanf 的全过程 10.3 引导
- 5.1 模式切换与各程序运行在哪个态 —— “CPU 处于什么态”
- 6.2 状态转换原因表 —— “进程处于什么状态”
- 6.5 进程切换五步、模式切换 ≠ 进程切换 —— 切换时保存 / 恢复什么
- 5.5 多重中断时间线 —— 谁打断谁
- 9.8 设备分配与 SPOOLing —— 打印类过程题
这类题长什么样
近几年的新题型:不算数,而是给你一个过程(读一个字符、开机、运行一段 C 程序),问步骤的先后、每一步由谁执行、CPU 处于用户态还是内核态、进程处于什么状态、某个东西放在地址空间的哪个区域。这类题是计组和操作系统融合得最紧的地方,靠的是把几条“流水线”记熟。
| 常见问法 | 考过的地方 |
|---|---|
| 给出打乱的操作,排出正确顺序 | 2023-46(1),2021-46(1)(2) |
| 某一步之后 CPU 一定切换 / 进程才能被调度 | 2023-46(2) |
| 哪一步属于驱动程序 / 中断处理程序 / 系统调用 | 2023-46(3),2025-46(2),2019-44(3) |
| 此时进程是什么状态、CPU 在什么态 | 2023-46(4),2025-46(1),2017-45(3) |
| 某变量 / 代码 / PCB 在地址空间的哪个区域 | 2025-46 |
| 中断响应时 CPU 做了什么 | 2016-44(2),2025-44(2) |
做这类题必须会的 4 条“流水线”
① 一次设备输入的全过程(最重要)。
② “谁在执行、在什么态”对照表。
| 执行者 | CPU 状态 | 典型动作 |
|---|---|---|
| 用户程序、库函数(scanf、printf、malloc 的用户部分) | 用户态 | 计算、调用库函数、执行陷入指令 |
| 系统调用处理 / 设备无关软件 | 内核态 | 检查参数、查缓冲区、分配设备、把进程挂到阻塞队列 |
| 设备驱动程序 | 内核态 | 读写设备控制器的寄存器、启动设备、把数据搬进系统缓冲区、簇号 → 物理地址 |
| 中断处理程序 | 内核态 | 响应设备完成,调用驱动取数据,唤醒等待的进程 |
| 调度程序 | 内核态 | 从就绪队列选进程,进程切换 |
| 硬件(中断隐指令) | — | 关中断、保存断点和 PSW、识别中断源、转入处理程序、切到内核态 |
③ 进程地址空间里“住着谁”。从高地址到低地址:内核区(PCB、页表、内核栈等内核数据)→ 用户栈(局部变量、参数、返回地址)→ 共享库映射区(printf 等库代码)→ 堆(malloc)→ 读/写数据段(全局、静态变量)→ 只读代码段(函数的机器指令、常量字符串)。指针变量放在哪由它自己的类型决定,它指向的东西放在哪要另看(2025-46 的 ptr 在数据段,它指向的字符串在堆)。
④ 开机与准备启动盘。开机:ROM 中的引导程序 → 磁盘引导程序(MBR)→ 分区引导程序 → 操作系统初始化程序。准备启动盘:物理格式化 → 分区 → 逻辑格式化 → 安装操作系统;扇区在物理格式化时划分,根目录在逻辑格式化时建立。
解题模板
- 先按时间讲一遍故事。用“进程发出请求 → 发现要等 → 阻塞 → 设备完成 → 中断 → 取数据 → 唤醒 → 调度 → 返回”这条主线,把题目给的每个编号操作放到正确的位置上。
- 给每一步标三样东西:谁执行(用户程序 / 内核某部分 / 硬件)、CPU 在什么态、进程在什么状态。
- 回答“之后一定……”类问题:找状态发生变化的那一步(进阻塞队列 → 一定切换;进就绪队列 → 才能被调度)。
- 地址空间题:先判断变量类型(全局 / 局部 / malloc / 代码 / 内核数据),再对号入座。
进程 P 通过执行系统调用从键盘接收一个字符的输入,已知此过程中与进程 P 相关的操作包括:①将进程 P 插入就绪队列;②将进程 P 插入阻塞队列;③将字符从键盘控制器读入系统缓冲区;④启动键盘中断处理程序;⑤进程 P 从系统调用返回;⑥用户在键盘上输入字符。以上编号①~⑥仅用于标记操作,与操作的先后顺序无关。请回答下列问题。
(1) 按照正确的操作顺序,操作①的前一个和后一个操作分别是上述操作中的哪一个?操作⑥的后一个操作上述操作中的哪一个?
(2) 在上述哪个操作之后 CPU 一定从进程 P 切换到其他进程?在上述哪个操作之后 CPU 调度程序才能选择进程 P 执行?
(3) 完成上述哪个操作的代码属于键盘驱动程序?
(4) 键盘中断处理程序执行时,进程 P 处于什么状态?CPU 处于内核态还是用户态?
先在纸上自己做一遍,再点这里展开满分解答
考点9.6 一次输入的全过程6.2 进程状态9.5 I/O 软件层次训练营 ⑬
- P 执行系统调用要读一个字符,这时键盘缓冲区里还没有字符 → P 只能等:② 把 P 插入阻塞队列,CPU 切换去运行别的进程。
- 过了一会儿,⑥ 用户在键盘上输入字符,键盘控制器向 CPU 发中断。
- CPU 响应中断,④ 启动(执行)键盘中断处理程序,它调用驱动程序 ③ 把字符从键盘控制器读入系统缓冲区,然后唤醒 P:① 把 P 插入就绪队列。
- P 被调度程序选中后继续执行,⑤ 从系统调用返回,拿到字符。
正确顺序为 ② → ⑥ → ④ → ③ → ① → ⑤。
操作 ① 的前一个操作是 ③,后一个操作是 ⑤;操作 ⑥ 的后一个操作是 ④。
操作 ②(P 插入阻塞队列)之后,P 进入阻塞态,不能继续运行,CPU 一定从 P 切换到其他进程。
操作 ①(P 插入就绪队列)之后,P 处于就绪态,调度程序才能选择 P 执行。
操作 ③(将字符从键盘控制器读入系统缓冲区):它直接读写键盘控制器中的寄存器,与具体硬件相关,属于键盘驱动程序。
P 处于阻塞态(中断处理程序执行完 ③ 后才把它唤醒);CPU 处于内核态(中断处理程序是操作系统内核的代码)。
- 读设备的标准时间线:系统调用 → 阻塞 → 设备完成 → 中断 → 中断处理/驱动取数据 → 唤醒(就绪)→ 被调度 → 系统调用返回。
- 跟具体硬件寄存器打交道的是驱动程序;中断处理程序和驱动都在内核态运行。
模拟大题:一次缺页的全过程(按 408 风格自编)
真题考过“读一个字符”(2023-46)、开机引导(2021-46)和地址空间(2025-46)。还有一条很可能出题的流水线没考过:缺页。它一题就能把计组(异常的类型、断点、MMU)和操作系统(阻塞、唤醒、调度)都考到。下面按 2023-46 的问法自编一道。
单 CPU 系统采用请求分页存储管理。进程 P 在用户态执行指令 mov eax, [ebx],读取数组的一个元素,该元素所在的页不在内存中,内存中还有空闲页框,就绪队列中有进程 Q。这次访问涉及下列操作(顺序已打乱):
- ① 调度程序选中 P,恢复 P 的现场
- ② MMU 查页表,发现该页的存在位为 0,产生缺页异常
- ③ 磁盘控制器完成读操作,向 CPU 发出中断请求
- ④ 缺页异常处理程序分配一个空闲页框,调用磁盘驱动程序启动磁盘读入该页,并把 P 插入阻塞队列
- ⑤ 页面读入完成后,内核修改 P 的页表项(填入页框号、存在位置 1),唤醒 P,把它插入就绪队列
- ⑥ P 执行 mov 指令,这次访存成功,读出数据
- ⑦ CPU 保存断点和程序状态字,切换到内核态,转去执行缺页异常处理程序
- ⑧ 调度程序选中进程 Q 运行
(1) 写出这些操作的正确执行顺序。(2 分)
(2) 哪些操作是由硬件直接完成的,不执行任何程序代码?哪些操作在内核态执行?执行 ⑥ 时 CPU 处于什么态?(2 分)
(3) 在操作 ④ 与 ⑤ 之间,P 处于什么状态?在 ⑤ 与 ① 之间呢?操作 ③ 发生时,CPU 正在执行哪个进程?(2 分)
(4) 缺页异常属于故障、自陷还是终止?操作 ⑦ 保存的断点是哪条指令的地址,为什么?如果换成 P 执行系统调用 read(),返回后接着执行哪条指令?(2 分)
(5) 操作 ② 的缺页异常和操作 ③ 的磁盘中断,CPU 分别在什么时候发现并处理?(1 分)
先在纸上自己做一遍,再点这里展开满分解答
考点8.7 缺页处理全过程5.3 故障、自陷、终止5.4 中断的完整接力6.2 状态转换
- 主线和“读一个字符”一样,只是起点换成了异常:出错 → 进内核 → 发起 I/O、阻塞 → 调度别人 → 设备中断 → 唤醒 → 调度回来 → 继续。
- 有两点和系统调用不同:缺页是 MMU 在指令执行过程中发现的;处理完要让原来那条指令重新执行。
- 有空闲页框,所以不用选淘汰页,也不用写回。
② → ⑦ → ④ → ⑧ → ③ → ⑤ → ① → ⑥。
MMU 发现缺页(②),CPU 响应异常进入内核(⑦)。缺页处理程序启动磁盘并让 P 阻塞(④),CPU 转去运行 Q(⑧)。磁盘读完发中断(③),内核修改页表并唤醒 P(⑤)。P 再次被调度(①),重新执行 mov 指令(⑥)。
④ 之后 P 已经阻塞,CPU 一定会切换给别的进程(⑧),不会空等磁盘。⑤ 只是把 P 放进就绪队列,要等 ① 被调度后 P 才真正运行。
由硬件直接完成的:②(MMU 地址转换时检测)、③(磁盘控制器发中断请求)、⑦(中断隐指令:保存断点和 PSW、切换到内核态、转到处理程序)。
在内核态执行的:④、⑤、⑧、①。缺页异常处理程序、磁盘驱动程序、中断处理程序、调度程序都是内核代码。
执行 ⑥ 时 CPU 处于用户态:从异常返回时恢复了 P 的 PSW,回到用户态继续执行 P 的指令。
④ 与 ⑤ 之间:P 处于阻塞态,在等待磁盘把页面读入。
⑤ 与 ① 之间:P 处于就绪态,已被唤醒,等待调度。
③ 发生时,CPU 正在执行进程 Q。随后的中断处理在 Q 运行期间进行,它刚开始执行时 P 仍是阻塞态,由 ⑤ 唤醒后才变为就绪态。
不要以为磁盘一发中断,P 就立刻恢复运行。中断处理只负责唤醒 P,让它进入就绪态;P 要等调度程序选中(①)才能再运行。
缺页异常属于故障,而且是可以恢复的故障。
⑦ 保存的断点是引起缺页的这条 mov 指令本身的地址,不是下一条指令的地址。原因:这条指令在取操作数时被打断,并没有执行完;把页面调入后要重新执行它,所以 ⑥ 执行的仍是这条 mov 指令。
系统调用属于自陷(陷入)。执行陷入指令时,这条指令本身就已经完成了,所以断点是陷入指令的下一条指令的地址,返回后从下一条接着执行。
缺页异常是在 mov 指令执行过程中(访存时)由 MMU 发现的,CPU 立即处理,不等这条指令结束。
磁盘中断是在 Q 运行时到来的外部中断。CPU 在 Q 的当前指令执行结束后检查中断请求,而且要在开中断的状态下才响应。
- 缺页题的主线:异常 → 阻塞 → 调度别人 → 中断 → 唤醒 → 调度回来 → 重新执行原指令。
- 变式:没有空闲页框,并且选中的淘汰页被修改过(修改位为 1)。这时 ④ 要先按置换算法选出淘汰页,把它写回磁盘,再读入新页。P 要等两次磁盘操作,阻塞时间更长。
- “断点是哪条指令”只看异常类型:故障返回原指令,自陷和中断返回下一条。
举一反三:条件一变,答案怎么变
以上面的模拟题(一次缺页)为基准,每行只改一个条件。先自己想出新答案和理由,再点开核对。
| 条件怎么变 | 新答案和理由 |
|---|---|
| 没有空闲页框,选中的淘汰页修改位为 1 | 想好了再点④ 中要先按置换算法选出淘汰页,启动磁盘把它写回,再读入新页。P 在两次 I/O 期间都是阻塞态。 |
| P 执行 read() 从磁盘读文件 | 想好了再点执行陷入指令进入内核,属于自陷,断点是下一条指令。I/O 完成后 P 被唤醒;被调度后,从 read 之后继续执行。 |
| P 做除法时除数为 0 | 想好了再点属于故障,但处理程序无法修复,一般终止 P,不会返回重新执行。 |
| P 访问的地址超出了自己的地址空间 | 想好了再点地址越界(存储保护错),通常终止进程(“段错误”)。 |
| P 运行时时间片用完 | 想好了再点时钟中断(外部中断)。P 由运行态变为就绪态(不是阻塞态),断点是下一条指令。 |
| TLB 缺失,但页在内存中 | 想好了再点由 MMU 硬件查页表得到页框号(x86 的做法),不进入操作系统,P 一直是运行态。只有页不在内存时才会产生缺页异常。 |
| P 执行的是写指令 mov [ebx], eax;该页在内存中,但它是 fork 后与父进程写时复制共享的只读页 | 想好了再点MMU 查页表:页在内存,但要写只读页 → 保护异常(故障),陷入内核。内核发现这是写时复制页,就分配一个空闲页框、复制原页内容,把 P 的页表项改为指向新页框并设为可写,然后返回重新执行这条写指令。整个过程没有磁盘 I/O,P 不阻塞,也不用调度 Q(8.7 写时复制)。 |
| P 改用异步 I/O(aio_read)读文件 | 想好了再点P 发出请求后立即返回,继续在用户态运行,不进入阻塞态;磁盘完成后由中断处理程序把数据放好,再由系统用信号或回调通知 P(9.5 三种 I/O)。 |
丢分点清单
- 认为中断处理程序执行时进程 P 已经是就绪态或运行态(它要等唤醒之后才就绪)。
- 把“启动中断处理程序”放在“读字符”之后(先进中断处理程序,再由它调用驱动读字符)。
- scanf、printf 属于库函数,但它们的功能最终要经系统调用由驱动程序完成——两者都要答。
- 把 PCB 放在用户区。
- 引导顺序漏掉 ROM 中的引导程序。
本类其他真题
- 2021-46:多阶段引导的顺序、做启动盘的四个操作顺序、扇区划分和根目录建立在哪一步。
- 2025-46:一段 malloc + scanf 程序:PCB、main 代码、ptr、length、字符串分别在哪个区,哪些函数要用驱动程序。
- 2017-45(3):scanf 过程中进程状态的变化、CPU 是否进入内核态。
- 2016-44(2)、2025-44(2):中断/异常响应时 CPU 做什么(计组角度)。
11.15整卷模拟:两套限时大题卷
前面 13 类题型是一类一类练的。真实考试里,计组和操作系统的 4 道大题连在一起出现,而且一道题常常横跨好几类:一道 Cache 题会问到分页,一道 I/O 题会问到中断屏蔽。下面两套卷子按 408 的格式出题(第 43、44 题计组,第 45、46 题操作系统,分值与 2026 年相同:10 + 15 + 7 + 8 = 40 分),场景和数字都是新的,每题都附满分答案和评分细则。
- 限时 65 分钟,闭卷,一次做完 4 道题,写在纸上。408 全卷 180 分钟,这 4 道题一般要留出 60~70 分钟。
- 做完再点开答案,按评分细则逐个得分点给自己打分:写出了关键步骤就给分,只有结果、没有过程的要扣分。
- 记下每题的得分。某一题低于该题分值的一半,就回到它“涉及题型”标出的那几节重新练。
模拟卷一
某 32 位计算机,int 型用 32 位补码表示,float 型采用 IEEE 754 单精度格式。编译器会把乘以常数的运算优化为移位和加法。有如下 C 代码:
int x = 0x0CCCCCCD; int a = x * 10; // 被编译为 (x << 3) + (x << 1) int y = -7; int q = y / 4; int r = y >> 2; float f = x;
(1) x << 3、x << 1 和 a 的机器数各是多少(用十六进制表示)?a 的值是多少?这次乘法是否溢出?说明理由。(3 分)
(2) 若改用乘法指令计算 x × 10,得到 64 位乘积。本例的 64 位乘积是多少?硬件怎样根据乘积判断 int 型乘法是否溢出?(2 分)
(3) q 和 r 的值各是多少?为什么编译器不能把 y / 4 直接编译成算术右移 2 位?编译器通常怎样处理?(2 分)
(4) f 的机器数是多少(用十六进制表示)?f 的值与 x 相差多少?为什么?(3 分)
做完再点开:满分答案与评分细则
x << 3 = 6666 6668H,x << 1 = 1999 999AH,a = 6666 6668H + 1999 999AH = 8000 0002H,按补码读值为 −231 + 2 = −2147483646。
溢出。x = 214748365,真实乘积 2147483650 > 231 − 1;从运算上看,两个正数相加得到负数,最高位进位 Cn = 0、次高位进位 Cn−1 = 1,OF = 1。
64 位乘积 = 0000 0000 8000 0002H。
判断规则:带符号乘法取低 32 位作结果,只有当高 32 位的每一位都等于低 32 位的最高位时才不溢出。本例低 32 位的最高位是 1,而高 32 位全为 0,所以溢出。
q = −1(C 语言整数除法向 0 取整,−1.75 → −1);r = −2(算术右移相当于向下取整,−1.75 → −2)。
y 为负数时两者结果不同,所以不能直接用右移代替除法。编译器的做法是:y 为负时先加上偏置 22 − 1 = 3 再右移,即 (−7 + 3) >> 2 = −1,与除法结果一致。
x = 1100 1100 1100 1100 1100 1100 1101B,共 28 位有效位,= 1.100 1100 … 1101B × 227。阶码 E = 27 + 127 = 154 = 1001 1010B。
尾数只能放 23 位,要舍去最低 4 位 1101B。它大于一半(1000B),按就近舍入要进 1,尾数 = 100 1100 1100 1100 1100 1101B。
f = 0 10011010 10011001100110011001101B = 4D4C CCCDH,值 = x − 13 + 16 = 214748368,比 x 大 3。原因:float 只有 24 位有效位,x 需要 28 位,无法精确表示。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | x << 3、x << 1 的机器数正确 | 1 |
| a 的机器数和值正确 | 1 | |
| 判断为溢出,并给出理由(真值超范围或 OF = 1) | 1 | |
| (2) | 64 位乘积正确 | 1 |
| 写出“高 32 位全等于低 32 位最高位才不溢出” | 1 | |
| (3) | q = −1、r = −2 | 1 |
| 说明取整方向不同,并给出加偏置的处理 | 1 | |
| (4) | 阶码 154 和规格化形式正确 | 1 |
| 舍入过程正确,机器数 4D4C CCCDH | 1 | |
| 相差 3,并说明有效位只有 24 位 | 1 |
某计算机的虚拟地址和物理地址都是 32 位,按字节编址,页大小 4KB。TLB 共 16 项,全相联,LRU 替换。数据 Cache 容量 16KB,4 路组相联,块大小 64B,LRU 替换,用物理地址访问。操作系统采用请求分页、局部置换、LRU 置换算法。有如下程序:
int a[1024][1024]; // 起始虚拟地址 0100 0000H
for (j = 0; j < 1024; j++) // 变量 i、j、sum 都在寄存器中
for (i = 0; i < 1024; i++)
sum += a[i][j];
程序开始时 TLB 和 Cache 都为空,a 的所有页都不在内存中。
(1) 数组 a 共占多少页?a[1][2] 的虚拟地址是多少?它的虚页号和页内偏移各是多少?(3 分)
(2) 物理地址中 Cache 的 Tag、组号、块内地址各占几位?数组元素落在 Cache 的哪一组,为什么与操作系统把页放在哪个页框无关?(3 分)
(3) 执行这段程序时,访问数组 a 的 Cache 命中率是多少?若把两重循环交换(i 在外层),命中率是多少?(4 分)
(4) 按原程序,访问 a 时 TLB 的命中率是多少?交换循环后呢?(3 分)
(5) 若系统只给 a 分配 512 个页框,原程序和交换后的程序各发生多少次缺页?(2 分)
做完再点开:满分答案与评分细则
考点8.3 Cache8.5 TLB8.7 置换算法训练营 ⑤ 模拟题
- 一行 a[i] 有 1024 × 4B = 4KB,恰好一页,而且 a 的起始地址页对齐:第 i 行就是第 i 页。
- Cache 行数 = 16KB ÷ 64B = 256,4 路,所以有 64 组:块内地址 6 位,组号 6 位,两者合起来 12 位,正好是页内偏移。
- 原程序按列访问:相邻两次访问相差 4KB,即换了一页,而页内偏移不变。
a 占 1024 × 1024 × 4B = 4MB,共 4MB ÷ 4KB = 1024 页。
a[1][2] 的虚拟地址 = 0100 0000H + (1 × 1024 + 2) × 4 = 0100 0000H + 1008H = 0100 1008H,虚页号 = 01001H,页内偏移 = 008H。
块内地址 6 位(第 5~0 位),组号 6 位(第 11~6 位),Tag = 32 − 12 = 20 位(第 31~12 位)。
组号和块内地址都在低 12 位,也就是页内偏移里。地址转换只替换页号,页内偏移不变,所以一个元素落在哪一组只由它的页内偏移决定,与页框号无关。
原程序:命中率为 0。a[i][j] 与 a[i+1][j] 相差 4KB,页内偏移相同,所以同一列的 1024 个元素都映射到同一组,而一组只有 4 行。内层循环每访问一个元素就调入一个新块,这一组中的块不断被替换。等到访问下一列 a[i][j+1] 时,它所在的块(与 a[i][j] 同一块)早已被换出,所以每次访问都缺失。
交换循环后:命中率为 15/16 = 93.75%。按行顺序访问,每块 64B 放 16 个 int,每块只有第一次访问缺失。
判断按列访问的命中率,关键是算清楚“同一组要放多少个块”:这里是 1024 个块,远超 4 路。
原程序:命中率为 0。内层循环依次访问第 0、1、…、1023 页,每次都换一页。TLB 只有 16 项,按 LRU 替换,一页被访问过之后,要再过 1024 次访问才会用到它,而那时它早已被换出。
交换循环后:命中率为 1023/1024 ≈ 99.9%。每一页连续访问 1024 次,只有第一次 TLB 缺失。
原程序:1024 × 1024 = 1 048 576 次。每一列都按第 0 ~ 1023 页的顺序访问一遍。只有 512 个页框,LRU 下要访问的页恰好总是最久没用、已被换出的那一页,每次访问都缺页。
交换循环后:1024 次。每页只在第一次访问时缺页,用完这一页才访问下一页。
这就是“循环访问的页数多于页框数”时 LRU 的最坏情况,也是程序局部性差会导致抖动的原因。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | 1024 页 | 1 |
| a[1][2] 的虚拟地址 0100 1008H | 1 | |
| 虚页号 01001H、偏移 008H | 1 | |
| (2) | Tag 20、组号 6、块内 6 | 2 |
| 说明组号在页内偏移中 | 1 | |
| (3) | 原程序命中率 0,并说明同一列映射到同一组、放不下 | 2 |
| 交换后 93.75%,并说明每块 16 个元素 | 2 | |
| (4) | 原程序 TLB 命中率 0,并给出理由 | 2 |
| 交换后 1023/1024 | 1 | |
| (5) | 原程序 1 048 576 次 | 1 |
| 交换后 1024 次 | 1 |
理发店里有 1 位理发师、1 把理发椅和 n 把供等候的椅子。没有顾客时,理发师在理发椅上睡觉。顾客到来时:若理发师在睡觉,就叫醒他;若理发师正忙而还有空的等候椅,顾客就坐下等候;若等候椅已坐满,顾客直接离开。
(1) 用信号量和 P、V 操作描述理发师和顾客的同步过程,说明信号量的含义和初值。(5 分)
(2) 有同学想用一个初值为 n 的信号量 chairs 表示空的等候椅,让顾客先执行 P(chairs) 再坐下。这样做为什么不符合题意?(2 分)
做完再点开:满分答案与评分细则
- 理发师和顾客是一对同步关系:理发师等顾客(没顾客就睡),顾客等理发师(理发师忙就等)。两个方向各用一个同步信号量。
- “坐满就离开”要求顾客先判断、再决定是否等待,信号量的 P 操作做不到,只能用一个受 mutex 保护的计数器。
int waiting = 0; // 正在等候的顾客数
semaphore mutex = 1; // 互斥访问 waiting
semaphore customers = 0; // 等候理发的顾客数,理发师在它上面睡觉
semaphore barber = 0; // 理发师是否准备好,顾客在它上面等
理发师() {
while (1) {
P(customers); // 没有顾客就睡觉
P(mutex);
waiting--; // 一位顾客离开等候椅
V(barber); // 通知这位顾客:理发师准备好了
V(mutex);
理发;
}
}
顾客() {
P(mutex);
if (waiting < n) {
waiting++;
V(customers); // 若理发师在睡觉,唤醒他
V(mutex);
P(barber); // 等理发师准备好
坐上理发椅,理发;
} else {
V(mutex); // 等候椅已满,离开
}
}
顾客在 else 分支里也必须 V(mutex),否则后面的人都进不来。若题目还要求“理完发顾客才能离开”,就再加一个 semaphore finished = 0:理发师理完 V(finished),顾客理发后 P(finished)。
等候椅坐满时,chairs 的值为 0,顾客执行 P(chairs) 会阻塞等待,一直等到有空椅。但题目要求坐满时顾客直接离开。P 操作不能“试一下、不行就走”,所以要用计数器 waiting 配合 mutex,先判断再决定是否等待。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | 信号量和计数器的定义、含义、初值正确 | 1 |
| 理发师:P(customers) 等顾客,V(barber) 通知顾客 | 2 | |
| 顾客:在 mutex 保护下判断 waiting < n,满了就离开(两条路径都释放 mutex) | 1 | |
| 顾客:V(customers) 唤醒理发师,P(barber) 等待 | 1 | |
| (2) | 指出 P(chairs) 会阻塞而不是离开,需要“先判断再决定” | 2 |
某文件系统的磁盘块大小为 1KB,块号占 4B。文件 F 共占 10 个磁盘块,它的 FCB 已在内存中,FCB 中记有 F 的首块号和末块号。
(1) 若 F 采用隐式链接分配(每块最后 4B 存放下一块的块号),读 F 中偏移量为 5000 处的字节需要读几次磁盘?若改为显式链接分配(FAT 已在内存中)呢?(3 分)
(2) 若 F 的第 3 块(从 0 开始编号)损坏,两种分配方式下各会有什么后果?(1 分)
(3) 顺序读入 F 的全部 10 块并逐块处理:把一块读入系统缓冲区需 100μs,从缓冲区传到用户区需 20μs,CPU 处理一块需 50μs。采用单缓冲和双缓冲时,各需要多少时间?(3 分)
(4) 在隐式链接分配下,向 F 的末尾追加一块,至少要访问几次磁盘?(1 分)
做完再点开:满分答案与评分细则
考点10.6 三种分配方式对比9.7 单缓冲与双缓冲训练营 ⑪
隐式链接:每块存数据 1024 − 4 = 1020B。5000 ÷ 1020 = 4 余 920,在第 4 块。只能从首块开始顺着指针读第 0、1、2、3、4 块,共 5 次。
显式链接:指针都在 FAT 中,每块存数据 1024B。5000 ÷ 1024 = 4 余 904,也在第 4 块。在内存的 FAT 中从首块号出发找到第 4 块的块号,只读这一块,共 1 次。
隐式链接每块要留 4B 放指针,有效数据只有 1020B,算块号时不能再除以 1024。
隐式链接:第 4 块的块号存放在第 3 块中,第 3 块损坏后链就断了,第 3 块及其后的所有块都无法访问。显式链接:链保存在 FAT 中,只丢失第 3 块的数据,其余各块仍能访问。
设 T = 100μs(读入缓冲区)、M = 20μs(传到用户区)、C = 50μs(处理)。第一块要依次经过 T、M、C,共 170μs;之后每块增加一个“周期”。
单缓冲:每块的周期 = max(C, T) + M = 120μs,总时间 = 170 + 9 × 120 = 1250μs。
双缓冲:每块的周期 = max(T, C + M) = 100μs,总时间 = 170 + 9 × 100 = 1070μs。
单缓冲时,下一块要等缓冲区里的数据传走(M)才能读入,所以 M 不能和 T 重叠;双缓冲时两个缓冲区轮流使用,M + C 可以和 T 完全重叠。
至少 3 次:把新数据写入一个空闲块(1 次);读出原来的末块(1 次),把新块的块号填进它的指针后写回(1 次)。FCB 中的末块号在内存中修改。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | 隐式链接:块内有效数据 1020B,块号 4,读 5 次 | 2 |
| 显式链接:读 1 次 | 1 | |
| (2) | 隐式链接后续全部丢失,FAT 只丢这一块 | 1 |
| (3) | 写出单缓冲、双缓冲每块的周期 | 1 |
| 单缓冲 1250μs | 1 | |
| 双缓冲 1070μs | 1 | |
| (4) | 3 次,并说明写新块、读写原末块 | 1 |
模拟卷二
某计算机有 A、B、C 三个中断源,中断响应优先级为 A > B > C。通过设置中断屏蔽字,把中断处理优先级改为 C > A > B。屏蔽字按 A、B、C 的顺序各占 1 位,1 表示屏蔽。中断向量表从主存 0 号单元开始,每个中断向量占 4 字节,A、B、C 的中断类型号分别为 8、9、10。
A、B、C 的中断服务程序分别需要执行 10μs、20μs、15μs。t = 0 时 B、C 同时发出中断请求,t = 5μs 时 A 发出中断请求。CPU 原来在开中断状态下执行主程序。忽略中断响应、保存和恢复现场等开销,各中断服务程序在设置好新的屏蔽字后立即开中断。
(1) 写出 A、B、C 的中断服务程序中设置的屏蔽字。(3 分)
(2) 画出 CPU 执行程序的轨迹,写出 A、B、C 的中断服务程序各自完成的时刻。(4 分)
(3) B 的中断向量地址是多少?中断向量地址与中断向量有什么区别?(2 分)
(4) t = 5μs 时 A 的请求被屏蔽了,这个请求会丢失吗?它在什么时候被响应?(1 分)
做完再点开:满分答案与评分细则
考点5.5 多重中断与屏蔽字5.5 响应顺序 ≠ 处理顺序5.4 中断的完整接力
每个中断服务程序屏蔽它自己,以及处理优先级比它低的中断。处理优先级 C > A > B:
A 的屏蔽字 = 110(屏蔽 A、B);B 的屏蔽字 = 010(只屏蔽自己);C 的屏蔽字 = 111(全部屏蔽)。
t = 0:B、C 同时请求,按响应优先级先响应 B。B 的服务程序设置屏蔽字 010 后开中断,C 没有被屏蔽,立即被响应,C 执行 0 ~ 15μs。
t = 5:A 请求,但 C 的屏蔽字为 111,A 被屏蔽,等待。
t = 15:C 完成,返回 B,恢复屏蔽字 010。A 没有被屏蔽,立即被响应,A 执行 15 ~ 25μs。
t = 25:A 完成,返回 B,B 执行 25 ~ 45μs,然后返回主程序。
轨迹:主程序 → B(刚进入就被 C 打断)→ C → B(刚恢复就被 A 打断)→ A → B → 主程序。完成时刻:C 为 15μs,A 为 25μs,B 为 45μs。
先被响应的 B 最后才完成:响应顺序由硬件决定,处理顺序由屏蔽字决定,两者可以不同。
B 的中断向量地址 = 9 × 4 = 36 = 0024H。
中断向量是中断服务程序的入口地址;中断向量地址是存放这个入口地址的存储单元的地址。CPU 响应中断时,由中断类型号算出向量地址,从中取出中断向量送入 PC。
不会丢失。A 的请求保存在中断请求寄存器(中断请求触发器)中。t = 15μs 时 C 返回,屏蔽被解除,A 在这时被响应。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | 三个屏蔽字各 1 分 | 3 |
| (2) | 轨迹正确(B 先响应、C 立即嵌套、A 在 15 被响应) | 2 |
| 三个完成时刻正确 | 2 | |
| (3) | 向量地址 0024H | 1 |
| 正确区分中断向量与中断向量地址 | 1 | |
| (4) | 不丢失,t = 15μs 时被响应 | 1 |
某 16 位计算机按字节编址,采用 16 位定长指令字,有 8 个通用寄存器 r0 ~ r7。指令格式如下:
| 类型 | 格式(从高位到低位) | 指令 |
|---|---|---|
| R 型 | op(4 位)| rs(3 位)| rt(3 位)| rd(3 位)| funct(3 位) | add、sub、and、or、slt 等:rd ← rs op rt;所有 R 型指令的 op 都是 0000,用 funct 区分 |
| I 型 | op(4 位)| rs(3 位)| rt(3 位)| imm(6 位,补码) | addi:rt ← rs + imm;lw:rt ← M[rs + imm];sw:M[rs + imm] ← rt;beq:若 rs = rt,则 PC ← PC + 2 + imm × 2 |
该机先采用单周期数据通路实现。数据通路中有 3 个二选一多路选择器:RegDst 选写入寄存器的编号(0 选 rt,1 选 rd);ALUSrc 选 ALU 的第二个操作数(0 选 rt 的内容,1 选符号扩展后的 imm);MemtoReg 选写回寄存器的数据(0 选 ALU 结果,1 选数据存储器读出的数据)。另有控制信号 RegWrite、MemRead、MemWrite 和 Branch(Branch 与 ALU 的“结果为 0”信号相与,决定下一条指令的地址)。
(1) R 型指令最多有几条?除 R 型外,最多还能定义几种 I 型指令?imm 的取值范围是多少?lw 能访问的地址范围是多少(用 rs 表示)?(3 分)
(2) 某条 beq 指令位于 1020H,其 imm 字段为 3AH(6 位),转移成功时目标地址是多少?若要转移到 1040H,imm 字段应为多少?(3 分)
(3) 写出 add、lw、sw、beq 四条指令执行时 7 个控制信号的取值(无关的写 ×)。(4 分)
(4) 各部件的延迟为:取指令 200ps,读寄存器 100ps,ALU 150ps,数据存储器 200ps,写寄存器 50ps,其余忽略。单周期 CPU 的时钟周期至少多长?若改为 5 段流水线(段寄存器延迟 20ps),时钟周期多长?执行 106 条指令(不发生阻塞)时,流水线 CPU 比单周期 CPU 快多少倍?(3 分)
(5) 在有转发的 5 段流水线中,若 lw r1, 0(r2) 之后紧跟 add r3, r1, r4,会发生什么?编译器可以怎样避免?(2 分)
做完再点开:满分答案与评分细则
考点3.1 扩展操作码与字段3.2 相对寻址4.2 单周期数据通路4.4 冒险的对策
R 型共用 op = 0000,用 3 位 funct 区分,最多 8 条。op 共 16 种,除去 0000,最多还能定义 15 种 I 型指令。
imm 是 6 位补码,范围 −32 ~ +31。lw 访问的地址为 (rs) − 32 ~ (rs) + 31。
3AH 的 6 位二进制为 111010,是 −6 的补码。目标 = 1020H + 2 + (−6) × 2 = 1022H − 0CH = 1016H。
转移到 1040H:imm = (1040H − 1022H) ÷ 2 = 1EH ÷ 2 = 15 = 001111B,即 0FH。
两处容易错:偏移相对的是 PC + 2(下一条指令的地址);imm 以“指令字”(2 字节)为单位,要乘 2。
| 指令 | RegDst | ALUSrc | MemtoReg | RegWrite | MemRead | MemWrite | Branch |
|---|---|---|---|---|---|---|---|
| add | 1 | 0 | 0 | 1 | 0 | 0 | 0 |
| lw | 0 | 1 | 1 | 1 | 1 | 0 | 0 |
| sw | × | 1 | × | 0 | 0 | 1 | 0 |
| beq | × | 0 | × | 0 | 0 | 0 | 1 |
理由:add 写 rd,两个操作数都来自寄存器;lw 写 rt,用 rs + imm 算地址,写回的是存储器读出的数据;sw 和 beq 不写寄存器,所以 RegDst、MemtoReg 无关;beq 用 ALU 比较 rs 与 rt(做减法,看结果是否为 0),第二个操作数来自 rt。
不写寄存器时,RegWrite 必须是 0,不能写 ×;不写存储器时 MemWrite 必须是 0。只有“选了也不会被用到”的多路选择器信号才能写 ×。
单周期的时钟周期由最慢的指令 lw 决定:200 + 100 + 150 + 200 + 50 = 700ps。
流水线的时钟周期 = 最长一段 + 段寄存器延迟 = 200 + 20 = 220ps。
单周期:106 × 700ps = 700μs;流水线:(5 + 106 − 1) × 220ps ≈ 220μs。流水线约快 700 ÷ 220 ≈ 3.18 倍。
add 要用 lw 读出的 r1,而 r1 要到 lw 的 M 段结束才得到,转发也来不及,流水线要阻塞 1 个时钟周期(load-use 冒险)。
编译器可以调整指令顺序,在 lw 和 add 之间插入一条与它们无关的指令;找不到合适的指令时插入 nop。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | R 型 8 条、I 型最多 15 种 | 2 |
| imm 范围与 lw 地址范围 | 1 | |
| (2) | 目标地址 1016H(含 −6 与 ×2 的过程) | 2 |
| imm = 0FH | 1 | |
| (3) | 每条指令一行,每行 1 分 | 4 |
| (4) | 700ps、220ps | 2 |
| 加速比约 3.18 | 1 | |
| (5) | 阻塞 1 拍、调度指令或插 nop | 2 |
单 CPU 系统中有一台磁盘,磁盘一次只为一个进程服务。进程 P1、P2 在 0 时刻都已进入就绪队列,P1 的优先级高于 P2。它们的运行过程如下(忽略调度和切换的开销):
P1:计算 20ms → 磁盘 I/O 30ms → 计算 10ms
P2:计算 40ms → 磁盘 I/O 20ms → 计算 20ms
(1) 采用抢占式优先级调度,画出 CPU 和磁盘的甘特图,求 P1、P2 的完成时刻和 CPU 利用率。(3 分)
(2) 按 (1) 的调度,写出 P2 从开始到结束经历的状态变化。(1 分)
(3) 改为非抢占式优先级调度,P1、P2 的完成时刻和 CPU 利用率各是多少?与 (1) 相比,两个进程的平均周转时间有什么变化?(3 分)
做完再点开:满分答案与评分细则
CPU:0 ~ 20 P1;20 ~ 50 P2;50 时 P1 的 I/O 完成,优先级高,抢占 P2:50 ~ 60 P1(P1 完成);60 ~ 70 P2(算完剩下的 10ms);70 ~ 90 空闲;90 ~ 110 P2。
磁盘:20 ~ 50 P1;70 ~ 90 P2。
完成时刻:P1 为 60ms,P2 为 110ms。CPU 忙 20 + 30 + 10 + 10 + 20 = 90ms,利用率 = 90 ÷ 110 ≈ 81.8%。
就绪(0)→ 运行(20)→ 就绪(50,被 P1 抢占)→ 运行(60)→ 阻塞(70,等磁盘 I/O)→ 就绪(90,I/O 完成)→ 运行(90)→ 结束(110)。
CPU:0 ~ 20 P1;20 ~ 60 P2(50 时 P1 就绪,但不能抢占);60 ~ 70 P1(完成);70 ~ 80 空闲;80 ~ 100 P2。磁盘:20 ~ 50 P1;60 ~ 80 P2。
完成时刻:P1 为 70ms,P2 为 100ms。CPU 利用率 = 90 ÷ 100 = 90%。
平均周转时间:抢占式为 (60 + 110) ÷ 2 = 85ms,非抢占式为 (70 + 100) ÷ 2 = 85ms,相同。抢占式让高优先级的 P1 早完成 10ms,但推迟了 P2 的 I/O,CPU 多空闲了 10ms,P2 晚完成 10ms。
这类“计算 + I/O”交替的题,要同时画 CPU 和设备两条时间线。进程做 I/O 时处于阻塞态,不占 CPU。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | 甘特图正确(含 50 时的抢占) | 1 |
| P1 60ms、P2 110ms | 1 | |
| CPU 利用率 81.8% | 1 | |
| (2) | 状态变化序列完整 | 1 |
| (3) | P1 70ms、P2 100ms | 1 |
| CPU 利用率 90% | 1 | |
| 平均周转时间相同,并说明原因 | 1 |
某系统采用段页式存储管理,按字节编址。逻辑地址 32 位:段号(8 位)| 段内页号(12 位)| 页内偏移(12 位);页大小 4KB,物理地址 32 位,页表项占 4B。当前进程的段表长度为 3,段表如下:
| 段号 | 页表长度(该段的页数) | 页表起始物理地址 |
|---|---|---|
| 0 | 5 | 0010 0000H |
| 1 | 2 | 0010 1000H |
| 2 | 8 | 0010 2000H |
段 2 的页表中,第 3 项的内容为:存在位 1,页框号 02F5AH。
(1) 一个进程最多能有多少个段?每段最长多少?(1 分)
(2) 逻辑地址 0200 3A10H 的段号、段内页号和页内偏移各是多少?访问的页表项的物理地址是多少?最后得到的物理地址是多少?(3 分)
(3) 访问逻辑地址 0105 0100H 和 0500 0000H 时,各会发生什么?(2 分)
(4) 设访问一次主存需 100ns,访问 TLB 需 10ns,TLB 命中率为 90%(TLB 命中时直接得到页框号),不考虑缺页。有效访问时间是多少?没有 TLB 时呢?(2 分)
做完再点开:满分答案与评分细则
考点8.6 分段与段页式8.5 TLB 与有效访问时间训练营 ⑩
- 段页式的地址变换走三步:查段表(得到该段页表的位置和长度)→ 查页表(得到页框号)→ 访问数据。没有 TLB 时一共访存 3 次。
- 两处越界检查:段号 ≥ 段表长度;段内页号 ≥ 该段的页表长度。
最多 28 = 256 个段;每段最多 212 页,每段最长 212 × 4KB = 16MB。
0200 3A10H:段号 = 02H,段内页号 = 003H,页内偏移 = A10H。
段号 2 < 段表长度 3,查段表得页表起始地址 0010 2000H、页表长度 8;页号 3 < 8,不越界。页表项地址 = 0010 2000H + 3 × 4 = 0010 200CH。
页表项存在位为 1,页框号 02F5AH,物理地址 = 02F5AH × 4KB + A10H = 02F5 AA10H。
0105 0100H:段号 1,段内页号 050H(= 80),页内偏移 100H。段 1 的页表长度为 2,80 ≥ 2,段内页号越界,产生越界中断(异常)。
8 | 12 | 12 位正好对应十六进制的 2 | 3 | 3 位:0105 0100H 切成 01 | 050 | 100,段内页号是 050H,不要看成 005H。
0500 0000H:段号 5 ≥ 段表长度 3,段号越界,产生越界中断(异常)。
TLB 命中:10 + 100 = 110ns。TLB 未命中:10 + 100(段表)+ 100(页表)+ 100(数据)= 310ns。
EAT = 0.9 × 110 + 0.1 × 310 = 130ns。没有 TLB 时,每次都要访存 3 次,为 300ns。
段页式比纯分页多查一次段表,没有 TLB 时访存次数从 2 次变成 3 次,所以更依赖 TLB。它的好处是既能按段共享和保护,又没有外部碎片。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | 256 个段、每段 16MB | 1 |
| (2) | 段号、页号、偏移 | 1 |
| 页表项地址 0010 200CH | 1 | |
| 物理地址 02F5 AA10H | 1 | |
| (3) | 第一个地址:段内页号越界 | 1 |
| 第二个地址:段号越界 | 1 | |
| (4) | 130ns(含命中、未命中两种情况的时间) | 1 |
| 300ns | 1 |
把两套卷的 8 道题按得分排序。得分率最低的两道,回到它们标出的“涉及题型”,重做那一类的真题和模拟题,再看一遍“举一反三”变式表。每类大题的套路是固定的,丢分通常是因为漏了某一步,而不是不会。
考场作战手册:题型模板、公式、易错与真题索引
前十篇是“学”,第 11 篇是“练大题”,这一篇是考前“速查”,第 13 篇是“满分冲刺”(冷考点、专练、三套全真模拟卷、知识点细目表)。把计组和操作系统的综合题按题型归类,每类给出固定的解题步骤和对应的真题年份;再附公式速查、高频易错点、2009—2026 年每年大题的检索表,以及按年份刷全部 451 道计组/操作系统真题的题目网格(12.8)。建议:学完一篇就来这里找对应题型,把列出的真题按年份做一遍;考前一周只看这一篇。
12.1答题规范:怎么把会做的分拿满
完整版(含“容易丢分的写法 vs 步骤分拿满的写法”对比)见 11.1,这里是考前速查。
- 先写已知条件的“翻译”:比如“页 4KB → 页内偏移 12 位”“按字节编址”。阅卷按步骤给分,结果错了过程还有分(真题评分说明常写“只给结果、未给过程,每问给一半分”)。
- 公式先写符号式再代数:如“占比 = 每秒中断次数 × 每次开销 / 主频 = 125000 × 100 / 500M = 2.5%”。
- 地址题一律展开成二进制,在二进制上画竖线切字段,再换回十六进制。
- 单位写全:B 和 b、K = 210 还是 103、ns/μs/ms。
- PV 题写清三件事:每个信号量的含义和初值、每个进程的完整伪代码(循环写出来)、关键 P/V 旁边加注释。
- 题目条件优先于教材默认:流水线是否转发、Cache 是直写还是回写、“数值大优先级高”还是反过来、页号是十进制还是十六进制……题目说了算。
12.2计组综合题:七种题型
| 题型 | 解题步骤 | 真题 | 讲解 |
|---|---|---|---|
| ① C 程序的数据表示与运算 | 写出每个变量的位宽和有无符号 → 求机器数(十六进制)→ 赋值/运算时判断:扩展(看原类型)、截断、按新类型读 → 判断溢出看 OF(有符号)或 CF(无符号)→ 浮点看 24 位有效位 | 2011-43,2017-43,2020-43,2021-43(3),2025-44 | 2.2 2.4 2.6 训练营 |
| ② 指令格式与机器代码 | 从格式图数位数:操作码位数 → 指令条数;寄存器编号位数 → 寄存器个数;偏移位数 → 范围(补码)→ 编码时逐字段写二进制再拼 → 相对寻址:目标 = 本指令地址 + 指令长度 + 偏移(注意单位)→ 从机器码判断大小端 → 地址差推断编址单位、元素大小 | 2010-43,2013-44,2014-44,2015-44,2017-44,2019-45,2021-43,2023-44,2024-43/44,2026-43 | 3.1 3.2 3.3 训练营 |
| ③ 数据通路与控制信号 | 先写这条指令要做的几件事 → 每件事拆成“源 out + 目的 in” → 单总线每节拍只能传一个数 → 访存 = 地址进 MAR → 读 → MDR → ALU 一端要先进暂存器 → 取指阶段共用:PCout,MARin / MemR,MDRinE,PC+1 / MDRout,IRin | 2009-44,2015-43/44,2022-43,2024-43,2026-44 | 4.2 训练营 |
| ④ 流水线 | 列每条指令“写哪个寄存器、读哪些寄存器” → 找 RAW → 按题目规则画时空图(无转发:后继的 ID 在前驱 WB 之后;load-use 即使转发也停 1 拍;分支停几拍按题目)→ 理想周期数 = k + n − 1 | 2012-44,2014-44(4),2016-19,2019-18,2023-19 | 4.4 训练营 |
| ⑤ Cache 地址、容量、命中率 | 块内位 = log₂块大小;组数 = 行数/路数;Tag = 其余 → 每行位数 = 数据 + Tag + 有效 + 脏(回写) + 替换位 → 命中率:算元素大小、一块几个元素、起始地址是否对齐 → 访问顺序下每块首次缺失 → 检查是否被同组其他块挤掉 | 2010-44,2015-15,2016-45,2018-44,2020-44,2021-16,2023-43,2025-43 | 8.3 训练营 |
| ⑥ 虚拟存储 + TLB + Cache 综合 | 虚拟地址 → 页号/偏移 → TLB(全相联:比整个页号;组相联:页号低位是组号)→ 页表(有效位)→ 物理地址 = 页框号 ∥ 偏移 → 再按 Cache 格式切 → 组内比 Tag 且看有效位 → 缺页要访盘并重新执行指令 | 2011-44,2014-45,2016-45,2018-44,2019-46,2021-44,2023-43,2025-43 | 8.5 8.8 训练营 |
| ⑦ I/O 性能 | 每秒次数 = 速率 / 每次传的数据量 → 每次 CPU 开销(指令数 × CPI + 其他)→ 占比 = 次数 × 开销 / 主频 → 能否用中断:比较“数据到来间隔”和“中断处理时间” → DMA 看块大小;周期挪用每满一个缓冲区请求一次总线 | 2009-43,2012-43,2013-43,2016-44,2018-43,2022-44 | 9.4 |
12.3操作系统综合题:六种题型
| 题型 | 解题步骤 | 真题 | 讲解 |
|---|---|---|---|
| ① PV 同步互斥 | 五步法:找进程和动作 → 找约束(互斥、先后、容量)→ 每个约束一个信号量(写含义和初值)→ 互斥夹紧、同步前 V 后 P、资源用前 P 用后 V → 检查:先 P 资源再 P 互斥;是否死锁;是否满足“最大并发” | 2009-45,2011-45,2013-45,2014-47,2015-45,2017-46,2019-43,2020-45,2021-45,2022-46,2023-45,2024-46,2025-45 | 7.4 训练营 |
| ② 调度计算 | 标到达时刻 → 按算法(是否抢占、优先数大小方向)画甘特图 → 列表:完成、周转 = 完成 − 到达、带权 = 周转/运行、等待 = 周转 − 运行 → 求平均;有调度开销时每次上 CPU 前加上 | 2012-29,2016-24,2016-46,2018-24,2019-27,2022-25,2023-29,2024-30,2026-45 | 6.7 训练营 |
| ③ 分页地址变换与页表 | 页大小 → 偏移位数 → 页号(多级:每级位数 = log₂(页大小/表项大小))→ 页表项地址 = 基址 + 索引 × 表项大小 → 有效位判缺页 → 置换算法选页框 → 物理地址 = 页框号 ∥ 偏移 → 时间 = TLB + 页表访存 + 数据访存 (+ 缺页,且要重新执行) | 2009-46,2010-46,2012-45,2013-46,2015-46,2017-45,2018-45,2020-46,2024-45 | 8.5 8.7 训练营 |
| ④ 文件系统 | N = 块大小 / 地址项大小 → 最大文件 = (直接 + N + N² + N³) × 块大小 → 某偏移在第几个逻辑块 → 落在直接/一级/二级 → 读盘次数 → 目录项 = 文件名 + inode 号 → 硬链接共享 inode → FAT:表项存下一块号 | 2011-46,2012-46,2014-46,2016-47,2018-46,2022-45,2026-46 | 10.4 10.6 训练营 |
| ⑤ 磁盘 | 容量 = 柱面 × 磁头 × 扇区 × 扇区大小 → 访问时间 = 寻道 + 半圈 + 传输 → 簇号 → 柱面/磁头/扇区(除法取整、取余)→ 调度算法算顺序和移动距离 → 位示图大小与定位 | 2010-45,2019-44,2022-44 | 10.1 10.2 10.7 训练营 |
| ⑥ 过程分析(I/O、引导、地址空间) | 按时间顺序写出每一步:谁执行(用户程序/内核/驱动/中断处理程序/硬件)、CPU 在什么态、进程在什么状态、数据在哪里 | 2017-45(3),2021-46,2023-46,2025-46 | 9.6 10.3 3.6 |
12.4公式速查
| 主题 | 公式 |
|---|---|
| 单位 | 210=1K,220=1M,230=1G;容量用 2 的幂,频率/速率用 10 的幂;1B = 8b;时钟周期 = 1/主频 |
| 性能 | CPU 时间 = 指令数 × CPI / 主频;平均 CPI = Σ 比例 × CPI;MIPS = 主频 / (CPI × 106);Amdahl:加速比 = 1 / [(1 − f) + f/s],f 按时间算 |
| 补码 | n 位范围 −2n−1~2n−1−1;[−x]补 = 2n − x = 取反加 1;最高位权重 −2n−1;定点小数补码 −1 ~ 1 − 2−(n−1),原码/反码 ±(1 − 2−(n−1)) |
| 溢出判断 | 同号相加结果变号;Cs ⊕ C1 = 1;双符号位 01 正溢出、10 负溢出;浮点数只看阶码是否上溢 |
| IEEE 754 | float:1 + 8(偏置 127)+ 23;值 = (−1)S×1.M×2E−127;E=0 非规格化 0.M×2−126;E=255 ∞/NaN;最大 2128−2104;最小规格化 2−126;最小正非规格化 2−149(机器数 0000 0001H);默认就近舍入,正好一半时取偶;相邻两个 float 的间距 = 2指数 − 23(224 附近为 2,大数吃小数,2.6) |
| 标志位 | 无符号 A>B:CF=0 且 ZF=0;有符号 A<B:SF⊕OF=1;加法 OF = 两同号加出异号;减法 CF = 借位 |
| 流水线 | T = (k + n − 1)Δt;Δt = 最长段 + 段寄存器延迟;吞吐率 = n/T;加速比 = nkΔt/T;效率 = n/(k + n − 1);有阻塞时 CPI = 1 + Σ(事件频率 × 阻塞拍数) |
| 微程序 | 字段直接编码每字段 ⌈log2(微命令数 + 1)⌉ 位;断定法下地址 ⌈log2微指令总数⌉ 位;微指令总数 = Σ 各指令微程序 + 公共取指(间址、中断)微程序;n 条机器指令至少 n + 1 段微程序 |
| 扩展操作码 | 下一级可用编码 = 上一级剩余编码 × 2让出的位数 |
| 主存 | 芯片数 = 总容量/芯片容量;带宽 = 每次位数 / 存取周期;交叉:体号 = 地址 mod m,最大带宽 = m × 字 / 存取周期,连续读 n 字 = T + (n − 1)r(顺序方式 nT);DRAM 地址引脚减半;异步刷新间隔 = 刷新周期 / 行数;集中刷新死区 = 行数 × 存取周期,死时间率 = 死区 / 刷新周期 |
| Cache | 组数 = 行数/路数;Tag = 地址位 − 组号位 − 块内位;每行 = 数据 + Tag + V [+ D] [+ 替换位];平均访问 = tc + (1−H)tm(同时访问时 H·tc + (1−H)tm);效率 e = tc / 平均访问时间;两级 Cache:tL1 + mL1 × (tL2 + mL2 × tm)(m 为各级缺失率) |
| 分页 | 偏移 = log₂页大小;页表大小 = 页数 × 表项;表项地址 = 基址 + 页号 × 表项大小;每级索引 = log₂(页大小/表项);EAT = tTLB + tm + (1−h)tm [+ p × t缺页];TLB 与页表同时查:h(tTLB + tm) + (1−h)·2tm;反置页表大小 = 物理页框数 × 表项大小;只看缺页:EAT = (1 − p)·tm + p·tf(p 为缺页率,tf 为缺页处理时间,8.9) |
| 总线 / I/O | 带宽 = 宽度 × 每秒传送次数;CPU 占比 = 每秒次数 × 每次周期数 / 主频;周期挪用占用主存比例 = 每秒请求次数 × 存储周期;仲裁控制线:链式 3、计数器 2 + ⌈log2n⌉、独立请求 2n + 1;全互锁一次传送 ≥ 4 个信号传播时间;最大并发 I/O 速率 = Σ 各控制器同时能传的速率,再与总线带宽取小 |
| 磁盘 | 访问 = 寻道 + 60/(2×转速) + 传输;容量 = 柱面 × 磁头 × 扇区 × 扇区大小;数据传输率 = 每秒转数 × 每道容量;平均寻道长度 = 总移动距离 / 请求数;RAID 可用容量:RAID 0 nC、RAID 1 nC/2、RAID 5 (n − 1)C,RAID 5 小写 = 2 读 2 写;SSD 写放大 = Flash 实际写入量 / 主机写入量 |
| 文件 | N = 块/地址项;最大文件 = (d + N + N² + N³) × 块;位示图位数 = 块总数;隐式链接读第 i 块(从 0 起)要 i + 1 次;目录顺序查找平均读 (目录块数 + 1)/2 块;链接方案每个索引块 N − 1 个块号,两层索引最多 N² 块;索引顺序文件分 √N 组平均约查 √N 次 |
| 缓冲 | 单缓冲每块 max(C, T) + M;双缓冲每块 max(T, C + M) |
| 调度 | 周转 = 完成 − 到达;带权 = 周转/运行;响应比 = (等待 + 运行)/运行;实时:U = Σ Ci/Ti,EDF 可调度 ⇔ U ≤ 1,RMS 充分条件 U ≤ n(21/n − 1) |
| 进程创建 | 循环 n 次、每次都 fork:最后共 2n 个进程(含原进程),fork 执行 2n − 1 次;fork 在父进程返回子进程 PID,在子进程返回 0(6.3) |
| 页面置换 | 置换次数 = 缺页次数 − 填空页框的次数;工作集 W(t, Δ) = 最近 Δ 次访问中出现的不同页面;改进型 CLOCK 淘汰次序 (0,0) → (0,1) → (1,0) → (1,1),最多扫四轮 |
| 死锁 | 不死锁最少资源 = Σ(需求i − 1) + 1;Need = Max − Allocation |
| 信号量 | value < 0 时 |value| = 等待的进程数;m 个资源、n 个进程竞争(n > m):取值范围 m − n ~ m |
12.5高频易错清单(91 条)
| # | 易错说法 / 陷阱 | 正确理解 |
|---|---|---|
| 1 | MAR 位数看实际装了多少内存 | 看地址空间大小(2011-15) |
| 2 | short → unsigned int 零扩展 | 看原类型:原来有符号就符号扩展(2025-12) |
| 3 | 编码 float 时把隐藏的 1 也存进去 | 1.M 的 1 不存;阶码 = 指数 + 127 |
| 4 | 判断无符号溢出看 OF | 无符号看 CF,有符号看 OF |
| 5 | 相对寻址 EA = 本指令地址 + 偏移 | = 下一条指令地址 + 偏移(×单位) |
| 6 | “CPU 速度提高 50%”,时间减半 | 时间变为 1/1.5(2012-12) |
| 7 | trap(陷入)指令是特权指令 | 在用户态执行,是非特权指令 |
| 8 | 中断隐指令保存通用寄存器 | 硬件只保存 PC、PSW;通用寄存器由服务程序保存 |
| 9 | 屏蔽字决定响应优先级 | 屏蔽字决定处理优先级(2024-21) |
| 10 | 缺页处理后执行下一条指令 | 重新执行发生缺页的那条(2019-14) |
| 11 | 缺页属于外部中断 | 属于异常(故障)(2016-22) |
| 12 | 时间片用完 → 阻塞态 | → 就绪态(2017-27) |
| 13 | 进程被唤醒就立即运行 | 唤醒是阻塞 → 就绪 |
| 14 | 线程共享同一进程的栈 | 栈、寄存器私有;代码、数据、堆、文件共享 |
| 15 | 生产者先 P(mutex) 再 P(empty) | 会死锁;先 P 资源信号量再 P 互斥信号量 |
| 16 | 不安全状态 = 死锁 | 不安全只是可能死锁;安全一定不死锁 |
| 17 | Cache 组号也要存在每一行 | 组号不存,只存 Tag |
| 18 | 直写法需要脏位 | 回写法才需要脏位(2020-44) |
| 19 | Cache 命中只看 Tag 相等 | 还要有效位 = 1(2018-44) |
| 20 | 虚拟内存大小由内存和磁盘决定 | 由地址位数决定(2023-28) |
| 21 | 多级页表加快地址变换 | 减少页表连续内存占用,变换更慢(2014-32) |
| 22 | 页表里的页号默认十六进制 | 看题:2022-15 页号是十进制 |
| 23 | 缺页次数 = 置换次数 | 装入空页框的缺页不算置换(2019-29) |
| 24 | FIFO、LRU 都有 Belady 异常 | 只有 FIFO(2014-30) |
| 25 | DMA 传送过程中 CPU 不能访存 | 周期挪用只偷个别周期(2020-22) |
| 26 | 中断处理程序运行时,等输入的进程是运行态 | 仍是阻塞态,被打断的是别的进程(2023-46) |
| 27 | read 系统调用的参数是文件名 | 是文件描述符 fd,文件名在 open 时用(2012-28) |
| 28 | 删除文件要删除所在目录 | 只删目录项、释放 inode 和数据块(2013-23) |
| 29 | 访问权限存在目录项里 | 存在 inode/FCB 里(2025-29) |
| 30 | C-SCAN 的返回不算移动距离 | 408 真题口径计入(2010-45、2024-32),以题目为准 |
| 31 | MIPS 高的机器一定跑得快 | MIPS 只在 ISA 相同时可比;比性能要比同一程序的执行时间(2017-12) |
| 32 | Amdahl 定律里的比例 f 按指令条数算 | 按时间占比算 |
| 33 | 解释程序也生成目标程序 | 编译、汇编生成目标程序,解释不生成 |
| 34 | 微架构是 ISA 的物理实现 | 是逻辑实现(2026-12) |
| 35 | −128(8 位)、−1(定点小数)有原码 | 只有补码能表示(整数的移码也能表示 −128);原码、反码的范围正负对称,没有它们 |
| 36 | 由 [x]补 求 [−x]补:符号位不动,数值位取反加 1 | 要连同符号位一起取反加 1(符号位不动的是“求补码/原码”) |
| 37 | 补码尾数 1.100…0(−1/2)是规格化数 | 符号位与最高数值位相同,不是规格化;1.000…0(−1)是 |
| 38 | 浮点加法尾数溢出就是结果溢出 | 右规即可;结果是否溢出只看阶码(2009-13、2015-14) |
| 39 | -1 < 0U 为真 | 为假:有符号与无符号混用,−1 被当成 4294967295 |
| 40 | −7 >> 1 与 −7 / 2 结果相同 | 算术右移向下取整得 −4,C 除法向 0 取整得 −3 |
| 41 | 自增寻址 (R)+ 总是把 R 加 1 | 加一个操作数的长度,看编址单位(2010-43 按字编址,加 1) |
| 42 | lea eax, [ebx+8] 会访存 | 只计算地址、不访存;mov eax, [ebx+8] 才访存 |
| 43 | 单周期 CPU 可以用单总线数据通路 | 不能:一个周期要同时传多个数据(2016-20) |
| 44 | 单总线一个节拍里可以有两个 Xout | 只能有一个 Xout;Xin 可以有多个 |
| 45 | 有转发就不会有数据冒险阻塞 | load-use 仍要阻塞 1 拍(2024-19) |
| 46 | 中断、DMA、异常都在指令结束时响应 | 中断:指令结束后;DMA:存储周期结束后;异常:指令执行中发现就处理 |
| 47 | 中断向量 = 中断向量地址 | 向量是服务程序入口地址;向量地址是存放入口地址的单元的地址 |
| 48 | “就绪 → 阻塞”“阻塞 → 运行”可能发生 | 都不可能;被唤醒只能先进就绪 |
| 49 | SJF 的平均周转时间总是最短 | 作业同时到达时 SJF 最短;不同时到达时抢占式 SRTN 更短 |
| 50 | 资源分配图有环就一定死锁 | 只在每类资源都只有 1 个时成立;有多个实例要化简判断 |
| 51 | 管程中 x.signal() 没人等也会被记住 | 没人等就什么也不发生;信号量的 V 才会记住(value + 1) |
| 52 | 分页的逻辑地址是二维的 | 分页一维,分段、段页式二维 |
| 53 | 设备分到了就能启动 I/O | 设备、控制器、通道三者都分配成功才行 |
| 54 | 隐式链接分配也能随机访问 | 只能顺序访问,读第 i 块要 i + 1 次;FAT 在内存时才能快速随机访问 |
| 55 | 算术左移时移出的是 1 就溢出 | 看符号位变没变:移位前最高两位不同才溢出(FFFFH 左移得 FFFEH,不溢出) |
| 56 | 相对转移的目标 = 本指令地址 + 偏移 | 是下一条指令的地址 + 偏移(偏移为补码,机器码按小端存放) |
| 57 | 单总线下 R1 送 MAR 和送 ALU 要分两拍 | 一个 out 可以同时有多个 in:R1out、MARin、Inc、Zin 可以在同一拍 |
| 58 | 2 路组相联就不会有冲突缺失 | 同时要用的块数超过路数(如三个数组同组)时,LRU 下会反复互相替换,命中率可降到 0 |
| 59 | 组相联 TLB 的标记 = 整个虚页号 | 虚页号低位是组号,标记只是去掉组号后的高位 |
| 60 | “组号 + 块内地址”超过页内偏移,也能用虚拟地址选 Cache 组 | 不能:组号的高位来自页框号,要等地址转换完成 |
| 61 | 中断方式下 CPU 占比 < 100% 就不会丢数据 | 要比较每次中断的开销与两次数据到达的间隔 |
| 62 | 进程拿着 mutex 去 P 一个资源信号量没问题 | 可能死锁:释放资源的进程也要先拿 mutex(生产者—消费者、单车道桥) |
| 63 | RR 的平均周转时间一定比 FCFS 短 | 不一定;RR 的优势是响应快、公平,时间片太小还会增加切换开销 |
| 64 | 改进型 CLOCK 第一轮扫描就把访问位清 0 | 第一轮只找 (0, 0)、不改访问位;第二轮找 (0, 1) 时才把经过的页的访问位清 0 |
| 65 | 1KB 页也能直接取十六进制最后 3 位当偏移 | 偏移 10 位不是 4 的倍数,必须展开成二进制再切 |
| 66 | 打开文件时就把数据块读进来 | open 只沿路径读目录和 inode;读数据块是 read 的事 |
| 67 | 就近舍入遇到正好一半时直接进位 | 一半时取偶:16777219 存成 16777220(尾数末位为 0 的那个) |
| 68 | 非规格化数(阶码全 0)的指数是 0 − 127 = −127 | 按 1 − 127 = −126 算,且没有隐藏的 1:值 = 0.M × 2−126 |
| 69 | 改进型 CLOCK 优先淘汰没修改过的页,(1, 0) 比 (0, 1) 先淘汰 | 先看访问位再看修改位:(0, 1) 先于 (1, 0)(2016-26) |
| 70 | 页面缓冲算法 PBA 能降低缺页率 | 只降低缺页的代价(被淘汰的页能直接取回、少读盘),缺页率不变(2022-30) |
| 71 | LFU 是 LRU 的近似 | 不是:LFU 只看次数,过去常用而现在不用的页会一直占着页框 |
| 72 | fork 时就把父进程的页全部复制给子进程 | 写时复制:先共享并设为只读,谁写哪页才复制哪页 |
| 73 | 内存映射文件把文件映射到物理地址空间 | 映射到进程的虚拟地址空间,物理页框由缺页按需调入(2025-30) |
| 74 | 反置页表每个进程一张 | 全系统一张,每个物理页框一项,大小只与物理内存有关 |
| 75 | 独立请求方式的仲裁控制线是 2n 根 | 王道口径 2n + 1(还有一根 BS);链式 3 根,计数器 2 + ⌈log2n⌉ 根 |
| 76 | 全互锁一次握手只要一来一回两个信号 | 请求↑、应答↑、请求↓、应答↓ 四步,每步都要等对方 |
| 77 | 非阻塞 I/O 就是异步 I/O | 非阻塞立即返回后要进程自己反复查;异步由系统做完后通知进程 |
| 78 | 循环缓冲中 in 追上 out 表示缓冲区全空 | 表示全满,输入进程阻塞;out 追上 in 才是全空 |
| 79 | 访问控制矩阵按行存就是访问控制表 ACL | 按列(每个文件一张)存才是 ACL;按行(每个用户一张)存是权限表 |
| 80 | 系统打开文件表的表项里放着各进程的读写指针 | 教材两级模型:读写指针在进程打开文件表,系统表一个文件只一项(2017-31、2020-23) |
| 81 | RAID 5 坏两块盘也能恢复;条带化能提高可靠性 | RAID 5 只容忍 1 块;条带化只提速,可靠性靠镜像和校验(2013-20) |
| 82 | SSD 的磨损均衡由文件系统负责 | 由 SSD 控制器的 FTL 完成;文件系统只确定盘块大小(2025-32) |
| 83 | 扇区和簇(块)是一回事 | 扇区是磁盘读写的最小单位;簇(块)是文件系统分配单位,由若干扇区组成 |
| 84 | 两个线程访问不同的变量,就不会互相作废对方的 Cache 行 | 若落在同一个 Cache 块里,会因“伪共享”反复作废(11.6 预测题) |
| 85 | Cache 一致性协议保证了 x++ 不会丢失更新 | 一致性只保证看到的值一致,读改写仍可能被打断,要靠互斥(第 7 篇) |
| 86 | 按字节编址,MDR 就是 8 位 | MDR 的位数 = 一次访存读写的位数,可以是 16、32、64 位;编址单位只决定 MAR 的位数(1.2 四种宽度) |
| 87 | 一段区域的末地址 = 起始地址 + 区域大小 | 还要减 1:8KB 区域从 0800H 开始,末地址 = 0800H + 2000H − 1 = 27FFH(0.4) |
| 88 | 段页式地址(段号 8 位 | 段内页号 12 位 | 偏移 12 位)0105 0100H 的段内页号是 005H | 8 | 12 | 12 位正好是十六进制的 2 | 3 | 3 位:01 | 050 | 100,段内页号 050H = 80(11.15 卷二第 46 题) |
| 89 | 整数除法只有“除数为 0”一种异常 | 还有商溢出:−231 ÷ (−1) 的商 231 超出 int 范围(2025-44) |
| 90 | 把 P(资源) 写进临界区,就一定死锁 | 要看释放资源的一方是否也要先拿同一个 mutex:单车道桥下桥“先 V(cap) 再 P(mE)”时不死锁,只是并发度下降(11.9 程序穷举);稳妥的写法仍是先 P 资源、再 P 互斥 |
| 91 | 2013-44 数据通路里引出 OP、C、Z、N、OFFSET 的部件 ① 是 PC | 是指令寄存器 IR(指令的各字段都从它引出);② 是左移一位的移位器(得 2 × OFFSET),③ 是第二个加法器(图中第一个加法器只算 (PC) + 2) |
12.62009—2026 年计组、操作系统综合题检索表
| 年份 | 计组综合题 | 操作系统综合题 |
|---|---|---|
| 2009 | 43 中断/DMA 的 CPU 占比 9.4;44 数据通路 ADD (R1),R0 4.2 | 45 奇偶数缓冲区 PV 7.4;46 TLB + 缺页访存时间 8.5 |
| 2010 | 43 指令格式与编码 3.3;44 Cache 与数组按行/列 8.3 | 45 位示图 + C-SCAN 10.2;46 FIFO/CLOCK 求物理地址 8.7 |
| 2011 | 43 8 位机有符号/无符号运算 2.2;44 页表 + Cache + TLB 8.8 | 45 银行取号 PV 7.4;46 一次写入文件的分配方式 10.6 |
| 2012 | 43 MIPS、Cache 缺失、DMA、交叉存储 9.4;44 流水线 4.4 | 45 驻留集扫描回收 8.7;46 索引表区与最大文件 10.6 |
| 2013 | 43 突发传送与执行时间 9.2;44 条件转移指令与数据通路 3.2 | 45 博物馆 PV 7.4;46 一级/二级页表 8.5 |
| 2014 | 44 RISC 循环代码与流水线 3.3 4.4;45 指令 Cache、缺页、TLB 次数 8.7 | 46 连续/链接分配访盘次数 10.6;47 连续取 10 件的生产者消费者 7.4 |
| 2015 | 43 单总线数据通路 4.2;44 控制信号与指令编码 4.2 | 45 信箱辩论 PV 7.4;46 二级页表 8.5 |
| 2016 | 44 中断方式读字符 9.4;45 TLB/Cache 字段位数 8.3 | 46 动态优先数防饥饿 6.7;47 FAT 10.6 |
| 2017 | 43 int/float 求和函数 2.6;44 机器代码、CF、shl 3.3 | 45 页目录索引 + scanf 状态 9.6;46 复数线程最大并发 7.4 |
| 2018 | 43 查询/中断/DMA 比较 9.4;44 TLB + Cache 硬件图 8.8 | 45 二级页表、PDBR、改进 CLOCK 8.5;46 inode 最大文件与文件数 10.6 |
| 2019 | 45 递归 n!、call、小端、溢出 3.3;46 分页与指令 Cache 3.3 | 43 哲学家与碗 7.4;44 磁盘容量、SSTF、簇号换 CHS 10.2 |
| 2020 | 43 乘法实现与溢出 2.5;44 8 路组相联 Cache 8.3 | 45 前驱关系 PV 7.4;46 二级页表求地址 8.5 |
| 2021 | 43 R/I/J 指令格式 3.1;44 TLB 组相联 LRU 8.3 | 45 开关中断实现信号量 7.2;46 引导与格式化顺序 10.3 |
| 2022 | 43 数据通路、SF/OF 表达式 4.2;44 磁盘地址、访问时间、DMA 10.1 | 45 目录、inode、硬链接 10.5;46 两线程前驱 PV 7.4 |
| 2023 | 43 数组的分页与 Cache 8.3;44 机器代码、寻址、缺页 3.2 | 45 swap 实现互斥 7.2;46 键盘输入全过程 9.6 |
| 2024 | 43 RISC-V 式数据通路与指令编码 4.2;44 sum+=a[i]、小端、分页 3.3 | 45 4MB 大页的页表项地址 8.5;46 缓冲区 B 的同步互斥 7.4 |
| 2025 | 43 数组的 Cache 与缺页 8.3;44 补码除法器与除法异常 5.6 | 45 植树 PV 7.4;46 进程地址空间与 scanf 3.6 |
| 2026 | 43 16 位机指令格式与编码 3.1;44 数据通路与控制信号 4.2 | 45 优先级 + 时间片调度 6.7;46 inode、位示图、删除目录 10.8 |
题号说明:2019 年操作系统在前(43、44)、计组在后(45、46);2014、2016 年计组是 44、45,操作系统是 46、47(这两年计网题提前了:2014 年计网是第 43 题,2016 年计网是第 41 题);其余年份一般是计组 43、44,操作系统 45、46。2026 年真题目前流传多个整理版本,题面细节以你手中的原卷为准。
12.7接下来怎么复习
- 第一遍(学):按第 0~10 篇顺序读,每篇里的真题卡先自己做再看解析,用小工具验证计算。
- 第二遍(练):每篇末尾的“本篇真题闯关”把这一篇涉及的所有真题按小节排好了,逐题做;按第 11 篇“大题满分训练营”逐类把综合题完整写一遍过程,写完对照每题的“答题卡这样写”。
- 第三遍(考):用 12.8 的年份网格,按年份把 2023—2026 年的计组+操作系统部分限时做完(选择题约 21 道、综合题 4 道),做错的题会标红,最后用“把所有错题连起来做”回炉。
- 第四遍(冲刺):第 13 篇先看 13.0 的热力图和 2027 预测,补 13.1 冷考点,做 13.2 ~ 13.4 的辨析、计算和融合综合题;再用 13.5 ~ 13.7 三套 80 分全真卷各限时 100 分钟做一遍,交卷看分科得分和错题。
- 考前一周:只看 12.4 公式、12.5 易错清单、各篇末尾的自测,以及 13.8 知识点细目表里还没勾上的条目。
12.8按年份刷真题:2009—2026 年计组与操作系统全部 451 题
下面每一行是一年,每个按钮是一道题(选择题标题号,红框粗体是综合题)。点开后在弹窗里作答:选择题点选项立即判对错并给出讲解,综合题先自己在纸上写,再展开分步解答并自评。弹窗里的“下一题”按这一年的题号顺序往下走,相当于做这一年卷子的计组+操作系统部分。
做题记录只存在你这台电脑的浏览器里(换浏览器或清除网站数据会丢失)。讲解是本讲义自己写的,答案以本机题库为准;2026 年真题的流传版本个别题面有出入,已在讲解中注明。
计组和操作系统其实在讲同一台机器:硬件提供机制(地址变换、中断、DMA、原子指令),操作系统利用机制制定策略(分页、缺页处理、调度、PV、文件系统)。做综合题时,先问自己“这一步是硬件做的还是操作系统做的”,很多题就迎刃而解。
满分冲刺:命题规律、新考点、辨析、计算、融合与全真模拟
前十篇解决“会不会”,第 11、12 篇解决“大题怎么写、考前查什么”,这一篇解决“稳不稳”。计组 + 操作系统从 66 分到 80 分,差的不是新知识,而是五件事:字眼和口径不出错、Ⅰ/Ⅱ/Ⅲ 组合题每一条都判对、考纲有而真题少的点也练过、计算题的单位和步骤一次算对、综合题把“硬件做什么、操作系统做什么”串成一条线。本篇先用 18 年真题的统计说清命题规律,再给新考点专项、概念辨析、计算题专练、融合综合题专练,最后是三套 80 分的全真模拟卷和一张可以逐条打勾的全书知识点细目表。
13.0从 66 分到 80 分:满分差在哪里
按本书的分数目标,学完前十二篇大约能拿到计组 37、操作系统 29。剩下的 14 分,按我改卷和带学生的经验,几乎全丢在下面五个地方:
| 丢分的地方 | 典型表现 | 本书对策 |
|---|---|---|
| ① 字眼与口径 | 按字编址还是按字节编址;K 是 210 还是 103(容量用前者、速率用后者);“至少 / 最多 / 一定 / 可能”看漏;“不可能的是”“错误的是”选反;教材口径不同(汤小丹的 SCAN = LOOK、蒋本珊的组相联约定、动态流水线的两种含义) | 各节“易错”框;12.5 易错清单;本篇 13.2、13.3 |
| ② 组合题漏判一条 | Ⅰ/Ⅱ/Ⅲ/Ⅳ 四条判对三条照样 0 分。操作系统的组合题在 2015—2020 年占了 28%,近六年仍有 20%(下图统计) | 本篇 13.2 概念辨析专练 |
| ③ 冷点、新点没准备 | 2025 年考纲在进程通信里加了“信号”;多处理机调度、闲逛进程、总线仲裁、双端口 RAM、非规格化数从没出过题;硬布线控制器、多级 Cache、VFS、挂载、SPOOLing 都只考过一次 | 本篇 13.1 专项 |
| ④ 计算题算错一步 | 页表项数、Cache 标记位数、EAT、周转时间、磁盘时间……思路都对,错在单位换算、取整方向、漏算一次访存 | 本篇 13.3 计算题满分专练 |
| ⑤ 综合题的细节分 | 只写结果不写过程;PV 题信号量含义和初值没写、P 的顺序颠倒导致死锁;过程分析题说不清“这一步是硬件做的还是操作系统做的、CPU 在什么态” | 第 11 篇各类评分细则;本篇 13.4 融合专练、三套模拟卷的评分表 |
18 年的选择题落在哪里
从这两张图能读出五条规律,复习时间要据此分配:
- 计组的基本盘是“数据表示”:18 年 44 道,几乎年年 2 ~ 3 道(2018 年多达 6 道),补码、IEEE 754、C 语言类型转换、大小端是必拿分。其次是 CPU(22)、总线(20)、主存与中断(各 18)。
- 计组的 Cache、虚存、磁盘在选择题里“少”,在大题里“大”:Cache 选择题只有 10 道,但 43 ~ 46 题里 Cache 主考 4 次、兼考 5 次;虚存 + TLB + Cache 综合题主考 6 次。
- 操作系统的基本盘是“内存 + 概述与运行环境 + 文件”:合计 94 道,超过一半。内存管理几乎年年 2 道以上;概述与运行环境(用户态 / 内核态、中断、系统调用)2022、2023 两年各 4 道,是近年的明显升温点。
- 同步互斥、死锁的选择题少(13、11 道),但 PV 大题 18 年考了 13 次——这部分的分数主要在 45 / 46 题。
- 组合题:计组 2009—2014 / 2015—2020 / 2021—2026 三段分别占 10%、15%、11%;操作系统分别占 15%、28%、20%。操作系统的组合题明显更多,13.2 的辨析专练要重点做操作系统部分。
统计口径:每道选择题按本书讲解标注的主归属小节归章;组合题指题干中出现 Ⅰ/Ⅱ/Ⅲ 等多个说法、选项是说法组合的题。统计程序 verify/08/p13/stats.py,“考纲点在题面出现几次”由 cold.py 统计。
43 ~ 46 题的轮换规律
把 72 道综合题按第 11 篇的 13 类题型归类(每题按主考点归一类,“兼考”指该题某一问用到了这一类):
| 题型 | 主考的年份 | 兼考 | 最近 | 2027 的看法 |
|---|---|---|---|---|
| ② 指令格式与机器级代码 | 2010、2013、2014、2017、2019、2021、2023、2024、2026 | 2015 | 2026 | 出现最多,几乎隔年一次;汇编读代码、偏移量、栈帧、访存次数必须熟 |
| ③ 数据通路与控制信号 | 2009、2015、2022、2026 | 2024 | 2026 | 近五年三次(偶数年),2027 概率中等 |
| ⑤ Cache | 2010、2020、2023、2025 | 2011、2014、2016、2018、2019 | 2025 | 2023、2025 奇数年连考,且常和虚存一起出 |
| ⑥ 虚存 + TLB + Cache | 2011、2014、2016、2018、2019、2021 | 2023、2025 | 2021 | 主考已 5 年没出现,一次访存全过程(13.4)是高概率题 |
| ⑦ I/O 方式与总线 | 2009、2012、2013、2016、2018、2022 | — | 2022 | 已 4 年没考:中断 / DMA 的 CPU 占用率、总线带宽要练熟 |
| ① 数据表示与运算 | 2011、2017、2020、2025 | — | 2025 | 刚考过,但选择题年年考,基础必须牢 |
| ④ 指令流水线 | 2012 | 2014 | 2012 | 14 年没有作为主考点:时空图、阻塞拍数、CPI 可能以一问的形式出现 |
| ⑧ PV 同步互斥 | 2009、2011、2013、2014、2015、2017、2019—2025(每年) | — | 2025 | 2019—2025 连续 7 年,2026 断档——2027 回归的概率最高 |
| ⑩ 分页、多级页表与置换 | 2009、2010、2012、2013、2015、2017、2018、2020、2024 | — | 2024 | 平均两年一次,2027 是常规窗口 |
| ⑪ 文件系统 | 2011、2012、2014、2016、2018、2022、2026 | — | 2026 | 刚考过(2026 考空闲空间 + 全局结构) |
| ⑬ 过程分析题 | 2021、2023、2025 | — | 2025 | 奇数年隔年出现:缺页、系统调用、I/O 全过程里“谁在做、CPU 什么态、进程什么状态” |
| ⑫ 磁盘 | 2010、2019 | 2021、2022 | 2019 | 常作为文件系统或 I/O 题的一问 |
| ⑨ CPU 调度 | 2016、2026 | — | 2026 | 刚考过 |
年份由题库程序统计(verify/08/p13/stats.py)。2014、2019 两年的计组 / 操作系统大题题号与常规不同(如 2019 年操作系统在 43、44),统计按内容归类,不按题号。
2027 年值得重点准备的点
| 考点 | 真题情况 | 为什么值得准备 | 本书位置 |
|---|---|---|---|
| PV 同步大题 | 2019—2025 每年都有,2026 没有 | 断档一年后回归的可能性最大;多类资源 + 容量、阶段同步(屏障)、读者写者变体是近年的方向 | 7.4、11.9、13.4、三套卷 |
| 进程通信:信号 | 从未考过 | 2025 年考纲新写进“进程间通信”,新增条目常在一两年内出选择题 | 6.3、13.1 |
| 虚存性能的影响因素 | 2020-28、2022-30、2026-29 零散考过 | 考纲独立条目,三年两次出题,热度在上升 | 8.9、13.1 |
| 一次访存全过程(虚存 + TLB + Cache) | 主考 6 次,最近 2021 | 计组最“融合”的综合题,5 年没作为主考点 | 8.8、13.4 |
| I/O 方式的 CPU 占用率 | 大题最近 2022 | 4 年没考,计算模板固定,容易拿满分 | 9.4、13.3、13.4 |
| 指令流水线 | 大题只在 2012 主考 | 选择题每年 1 道左右,大题一问的形式随时可能出现 | 4.4、13.3 |
| 多处理器、硬件多线程、SMP、动态流水线 | 只有 2017-17、2022-22 两题 | 2022 年进入考纲,命题空间大 | 4.5、4.4、13.1 |
| 多处理机调度、闲逛进程、线程调度 | 从未单独出题 | 2022 年起考纲写明“调度的实现”这几项 | 6.6、13.1 |
| SSD、RAID、Flash | SSD 两题(2023、2025) | 外存的新内容,读写特性与磨损均衡是天然的辨析题 | 10.1、13.1 |
| 文件系统的全局结构、VFS、挂载 | 2021-46、2025-28、2026-46 | 2026 年大题刚用到全局结构,选择题会跟进 | 10.8、13.1 |
- 一轮学完以后做 13.1、13.2:做错的题点链接回到对应小节,把那一节的“易错”框重读一遍。
- 二轮复习做 13.3 计算专练(每类先看“固定步骤”,再限时做变式)和 13.4 融合专练(先画全过程图,再做题)。
- 怎么作答:本篇和全书各节的练习题都可以直接点选项,点完立刻判对错并展开解析;三套模拟卷先只记下你的选择,点“交卷”后统一判分、列出错题,综合题按评分细则自评打分,计组、操作系统分别计分(记录保存在本机浏览器)。
- 考前四周做三套模拟卷,每套限时 100 分钟(21 道选择题约 35 分钟、四道综合题约 65 分钟);也可以只做计组(55 分钟)或只做操作系统(45 分钟)。选择题错 1 道以内、综合题自评 90% 以上,才算达到满分水平。
- 考前一周用 13.8 细目表逐条自查:每一条都能说出结论和理由,就勾上;勾不上的回原文。
13.1考纲有、真题少:新考点与冷考点专项
下面 11 组考点,要么是近几年新写进考纲、还没出过题,要么是十几年只考过一两次。每组先说“为什么可能考、会怎么考”,再给 2 ~ 3 道按 408 风格自编的选择题。题目全部是原创数据,答案和每个干扰项都由程序计算或模拟核对(verify/08/p13/new_topics.py),解析里写明每个错误选项“错在哪一步”。建议先盖住解析,每题限时 2 分钟;直接点选项就能判对错。
① 进程通信:信号
为什么可能考:2025 年起考纲把“信号”和共享内存、消息传递、管道并列写进进程间通信,至今没出过题。会怎么考:Ⅰ/Ⅱ/Ⅲ 判断信号的发送者、处理时机、处理方式;Ctrl+C 一类场景的事件排序;与信号量、中断辨析。复习:6.3 信号。
Ⅰ. 信号可以由内核发给进程,也可以由一个进程通过系统调用发给另一个进程
Ⅱ. 进程收到信号后,内核立即打断该进程正在执行的指令,转去执行信号处理函数
Ⅲ. 同一种信号在被处理之前多次到达,进程可能只处理一次
Ⅳ. 用户自定义的信号处理函数在内核态执行
- A. 仅Ⅰ、Ⅲ
- B. 仅Ⅰ、Ⅱ、Ⅲ
- C. 仅Ⅲ、Ⅳ
- D. 仅Ⅰ、Ⅲ、Ⅳ
展开解析
考点6.3 信号
A抓住三点:谁能发(内核、其他进程)、何时处理(返回用户态之前)、处理函数在哪执行(用户态)。
- Ⅰ 对硬件异常、Ctrl+C 由内核发出;进程可以用 kill(pid, sig) 系统调用给别的进程发信号。
- Ⅱ 错内核只是在 PCB 的待处理位图里置位,要等目标进程从内核态返回用户态之前才检查处理,不会在指令中途打断——那是硬件中断的行为。
- Ⅲ 对每种信号只占 1 位,处理之前再来几次也只记一次(屏蔽期间发 3 次只处理 1 次,WSL 实测)。
- Ⅳ 错处理函数是用户程序里的一段代码,内核只负责在返回用户态时安排跳过去,执行时 CPU 在用户态。
干扰项从哪来:B 是把信号当成硬件中断,以为“立即打断”;C 是只记得“同种信号只记一次”,却以为信号只能由内核发出,又误以为处理函数在内核态;D 是以为“内核安排调用”就等于“在内核态执行”
信号 = 便条:内核置位,返回用户态前检查,处理函数在用户态执行,同种不排队。
① 内核在 P 的 PCB 中将 SIGINT 对应的待处理位置 1
② CPU 响应键盘中断,转入内核
③ P 在用户态执行 h
④ 内核准备返回 P 的用户态时,检查到有待处理且未被屏蔽的信号
- A. ①②④③
- B. ②①④③
- C. ②④①③
- D. ②①③④
展开解析
BCtrl+C 首先是一次键盘中断;内核识别出它之后才“发”信号;处理一律推迟到返回用户态之前。
- A 错信号不会凭空先出现:用户按键首先产生键盘中断,内核的终端驱动识别出 Ctrl+C 之后才置位,① 不可能在 ② 之前。
- B 对② 键盘中断进内核 → ① 终端驱动识别 Ctrl+C,给前台进程 P 置位 → ④ 中断处理完、返回 P 的用户态前检查到 SIGINT → ③ 跳到用户态执行 h。
- C 错把“检查”放到“置位”之前:内核检查时发现的正是 ① 刚置的那一位,先查后置就什么也查不到。
- D 错以为处理函数在内核检查之前就已执行:是 ④ 的检查决定了要不要去执行 h,③ 不可能在 ④ 之前。
硬件中断 → 内核置位 → 返回用户态前检查 → 用户态处理函数。
- A. 信号用于通知进程某个事件已经发生,信号量用于实现进程的同步与互斥
- B. 对信号量执行 P 操作可能使调用进程阻塞;进程收到信号后不一定被终止
- C. 用 kill 系统调用向进程发送 SIGTERM 信号后,目标进程一定立即终止
- D. SIGKILL 信号不能被进程捕获、屏蔽或忽略
展开解析
Ckill 只是“发信号”:目标进程何时处理、怎么处理都不由发送方决定。
- A 对一个是通知机制,一个是带等待队列的计数器(P、V 原语),用途不同。
- B 对P 使 value 减到负数时进程阻塞;信号可以被捕获或忽略,默认动作也不全是终止(如 SIGCHLD 默认忽略)。
- C 错本题选它。“一定立即终止”错两处:处理要等到目标进程返回用户态之前;SIGTERM 可以被捕获或忽略,进程可能根本不退出。
- D 对SIGKILL、SIGSTOP 是两个“强制”信号,保证管理员总能终止或暂停进程。
能被捕获、屏蔽、忽略的信号,发送方都无法保证效果;只有 SIGKILL、SIGSTOP 例外。
② 多处理器、硬件多线程与向量处理器
为什么可能考:2022 年进入考纲,18 年里只有 2022-22 一道题,而它一题就同时考了 MIMD、SIMD、硬件多线程、SMP 四个点——说明命题人认为这部分适合出辨析题。会怎么考:细粒度 / 粗粒度 / SMT 的区别;SMP 共享地址空间;向量处理器省掉了哪些指令。复习:4.5 Flynn 分类、物理核与逻辑核。
Ⅰ. 细粒度多线程可以在每个时钟周期切换线程
Ⅱ. 同时多线程(SMT)可以在同一个时钟周期内发射来自不同线程的指令
Ⅲ. 支持硬件多线程的处理器要为每个线程配置独立的 PC 和寄存器组
Ⅳ. 共享内存多处理器(SMP)中,每个处理器都有自己独立的物理地址空间
- A. 仅Ⅰ、Ⅱ
- B. 仅Ⅱ、Ⅲ、Ⅳ
- C. Ⅰ、Ⅱ、Ⅲ、Ⅳ
- D. 仅Ⅰ、Ⅱ、Ⅲ
展开解析
D硬件多线程“切换几乎不花时间”的前提是每个线程有自己的一套 PC 和寄存器;SMP 的关键词是“共享单一物理地址空间”。
- Ⅰ 对细粒度 = 每拍轮换线程;粗粒度 = 遇到 Cache 缺失等长时间阻塞才换。
- Ⅱ 对SMT(如 Intel 超线程)在同一拍里混着发射几个线程的指令,把空闲的功能部件填满。
- Ⅲ 对正是因为每个线程的现场都在硬件里各有一份,切换时不用像进程切换那样保存、恢复寄存器。
- Ⅳ 错SMP 中所有处理器共享单一的物理地址空间(2022-22 D),否则就不叫“共享内存”了;各有独立地址空间的是多计算机(消息传递)系统。
干扰项从哪来:A 是以为线程现场全靠操作系统保存,漏判 Ⅲ;B 是漏了 Ⅰ,又把 SMP 和多计算机系统混为一谈;C 是把四条都当成多处理器的“特点”全选
硬件多线程:多套 PC + 寄存器,共享功能部件;SMP:多个处理器共享一个物理地址空间。
- A. 16 倍
- B. 64 倍
- C. 96 倍
- D. 384 倍
展开解析
B两边都要按“总共处理 256 个元素”计数。
- 标量:256 × 6 = 1536 条。
- 向量:256 ÷ 64 = 4 轮,每轮 4 + 2 = 6 条,共 24 条。
- 1536 ÷ 24 = 64 倍。
- A 错16 = 64 × 6 ÷ 24:标量只算了一个向量长度(64 个元素)的 384 条,却拿去和 256 个元素的 24 条比。
- B 对1536 ÷ 24 = 64。
- C 错96 = 1536 ÷ 16:向量版只数了 4 轮 × 4 条向量指令,漏了每轮 2 条循环控制。
- D 错384 = 1536 ÷ 4:照搬“64 个元素只要 4 条向量指令”,忘了 256 个元素要分 4 轮做,也漏了循环控制。
SIMD / 向量:一条指令处理一组数据,省的是取指、译码和循环控制。
③ 超标量与动态流水线
为什么可能考:“超标量和动态流水线的基本概念”是考纲原文,只有 2017-17 涉及。会怎么考:多发射流水线的时间计算;动态调度“乱序执行、按序提交”的辨析;动态 / 静态调度的主体。复习:4.4 动态流水线。
Ⅰ. 指令可以不按程序顺序执行,但执行结果按程序顺序提交
Ⅱ. 动态调度由编译器在编译时完成
Ⅲ. 采用乱序执行后,存在写后读(RAW)相关的指令也不必等待
Ⅳ. 按序提交使得发生异常时仍能做到精确异常
- A. 仅Ⅰ、Ⅳ
- B. 仅Ⅰ、Ⅱ
- C. 仅Ⅰ、Ⅲ、Ⅳ
- D. 仅Ⅱ、Ⅲ、Ⅳ
展开解析
A“动态”指硬件在运行时调度;乱序只能让不相关的指令先走,真正的 RAW 照样要等。
- Ⅰ 对乱序执行、按序提交,结果先放在重排序缓冲里。
- Ⅱ 错编译器在编译时调整指令顺序叫静态调度;动态调度由硬件在运行时完成。
- Ⅲ 错后一条要用的数据前一条还没算出来,谁也不能凭空得到,只能等;乱序执行让的是别的不相关指令先执行。
- Ⅳ 对出异常的指令后面的指令即使算完也还没提交,可以整体作废,再从这条指令重新执行。
干扰项从哪来:B 是把“静态调度”和“动态调度”的主体(编译器 / 硬件)记反;C 是以为乱序执行能消除一切数据冒险;D 是同时犯了 B、C 的两个错误,还漏判 Ⅰ
乱序执行,按序提交;动态 = 硬件,静态 = 编译器。
- A. 1004
- B. 500
- C. 504
- D. 2500
展开解析
C先用 5 拍把第一批 2 条送完,以后每拍再完成 2 条。
- 第 1 批 2 条在第 5 拍结束时完成。
- 剩下 998 条 = 499 批,每拍完成一批,再要 499 拍。
- 共 5 + 499 = 504 拍(一般式:k + (n / m − 1),m 为发射宽度)。
- A 错1004 = 5 + 999:按单发射流水线算,忘了每拍能完成 2 条。
- B 错500 = 1000 ÷ 2:只算了“每拍 2 条”,漏了流水线装满之前的 4 拍。
- C 对5 + (500 − 1) = 504。
- D 错2500 = 1000 × 5 ÷ 2:把每条指令的 5 段当成串行执行,没有体现流水线的重叠。
m 路超标量 k 段流水线,n 条指令:k + (n/m − 1) 拍。
④ SSD、RAID 与 Flash
为什么可能考:SSD 2022 年进入考纲,只在 2023-22、2025-21、2025-32 的选项里出现过;RAID 只有 2013-20 一题;Flash 只有 2012-16。会怎么考:页写块擦、磨损均衡由谁做;RAID 的容量与容错;Flash 与 EEPROM 的区别。复习:10.1 SSD、RAID。
Ⅰ. 以页为单位读写,以块为单位擦除
Ⅱ. 随机读的速度与顺序读接近
Ⅲ. 已写入数据的页可以直接覆盖写入新数据
Ⅳ. 静态磨损均衡会把长期不修改的数据迁移到擦除次数多的块上
- A. 仅Ⅰ、Ⅱ
- B. 仅Ⅰ、Ⅲ
- C. Ⅰ、Ⅱ、Ⅲ、Ⅳ
- D. 仅Ⅰ、Ⅱ、Ⅳ
展开解析
考点10.1 SSD
DSSD 的三个关键词:页写块擦、没有寻道、磨损均衡。
- Ⅰ 对这是闪存的物理特性:写以页为单位,擦除以块(若干页)为单位。
- Ⅱ 对没有机械的寻道和旋转延迟,随机读不比顺序读慢多少——所以对 SSD 做磁盘调度意义不大。
- Ⅲ 错闪存的页写入前必须先擦除;SSD 的闪存翻译层把新数据写到另一个空页(异地更新),旧页标为无效,等垃圾回收。
- Ⅳ 对冷数据占着“年轻”的块不动会造成磨损不均,静态磨损均衡把它们搬到老块上,让年轻块参与写入。
干扰项从哪来:A 是漏判了静态磨损均衡(只记得动态磨损均衡);B 是把 SSD 当成可以原地改写的磁盘,并漏判 Ⅱ;C 是以为闪存可以像内存一样直接覆盖写
页写块擦、异地更新、垃圾回收、磨损均衡(由 SSD 控制器做,不由文件系统做,2025-32)。
- A. 10TB、1 块
- B. 12TB、0 块
- C. 6TB、3 块
- D. 8TB、2 块
展开解析
ARAID 5:分布式奇偶校验,相当于拿出 1 块盘的容量放校验,能容忍 1 块盘损坏。
- 可用容量 = (6 − 1) × 2TB = 10TB。
- 每个条带只有 1 个校验块,坏 1 块可用异或恢复,坏 2 块就无法恢复 → 1 块。
- A 对(n − 1) × 单盘容量,容错 1 块。
- B 错12TB、0 块是 RAID 0(只条带化、没有冗余)的数据,忘了 RAID 5 要留校验空间。
- C 错6TB、3 块是 RAID 1 / RAID 10 镜像的思路(一半容量;每对镜像坏一块都能撑住),不是 RAID 5。
- D 错8TB、2 块是以为 RAID 5 要两块盘放校验(那是 RAID 6 的做法,超出考纲)。
RAID 0:nC、不容错;RAID 1:nC/2;RAID 5:(n − 1)C、容错 1 块。
- A. Flash 存储器是非易失性存储器,断电后信息不丢失
- B. Flash 存储器由 EEPROM 发展而来,可以在线擦除和改写
- C. Flash 存储器写入前要先擦除,写的速度比读慢
- D. Flash 存储器可以以字节为单位进行擦除
⑤ 总线仲裁与异步定时
为什么可能考:总线仲裁 18 年从未考过;“总线事务和定时”是考纲条目,全互锁的握手次数是天然的计算点。会怎么考:三种集中仲裁的控制线数;全互锁传输率。复习:9.2 仲裁、异步定时。
- A. 2、5、32
- B. 3、6、32
- C. 3、6、33
- D. 3、18、33
展开解析
考点9.2 总线仲裁
C三种方式都有一根“总线忙”BS;链式多 BR、BG,计数器多 BR 和设备地址线,独立请求每个设备一对 BR/BG。
- 链式查询:BR、BG、BS 共 3 根。
- 计数器定时查询:BR、BS 2 根 + 设备地址线 ⌈log₂16⌉ = 4 根,共 6 根。
- 独立请求:每个设备一对 BRi、BGi,共 2 × 16 = 32 根,加 BS 共 33 根(王道口径 2n + 1)。
- A 错2、5、32 是三种方式都漏了总线忙线 BS。
- B 错3、6、32 是独立请求漏了 BS。
- C 对3、6、33。
- D 错18 = 16 + 2:计数器定时查询把“设备地址线”数成了设备个数 n,实际只要 ⌈log₂n⌉ 根就能编码 n 个设备。
控制线:链式 3、计数器 ⌈log₂n⌉ + 2、独立请求 2n + 1。
- A. 200MB/s
- B. 50MB/s
- C. 100MB/s
- D. 12.5MB/s
展开解析
B全互锁一次传送要 4 步握手:请求↑、回答↑、请求↓、回答↓,每步都要等对方确认。
- 一次传送时间 = 4 × 20ns = 80ns。
- 每次传 32 位 = 4B。
- 传输率 = 4B ÷ 80ns = 5 × 107 B/s = 50MB/s(速率的 M 取 106)。
- A 错200MB/s = 4B ÷ 20ns:只算了一次握手,把全互锁当成“发一次请求就完成”。
- B 对4B ÷ 80ns = 50MB/s。
- C 错100MB/s = 4B ÷ 40ns:按半互锁或不互锁的两步握手算,没有数满全互锁的 4 步。
- D 错12.5MB/s = 1B ÷ 80ns:把每次传送的 32 位当成了 1 字节。
全互锁:一次传送 4 个握手时间;速率单位的 M = 10⁶。
⑥ 非规格化数、移码与舍入
为什么可能考:IEEE 754 年年考,但阶码全 0 的非规格化数、单独考移码、舍入到偶数这几个角度几乎没出过。会怎么考:解码一个阶码全 0 的机器数;移码与补码互换;int 转 float 后值变了多少。复习:2.6 非规格化数、舍入、间距。
- A. 2−127
- B. 1.5 × 2−127
- C. 2−128
- D. 1.5 × 2−126
展开解析
A先看阶码:全 0 → 非规格化数,没有隐藏的 1,指数固定按 −126 算。
- 0040 0000H = 0 | 0000 0000 | 100 0000 … 0。
- S = 0;E = 0 → 非规格化,值 = 0.M × 2−126。
- M = 0.1B = 0.5 → 值 = 0.5 × 2−126 = 2−127(Python struct 解码核对)。
- A 对0.1B × 2−126 = 2−127。
- B 错1.5 × 2−127:当成规格化数补了隐藏的 1(1.1B = 1.5),指数又按 0 − 127 算。
- C 错2−128:知道没有隐藏位(0.1B),但指数用了 0 − 127 = −127,忘了非规格化数的指数固定是 −126。
- D 错1.5 × 2−126:指数 −126 对了,却又补了隐藏的 1。
E = 0:值 = 0.M × 2⁻¹²⁶(无隐藏位、指数 −126)。
- A. 3FH
- B. C1H
- C. 41H
- D. BFH
展开解析
D同一个数的移码和补码只差符号位:符号位取反即可。
- 3FH = 0011 1111B = 63,x = 63 − 128 = −65。
- −65 的 8 位补码 = 256 − 65 = 191 = 1011 1111B = BFH。
- 直接法:3FH 的最高位 0 → 1,得 BFH。
- A 错3FH:以为移码和补码相同,忘了偏置 128 使两者符号位相反。
- B 错C1H = 1100 0001B 是 −65 的原码,题目要的是补码。
- C 错41H = 65 的补码:把移码的最高位 0 当成了“正数”,符号判反了(移码最高位 0 表示负数)。
- D 对BFH。
偏置 2ⁿ⁻¹ 的移码 = 补码的符号位取反;移码最高位 1 表示正数。
float f = i; int j = f; 后(float 采用 IEEE 754 单精度、就近舍入),j 与 i 相比( )。- A. 相等
- B. 小 4
- C. 大 4
- D. 小 12
展开解析
C226 附近 float 的间距 = 226 − 23 = 8,只能表示 8 的倍数。
- 相邻可表示值:226 + 8 = 67108872 和 226 + 16 = 67108880。
- i 比 67108872 多 4,正好是半个间距 → 就近舍入取“偶数”:67108872 的尾数末位是 1(8 ÷ 8 = 1),67108880 的末位是 0 → 取 67108880。
- j = 67108880,比 i 大 4(gcc 实测)。
- A 错以为 int 转 float 总能精确;float 只有 24 位有效位,超过 224 的整数就可能被舍入。
- B 错小 4 是按截断(向 0 舍入)得到 67108872,忽略了“正好一半时取偶数”。
- C 对舍入到 67108880,大 4。
- D 错小 12 是把间距算成 32(误把尾数当 21 位),认为 12 不到半个间距而舍成 226。
间距 = 2^(指数 − 23);正好一半时取尾数末位为 0 的那个。
⑦ 调度的实现:闲逛进程、多处理机调度、线程调度
为什么可能考:2022 年起考纲把“调度器、闲逛进程、内核级线程与用户级线程调度、多处理机调度”写进“调度的实现”,至今没有单独出题。会怎么考:闲逛进程的性质;用户级线程下内核按什么调度;处理器亲和性、负载均衡。复习:6.6 闲逛进程与多处理机。
Ⅰ. 系统中没有其他就绪进程时,调度程序选择闲逛进程运行
Ⅱ. 闲逛进程的优先级最低,且它不会因为等待资源而阻塞
Ⅲ. 只支持用户级线程的系统中,内核调度的基本单位是线程
Ⅳ. 多处理机调度中的“处理器亲和性”是指尽量让进程在它上次运行的处理器上运行
- A. 仅Ⅰ、Ⅱ
- B. 仅Ⅰ、Ⅱ、Ⅳ
- C. 仅Ⅱ、Ⅳ
- D. Ⅰ、Ⅱ、Ⅲ、Ⅳ
展开解析
B闲逛进程是“CPU 总得跑点什么”的兜底;用户级线程内核看不见;亲和性为了复用 Cache 里的数据。
- Ⅰ 对就绪队列空时调度程序也有东西可选,“没有进程可运行”这种特殊情况就统一了。
- Ⅱ 对它只在没别人可运行时才运行,一有就绪进程就让出;它不申请任何资源,所以永远不会阻塞。
- Ⅲ 错用户级线程由用户态的线程库管理,内核看不见,内核调度的单位是进程。
- Ⅳ 对进程换到别的处理器,原来处理器 Cache 里它的数据就白费了;亲和性正是为了避免这种开销。
干扰项从哪来:A 是漏判 Ⅳ(没接触过多处理机调度);C 是漏判 Ⅰ,误以为闲逛进程只在关机前运行;D 是以为“有线程就按线程调度”,忽略了用户级线程对内核不可见
只有用户级线程 → 内核按进程调度;有内核级线程 → 按内核级线程调度。
- A. 20%
- B. 50%
- C. 80%
- D. 12.5%
展开解析
A内核只看见“调度实体”:A 整体算 1 个,B 的每个内核级线程各算 1 个。
- 内核看到的可运行实体:A(1 个)+ B 的 4 个内核级线程 = 5 个。
- 轮转时每个实体轮到的机会相同 → A 占 1/5 = 20%,再由 A 的线程库分给它的 4 个线程。
- A 对1 ÷ 5 = 20%。
- B 错50%:以为内核按进程平均分配(A、B 各一半),忽略了 B 的 4 个内核级线程各自参加轮转。
- C 错80% 是 B 获得的份额,把 A、B 弄反了。
- D 错12.5% = 1/8:以为 8 个线程平分 CPU,而且算的是 A 中一个线程的份额;就算按 8 个平分,A 的 4 个线程合起来也是 50%。
多对一模型下,一个进程无论有多少用户级线程,在内核眼里都只是一个调度实体。
⑧ 内存映射文件、写时复制与虚存性能
为什么可能考:内存映射文件只考过 2025-30;写时复制没有直接考过;“虚存性能的影响因素及改进方式”是考纲独立条目,近七年考了三次。会怎么考:映射时读不读文件;fork 后复制几页;EAT 的定量计算。复习:8.7 内存映射文件、写时复制、8.9 虚存性能。
- A. 文件映射后,进程可以像访问内存数组一样通过指针读写文件内容
- B. 多个进程映射同一个文件,可以实现进程间共享数据
- C. 建立映射时,操作系统立即把整个文件读入内存
- D. 被修改过的映射页,最终会被写回磁盘上的文件
展开解析
C映射只是在页表里登记“这段虚拟地址对应文件的哪一部分”,真正读入靠缺页时按需调页。
- A 对这正是内存映射文件的用处:用访存代替 read / write 系统调用。
- B 对各进程的页表项指向同一批物理页框,一个进程写入,另一个立刻能看到。
- C 错本题选它。建立映射时只建立对应关系,不读入数据;进程第一次访问某页时发生缺页,才从文件里把这一页读进来。
- D 对脏页在被换出、解除映射或同步时写回文件,和请求分页里换出脏页一样。
mmap = 文件当“对换区”:按需调页、脏页写回、可共享。
- A. 3
- B. 200
- C. 205
- D. 5
- A. 120ns
- B. 220ns
- C. 210ns
- D. 1120ns
展开解析
B先算不缺页时的 EAT,再按缺页率加权。
- 不缺页时:10 + 100 + 0.1 × 100 = 120ns(每次查 TLB、访问数据;TLB 未命中的 10% 再查一次页表)。
- 10ms = 107 ns。
- EAT = (1 − 10−5) × 120 + 10−5 × 107 ≈ 120 + 100 = 220ns。
- A 错120ns 是不缺页时的值,忽略了缺页(10−5 × 107 = 100ns,几乎让时间翻倍)。
- B 对约 220ns。
- C 错210ns:不缺页部分算成 10 + 100 = 110ns,漏了 TLB 未命中时多查一次页表的 0.1 × 100ns。
- D 错1120ns:把 10ms 换成了 108 ns(多了一个 0),缺页部分变成 1000ns。
EAT = 不缺页时间 × (1 − p) + 缺页时间 × p;ms = 10⁶ ns。
⑨ 文件系统的全局结构、VFS、挂载与目录操作
为什么可能考:VFS(2025-28)、挂载(2021-46)、目录操作(2013-23)都只考过一次,2026-46 刚用到文件系统的全局结构。会怎么考:VFS 的作用;超级块里有什么;创建 / 删除文件要改哪些结构。复习:10.8 全局结构、VFS 与挂载、10.5 目录。
Ⅰ. 虚拟文件系统(VFS)为上层提供统一的文件操作接口,屏蔽各种具体文件系统的差异
Ⅱ. 挂载是把一个文件系统的根目录接到另一个文件系统的某个目录(挂载点)上
Ⅲ. 超级块记录整个文件系统的全局信息,如块大小、空闲块数、索引结点数
Ⅳ. 不同的文件系统挂载在同一棵目录树上后,必须使用相同的盘块大小
- A. 仅Ⅰ、Ⅱ
- B. 仅Ⅰ、Ⅲ
- C. 仅Ⅱ、Ⅲ、Ⅳ
- D. 仅Ⅰ、Ⅱ、Ⅲ
展开解析
DVFS 的意义就是“各管各的格式,对上一个接口”。
- Ⅰ 对应用只调用 open / read / write,由 VFS 转给 ext4、FAT、NFS 等具体文件系统的实现(2025-28)。
- Ⅱ 对挂载后访问挂载点,就进入了被挂载文件系统的根目录。
- Ⅲ 对超级块是文件系统的“总说明书”,挂载时读入内存。
- Ⅳ 错每个文件系统有自己的超级块、自己的块大小,正因为 VFS 屏蔽了差异,它们才能挂在同一棵目录树上。
干扰项从哪来:A 是以为超级块只是磁盘上的“第一块”、不知道它记录整个文件系统的全局信息,漏判 Ⅲ;B 是不清楚挂载就是“接到挂载点上”,漏判 Ⅱ;C 是以为挂在同一棵树上就要“统一格式”,误判 Ⅳ,又漏了 Ⅰ
VFS 统一接口;挂载 = 接到挂载点;超级块 = 一个文件系统的全局信息。
- A. 为 f 分配数据块
- B. 检查目录 b 中是否已有名为 f 的文件
- C. 为 f 分配一个空闲的索引结点
- D. 在目录 b 中添加 f 的目录项
展开解析
考点10.5 目录与目录操作10.4 FCB 与 inode
A创建文件 = 建“户口”(inode + 目录项);数据块等真正写入时再分配。
- A 对空文件还没有任何数据,可以先不分配数据块,写入时再按需分配——“不一定需要”,本题选它。
- B 错同一目录下不能重名,创建前一定要检查(这也是“创建文件前要先查一遍目录”的原因)。
- C 错每个文件都必须有自己的索引结点记录属性和地址项,这一步一定要做。
- D 错不在父目录里登记,就没法按路径名找到 f,这一步一定要做。
创建:查重名 → 分配 inode → 写目录项;数据块写入时才分配。
⑩ I/O 应用接口、SPOOLing 与坏块
为什么可能考:“输入输出应用程序接口(阻塞 / 非阻塞 I/O)”从未单独出题;SPOOLing 只考过 2016-31;坏块是 2022 年考纲新增的“磁盘管理”内容。会怎么考:三种 I/O 调用的区别;输入井、输出井在哪;扇区备用对谁透明。复习:9.5 三种 I/O、9.8 SPOOLing、10.3 坏块。
Ⅰ. 进程调用阻塞式 read 而数据尚未就绪时,进程由运行态转为阻塞态
Ⅱ. 非阻塞式 read 在数据未就绪时立即返回,进程不会阻塞
Ⅲ. 异步 I/O 发起后进程可以继续执行,I/O 完成时再通过信号或回调等方式得到通知
Ⅳ. 非阻塞 I/O 和异步 I/O 是同一个概念
- A. 仅Ⅰ、Ⅱ
- B. Ⅰ、Ⅱ、Ⅲ、Ⅳ
- C. 仅Ⅰ、Ⅱ、Ⅲ
- D. 仅Ⅱ、Ⅲ、Ⅳ
展开解析
C三种“等法”的区别在于:数据没好时,进程是睡着等、马上回来自己再问,还是交代完就走、等别人通知。
- Ⅰ 对阻塞式调用在数据没准备好时把进程挂到设备等待队列,状态变为阻塞。
- Ⅱ 对非阻塞调用立即返回“暂时没有数据”,进程继续运行,通常要过一会儿再来问(轮询)。
- Ⅲ 对异步 I/O 把“读到哪个缓冲区”交代给内核就返回,内核完成后通知进程——数据搬运也由内核完成。
- Ⅳ 错非阻塞 I/O 要进程自己反复来问、自己把数据取走;异步 I/O 由内核做完再通知,两者不同。
干扰项从哪来:A 是只熟悉阻塞和非阻塞两种调用,不知道异步 I/O 由内核完成后通知,漏判 Ⅲ;B 是把“非阻塞”和“异步”当成同一回事;D 是误判 Ⅳ,又以为阻塞式调用不改变进程状态
阻塞:睡着等;非阻塞:马上回来、自己再问;异步:交代完就走、内核做完通知。
- A. 输入井和输出井是在磁盘上开辟的存储区
- B. SPOOLing 不需要外存的支持,只靠内存缓冲区就能实现
- C. 用户进程的打印数据先送到输出井,打印机空闲时再由输出进程输出
- D. SPOOLing 把一台独占的打印机变成可供多个用户“同时”使用的虚拟设备
展开解析
BSPOOLing 的核心是用磁盘上的输入井、输出井模拟脱机输入输出。
- A 对井在磁盘上,容量大;内存里的输入 / 输出缓冲区只是中转。
- B 错本题选它。没有磁盘上的井,就没地方存放“排队等打印”的大量数据,也就无法把独占设备虚拟成共享设备。
- C 对用户进程只把数据写进输出井就算“打印完了”,真正的打印由输出进程在打印机空闲时完成。
- D 对每个用户都觉得自己独占了一台打印机——这是虚拟设备。
SPOOLing = 输入井 / 输出井(磁盘)+ 输入 / 输出缓冲区(内存)+ 输入 / 输出进程。
Ⅰ. 采用扇区备用方式时,由磁盘控制器用备用扇区替换坏扇区,对操作系统透明
Ⅱ. 由文件系统在 FAT 或坏块表中标记坏块时,坏块对操作系统不透明
Ⅲ. 采用扇区备用方式后,操作系统看到的逻辑扇区号会因坏扇区而出现跳号
Ⅳ. 备用扇区一般在低级格式化时预留
- A. 仅Ⅰ、Ⅱ、Ⅳ
- B. 仅Ⅰ、Ⅱ
- C. 仅Ⅱ、Ⅲ、Ⅳ
- D. Ⅰ、Ⅱ、Ⅲ、Ⅳ
展开解析
考点10.3 坏块
A两种处理方式的区别就是“谁来做”:控制器做 → 透明;操作系统做 → 不透明。
- Ⅰ 对控制器维护坏块映射,把对坏扇区的访问重定向到备用扇区,操作系统完全不知道。
- Ⅱ 对操作系统记下哪些块坏了,以后不再分配,坏块对它显然是可见的。
- Ⅲ 错正因为“透明”,逻辑扇区号仍是连续的,坏扇区的编号被映射到备用扇区上,不会跳号。
- Ⅳ 对低级格式化(物理格式化)时划分扇区、预留备用扇区并初始化坏块表。
干扰项从哪来:B 是漏判了 Ⅳ(不知道备用扇区何时准备);C 是把“透明”理解反了,以为操作系统能看到跳号;D 是四条全选,同样误判了 Ⅲ
扇区备用:控制器做、对 OS 透明、低级格式化时预留;标记坏块:OS 做、不透明。
⑪ 冷了很久的计组点:多级 Cache、双端口 RAM、控制器
为什么可能考:多级 Cache 只有 2023-19 一题;双端口 RAM 从未考过;硬布线控制器只在 2009-19 出现;专门考控制器原理的选择题 2017-18 以后就没有了(2021-17、2024-13、2025-16 只在选项里提到微指令、微程序)。这些都是“冷了很久、随时可能回来”的点。复习:8.3 多级 Cache、8.2 双端口 RAM、4.3 控制器。
- A. 4ns
- B. 23ns
- C. 14ns
- D. 5ns
展开解析
D平均时间 = L1 时间 + L1 缺失率 × (L2 时间 + L2 局部缺失率 × 主存时间)。
- L2 那一层的平均时间:10 + 0.2 × 100 = 30ns。
- 总平均:2 + 0.1 × 30 = 5ns(程序核对)。
- A 错4ns = 2 + 0.1 × 0.2 × 100:漏了 L1 缺失时访问 L2 的那 10ns。
- B 错23ns = 2 + 0.1 × 10 + 0.2 × 100:把 20% 的局部缺失率当成了全局缺失率,没乘 L1 的 10%。
- C 错14ns = 2 + 10 + 0.1 × 0.2 × 100:以为每次访存都要访问 L2,L2 的 10ns 没乘缺失率 10%。
- D 对2 + 0.1 × (10 + 0.2 × 100) = 5ns。
全局缺失率 = 各级局部缺失率相乘(这里 0.1 × 0.2 = 2%)。
- A. 两个端口同时读同一个存储单元
- B. 两个端口同时访问不同的存储单元
- C. 一个端口写某存储单元,另一个端口同时读该单元
- D. 两个端口同时写不同的存储单元
展开解析
C只有访问同一个单元、且至少一方在写时,才会出错。
- A 错两边同时读同一单元,读出的都是同一个值,互不影响,不冲突。
- B 错不同单元有各自的地址译码和数据通路,双端口 RAM 正是为此设计的。
- C 对一边写、一边读同一单元,读出的可能是旧值也可能是新值(读出错误);控制逻辑发 BUSY 让一方等待。两边同时写同一单元则是写入错误,同样冲突。
- D 错写的是不同单元,各写各的,不冲突。
双端口 RAM 冲突 = 同一单元 + 至少一方写;同读同一单元不冲突。
Ⅰ. 硬布线控制器的控制信号由组合逻辑电路根据操作码、时序信号和状态条件直接产生
Ⅱ. 微程序控制器中,一条机器指令对应一段微程序
Ⅲ. 在同样的工艺条件下,硬布线控制器的速度比微程序控制器慢
Ⅳ. RISC 处理器多采用微程序控制器
- A. 仅Ⅰ、Ⅱ、Ⅳ
- B. 仅Ⅰ、Ⅱ
- C. 仅Ⅱ、Ⅲ
- D. 仅Ⅰ、Ⅲ、Ⅳ
展开解析
B硬布线 = 组合逻辑,快、难改,RISC 爱用;微程序 = 存储逻辑,慢一点、易改,CISC 爱用。
- Ⅰ 对这就是“硬布线”的定义:控制信号是电路直接“算”出来的。
- Ⅱ 对每条机器指令的功能由控制存储器里的一段微程序(若干条微指令)解释执行。
- Ⅲ 错硬布线不用逐条读控制存储器,速度更快(2009-19 考过“硬布线速度并不慢”)。
- Ⅳ 错RISC 指令简单规整,适合硬布线;微程序多用于指令复杂的 CISC。
干扰项从哪来:A 是把 RISC 和 CISC 的控制器类型记反;C 是把硬布线和微程序的速度记反,并漏判 Ⅰ;D 是同时记反了速度和 RISC 的控制方式
硬布线:快、难改、RISC;微程序:慢、易改、CISC。
13.2概念辨析专练:Ⅰ / Ⅱ / Ⅲ / Ⅳ 组合题
组合题是选择题里最“不讲情面”的一类:四个说法判对三个,照样 0 分。13.0 的统计显示,操作系统的组合题在 2015—2020 年占了 28%。下面 28 道按考纲章节排列(计组 16 道、操作系统 12 道),说法都取自各节“易错”框里最容易混淆的结论。做法建议:
- 先判最有把握的一条,用它一次划掉两个选项;
- 再判剩下的选项之间有差别的那几条,所有选项都包含(或都不包含)的说法可以不判;
- 每条都要能说出理由——对完答案后逐条看解析,说不出理由的那条就是你的薄弱点,点链接回原文。
解析逐条给出对 / 错和理由,并写出每个干扰选项是“把哪一条当成了对的”或“漏选了哪一条”、为什么容易这样错。题目由 verify/08/p13/gen_judge.py 生成,生成时断言“正确选项 = 真说法的集合”,干扰项都只与正确答案差一两条。
计组(1)计算机系统概述
Ⅰ. 冯·诺依曼机中,指令和数据以同等地位存放在存储器中,CPU 根据指令周期的不同阶段来区分取出的是指令还是数据
Ⅱ. 汇编语言程序员可以通过指令读取 MAR 和 MDR 的内容
Ⅲ. 同一台机器上运行两个程序,程序 A 的 MIPS 比程序 B 高,说明 A 的执行时间一定比 B 短
Ⅳ. 软件和硬件在逻辑功能上是等价的,同一功能既可以用硬件实现,也可以用软件实现
- A. 仅Ⅰ、Ⅳ
- B. 仅Ⅱ、Ⅳ
- C. 仅Ⅰ、Ⅱ、Ⅳ
- D. 仅Ⅰ、Ⅲ、Ⅳ
展开解析
考点1.3 指令的执行1.2 寄存器的可见性1.8 MIPS 的陷阱1.4 软硬件关系
A正确的是 Ⅰ、Ⅳ。
- Ⅰ 对取指周期取出的一定是指令,执行周期按地址取出的是数据(2009-11)。
- Ⅱ 错MAR、MDR 是 CPU 访存的内部接口,没有指令能直接访问它们,对汇编程序员透明。
- Ⅲ 错执行时间 = 指令条数 ÷ (MIPS × 10⁶),两个程序的指令条数不同,MIPS 高的未必更快。
- Ⅳ 对例如乘法既可以用乘法器做,也可以用加法和移位指令编程实现,只是速度和成本不同。
干扰项从哪来:B 是把 Ⅱ 当成对的(以为 CPU 里的寄存器都对程序员可见)、漏选 Ⅰ(以为指令和数据靠存放区域或标志位区分);C 是把 Ⅱ 当成对的(以为 CPU 里的寄存器都对程序员可见);D 是把 Ⅲ 当成对的(把“MIPS 越高越快”用到了不同程序之间)
Ⅰ. CPU 执行时间 = 指令条数 × CPI × 时钟周期
Ⅱ. 主频提高一倍,任何程序的执行时间都会缩短为原来的一半
Ⅲ. 计算机是否提供乘法指令属于体系结构问题,乘法指令用专门的乘法器还是用加法器多次相加实现属于组成问题
Ⅳ. 系列机中新机型能运行旧机型上的程序,称为向前兼容
- A. 仅Ⅰ
- B. 仅Ⅲ
- C. 仅Ⅰ、Ⅲ
- D. 仅Ⅰ、Ⅱ、Ⅲ
展开解析
考点1.8 性能指标1.4 体系结构与组成1.4 系列机与兼容
C正确的是 Ⅰ、Ⅲ。
- Ⅰ 对三个因素分别由程序(及编译器)、指令系统与实现、工艺共同决定。
- Ⅱ 错访存、I/O 的时间不随主频成比例缩短,主频提高后 CPI 还可能变大(每次访存要等更多周期)。
- Ⅲ 对体系结构是程序员看得见的属性(指令集),组成是这些属性的实现方式。
- Ⅳ 错新机兼容旧机的程序叫向后兼容,这是系列机必须保证的;向前兼容不一定保证。
干扰项从哪来:A 是漏选 Ⅲ(把“有没有”与“怎么做”混为一谈);B 是漏选 Ⅰ(把 CPI 和 MIPS 的公式记混);D 是把 Ⅱ 当成对的(只看公式里的时钟周期这一项)
计组(2)数据的表示和运算
Ⅰ. n 位补码能表示的整数比 n 位原码多一个
Ⅱ. 两个补码数相加时,若最高位(符号位)产生进位,则结果一定溢出
Ⅲ. 采用双符号位(变形补码)时,运算结果的两个符号位为 10 表示负溢出
Ⅳ. CF 标志对带符号整数的运算没有意义
- A. 仅Ⅰ、Ⅲ
- B. 仅Ⅰ、Ⅲ、Ⅳ
- C. 仅Ⅱ、Ⅲ、Ⅳ
- D. Ⅰ、Ⅱ、Ⅲ、Ⅳ
Ⅰ. IEEE 754 单精度格式的阶码用偏置值为 127 的移码表示
Ⅱ. float 型能精确表示所有 int 型整数
Ⅲ. 两个规格化浮点数相加,尾数求和后可能需要右规,右规可能导致阶码上溢
Ⅳ. 浮点数的加法满足结合律
- A. 仅Ⅰ
- B. 仅Ⅲ
- C. 仅Ⅰ、Ⅱ
- D. 仅Ⅰ、Ⅲ
展开解析
考点2.6 IEEE 7542.6 精度与间距2.6 浮点加减2.6 结合律
D正确的是 Ⅰ、Ⅲ。
- Ⅰ 对真值指数 = 阶码 − 127;注意不是教材一般移码的偏置 128。
- Ⅱ 错float 只有 24 位有效位,大于 2²⁴ 的某些整数会被舍入(如 2²⁴ + 1)。
- Ⅲ 对尾数出现 1x.xxx 要右移一位、阶码 +1,阶码原来已是最大值时就上溢。
- Ⅳ 错每一步都要舍入:(2²⁴ + 1) + 1 = 2²⁴,而 2²⁴ + (1 + 1) = 2²⁴ + 2。
干扰项从哪来:A 是漏选 Ⅲ(以为尾数溢出就等于结果溢出,或反过来以为不会溢出);B 是漏选 Ⅰ(把偏置值记成 128);C 是把 Ⅱ 当成对的(以为 float 的范围比 int 大,就能装下所有 int)、漏选 Ⅲ(以为尾数溢出就等于结果溢出,或反过来以为不会溢出)
计组(3)存储器层次结构
Ⅰ. DRAM 按行刷新,刷新地址由刷新地址计数器给出,不需要 CPU 参与
Ⅱ. SRAM 的读出是破坏性读出,读出后需要再生
Ⅲ. 采用低位交叉编址的多体存储器,相邻地址的单元分布在不同的存储体中
Ⅳ. ROM 和 RAM 一样,都采用随机存取方式
- A. 仅Ⅰ、Ⅲ
- B. 仅Ⅰ、Ⅲ、Ⅳ
- C. 仅Ⅱ、Ⅲ、Ⅳ
- D. Ⅰ、Ⅱ、Ⅲ、Ⅳ
展开解析
考点8.2 刷新8.2 SRAM 与 DRAM8.2 交叉编址8.2 存取方式
B正确的是 Ⅰ、Ⅲ、Ⅳ。
- Ⅰ 对刷新是存储器内部的操作,CPU 不知道也不参与(只是刷新期间不能访问)。
- Ⅱ 错破坏性读出是 DRAM(电容放电)的特点;SRAM 用触发器存储,读出不破坏。
- Ⅲ 对低位作体号,连续访问时各体轮流启动,可以流水。
- Ⅳ 对“随机存取”指访问任一单元的时间相同,与能不能写无关(2010-16)。
干扰项从哪来:A 是漏选 Ⅳ(把“随机存取”误解为“随机读写”);C 是把 Ⅱ 当成对的(把 SRAM、DRAM 的特点记反)、漏选 Ⅰ(以为刷新要 CPU 执行刷新程序);D 是把 Ⅱ 当成对的(把 SRAM、DRAM 的特点记反)
Ⅰ. 全相联映射的 Cache,主存地址中没有组号(Cache 行号)字段
Ⅱ. 采用写回法的 Cache,每一行都需要设置脏位(修改位)
Ⅲ. Cache 行中标记的位数与主存容量无关
Ⅳ. 在 Cache 容量不变的前提下,增大块的大小一定能提高命中率
- A. 仅Ⅰ
- B. 仅Ⅰ、Ⅱ
- C. 仅Ⅰ、Ⅱ、Ⅲ
- D. 仅Ⅰ、Ⅱ、Ⅳ
Ⅰ. TLB 命中时,所访问的页一定在主存中
Ⅱ. TLB 未命中时,一定会发生缺页
Ⅲ. 虚拟存储器中,主存与辅存之间只能采用写回法
Ⅳ. Cache 命中时,TLB 也一定命中
- A. 仅Ⅰ
- B. 仅Ⅲ
- C. 仅Ⅰ、Ⅲ
- D. 仅Ⅰ、Ⅱ、Ⅲ
计组(4)指令系统
Ⅰ. 采用扩展操作码时,短操作码不能作为长操作码的前缀
Ⅱ. 相对寻址的有效地址 EA = (PC) + A 中,PC 是当前这条指令的地址
Ⅲ. 基址寻址中,基址寄存器的内容由操作系统或管理程序设定,程序执行过程中一般不变
Ⅳ. 立即寻址方式取操作数时需要访问一次主存
- A. 仅Ⅰ
- B. 仅Ⅲ
- C. 仅Ⅰ、Ⅱ
- D. 仅Ⅰ、Ⅲ
展开解析
考点3.1 扩展操作码3.2 相对寻址3.2 基址与变址3.2 寻址方式
D正确的是 Ⅰ、Ⅲ。
- Ⅰ 对否则译码时无法区分一条指令到底是短操作码还是长操作码的开头。
- Ⅱ 错执行阶段 PC 已经在取指后更新,指向下一条指令;偏移量相对的是下一条指令的地址。
- Ⅲ 对基址寄存器面向系统(扩大寻址范围、重定位),变址寄存器面向用户(数组下标)。
- Ⅳ 错操作数就在指令里,取指时一起取到,执行阶段不再访存。
干扰项从哪来:A 是漏选 Ⅲ(把基址寄存器和变址寄存器的用途记反);B 是漏选 Ⅰ(只记住了“扩展”,忘了前缀条件);C 是把 Ⅱ 当成对的(忘了 PC 在取指周期就已经加了指令长度)、漏选 Ⅲ(把基址寄存器和变址寄存器的用途记反)
Ⅰ. RISC 机器中只有 Load / Store 指令访问存储器
Ⅱ. x86 的 call 指令先把返回地址压栈,再跳转到被调函数的入口
Ⅲ. 带符号整数比较后的条件转移,依据的是 CF 标志
Ⅳ. 大端方式下,int 型变量的最低有效字节存放在最低地址
- A. 仅Ⅰ、Ⅱ
- B. 仅Ⅰ、Ⅲ
- C. 仅Ⅰ、Ⅱ、Ⅲ
- D. 仅Ⅰ、Ⅱ、Ⅳ
展开解析
考点3.4 CISC 与 RISC3.3 过程调用3.1 条件码2.7 大端与小端
A正确的是 Ⅰ、Ⅱ。
- Ⅰ 对其他指令的操作数都在寄存器里,这让指令格式规整、利于流水线。
- Ⅱ 对返回地址就是 call 的下一条指令的地址,ret 时弹出给 PC。
- Ⅲ 错带符号比较看 SF、OF、ZF(如 jl 看 SF ⊕ OF);CF 用于无符号比较(jb、ja)。
- Ⅳ 错大端是最高有效字节放在最低地址;题中说的是小端。
干扰项从哪来:B 是把 Ⅲ 当成对的(把有符号和无符号的转移条件记混)、漏选 Ⅱ(以为返回地址保存在固定寄存器里);C 是把 Ⅲ 当成对的(把有符号和无符号的转移条件记混);D 是把 Ⅳ 当成对的(把大端、小端记反)
计组(5)CPU(含中断与异常)
Ⅰ. 单周期 CPU 的时钟周期取决于执行时间最长的那条指令
Ⅱ. 微程序控制器中,每条机器指令对应一条微指令
Ⅲ. 字段直接编码方式中,相互排斥的微命令应安排在同一个字段里
Ⅳ. 取指周期完成的操作与正在执行的是哪一条指令无关
- A. 仅Ⅰ、Ⅲ
- B. 仅Ⅰ、Ⅲ、Ⅳ
- C. 仅Ⅱ、Ⅲ、Ⅳ
- D. Ⅰ、Ⅱ、Ⅲ、Ⅳ
展开解析
考点4.1 单周期 CPU4.3 微程序控制器4.3 编码方式4.1 指令周期
B正确的是 Ⅰ、Ⅲ、Ⅳ。
- Ⅰ 对所有指令都在一个周期内完成,周期必须容得下最慢的指令(通常是 load)。
- Ⅱ 错一条机器指令对应一段微程序,由若干条微指令组成。
- Ⅲ 对同一字段一次只能译出一个微命令,互斥的放在一起正好;可同时出现的必须分开。
- Ⅳ 对取指令、PC 加 1 对所有指令都一样,是公共操作。
干扰项从哪来:A 是漏选 Ⅳ(以为取指阶段已经根据操作码做了不同的事);C 是把 Ⅱ 当成对的(把“微程序”和“微指令”混为一谈)、漏选 Ⅰ(以为取各类指令的平均时间);D 是把 Ⅱ 当成对的(把“微程序”和“微指令”混为一谈)
Ⅰ. 采用转发(旁路)技术后,所有数据冒险都不会再引起流水线阻塞
Ⅱ. 指令 Cache 与数据 Cache 分离,可以减少结构冒险
Ⅲ. 流水线的段数越多,每条指令从取指到完成所用的时间越短
Ⅳ. 采用延迟转移时,延迟槽中的指令无论是否转移都要执行
- A. 仅Ⅱ
- B. 仅Ⅳ
- C. 仅Ⅱ、Ⅳ
- D. 仅Ⅰ、Ⅱ、Ⅳ
展开解析
Ⅰ. 中断响应时,关中断、保存断点(PC)由硬件完成(中断隐指令)
Ⅱ. 缺页异常处理完成后,返回到引起缺页的那条指令重新执行
Ⅲ. CPU 在每条指令执行结束时检查外部中断请求,DMA 请求也只能在指令执行结束时响应
Ⅳ. 通过修改中断屏蔽字,可以改变中断的响应优先级
- A. 仅Ⅰ、Ⅱ
- B. 仅Ⅰ、Ⅲ
- C. 仅Ⅰ、Ⅱ、Ⅲ
- D. 仅Ⅰ、Ⅱ、Ⅳ
展开解析
考点5.4 硬件与软件的分工5.3 异常的分类9.4 DMA 与中断5.5 屏蔽字
A正确的是 Ⅰ、Ⅱ。
- Ⅰ 对这几步必须一气呵成,不能靠可能被打断的软件来做。
- Ⅱ 对缺页属于故障,指令没执行完,必须重新执行;自陷才返回下一条。
- Ⅲ 错DMA 请求在每个机器周期(存取周期)结束时就可以响应,不必等整条指令结束。
- Ⅳ 错屏蔽字改变的是处理优先级(谁能打断谁);响应优先级由硬件排队电路决定,不能改。
干扰项从哪来:B 是把 Ⅲ 当成对的(把 DMA 请求和中断请求的响应时机混为一谈)、漏选 Ⅱ(把故障与自陷的返回地址记反);C 是把 Ⅲ 当成对的(把 DMA 请求和中断请求的响应时机混为一谈);D 是把 Ⅳ 当成对的(把响应优先级和处理优先级混淆)
计组(6)总线
Ⅰ. 总线带宽是指总线单位时间内能传输的最大数据量
Ⅱ. 地址线和数据线分时复用,可以提高总线的数据传输率
Ⅲ. 异步总线用应答(握手)方式通信,不需要统一的时钟信号
Ⅳ. 突发传送方式下,一个总线事务只传送一次地址,就可以连续传送多个数据
- A. 仅Ⅰ、Ⅲ
- B. 仅Ⅰ、Ⅳ
- C. 仅Ⅲ、Ⅳ
- D. 仅Ⅰ、Ⅲ、Ⅳ
Ⅰ. 链式查询方式对电路故障敏感,离总线控制器越远的设备优先级越低
Ⅱ. 独立请求方式的响应速度最快,但所需的控制线最多
Ⅲ. 同步总线中所有设备使用同一个时钟,不适合连接速度差别很大的设备
Ⅳ. 总线宽度是指总线上地址线的条数
- A. 仅Ⅰ、Ⅱ
- B. 仅Ⅰ、Ⅱ、Ⅲ
- C. 仅Ⅰ、Ⅱ、Ⅳ
- D. Ⅰ、Ⅱ、Ⅲ、Ⅳ
计组(7)输入 / 输出系统
Ⅰ. 统一编址方式下,CPU 用访存指令访问 I/O 端口
Ⅱ. 独立编址方式下,I/O 端口地址可以与主存单元地址相同
Ⅲ. I/O 接口中的状态端口和控制端口不能共用同一个地址
Ⅳ. 程序查询方式下,CPU 和外设可以并行工作
- A. 仅Ⅰ
- B. 仅Ⅱ
- C. 仅Ⅰ、Ⅱ
- D. 仅Ⅰ、Ⅱ、Ⅲ
展开解析
考点9.3 端口编址9.3 I/O 接口9.4 三种 I/O 方式
C正确的是 Ⅰ、Ⅱ。
- Ⅰ 对I/O 端口占用一部分主存地址空间,没有专门的 I/O 指令(2014-21)。
- Ⅱ 对两个地址空间相互独立,靠 IN / OUT 这类专门指令区分访问的是哪一边。
- Ⅲ 错状态端口只读、控制端口只写,读写方向不同,常共用一个端口地址。
- Ⅳ 错CPU 要反复查询状态、等外设就绪,期间不能做别的事,两者是串行的。
干扰项从哪来:A 是漏选 Ⅱ(以为地址数值相同就会冲突);B 是漏选 Ⅰ(把统一编址和独立编址的特点记反);D 是把 Ⅲ 当成对的(以为每个寄存器都必须有自己的地址)
Ⅰ. 程序中断方式下,每传送一个数据(字或字节)就要中断 CPU 一次
Ⅱ. DMA 方式下,数据直接在主存和外设之间传送,传送过程中不需要 CPU 执行程序
Ⅲ. DMA 传送结束时,DMA 控制器向 CPU 发出中断请求,由 CPU 做后处理
Ⅳ. 采用周期挪用方式时,DMA 与 CPU 同时请求访存,CPU 优先
- A. 仅Ⅰ、Ⅱ
- B. 仅Ⅰ、Ⅲ
- C. 仅Ⅱ、Ⅲ
- D. 仅Ⅰ、Ⅱ、Ⅲ
操作系统(1)概述与运行环境
Ⅰ. 并发是指两个或多个事件在同一时间间隔内发生
Ⅱ. 访管(陷入)指令是特权指令,只能在内核态执行
Ⅲ. 用户程序通过系统调用请求操作系统服务,系统调用的处理在内核态进行
Ⅳ. 分时系统追求的首要目标是提高系统吞吐量
- A. 仅Ⅰ、Ⅲ
- B. 仅Ⅰ、Ⅳ
- C. 仅Ⅰ、Ⅱ、Ⅲ
- D. 仅Ⅰ、Ⅲ、Ⅳ
展开解析
考点1.5 四个特征5.1 特权指令5.6 系统调用1.6 操作系统的分类
A正确的是 Ⅰ、Ⅲ。
- Ⅰ 对单 CPU 上宏观同时、微观交替;同一时刻发生的是并行。
- Ⅱ 错访管指令正是在用户态执行、用来主动进入内核的,它是非特权指令。
- Ⅲ 对用户态发出陷入指令,CPU 切到内核态执行系统调用处理程序。
- Ⅳ 错分时系统首先追求及时响应(交互性);提高吞吐量是批处理系统的目标。
干扰项从哪来:B 是把 Ⅳ 当成对的(把分时系统和批处理系统的目标记混)、漏选 Ⅲ(以为系统调用在用户态完成);C 是把 Ⅱ 当成对的(以为“进内核的指令”就是特权指令);D 是把 Ⅳ 当成对的(把分时系统和批处理系统的目标记混)
Ⅰ. 微内核结构中,文件系统等服务大多运行在用户态,频繁的模式切换会降低性能
Ⅱ. 开机引导时,BIOS 先从主引导记录(MBR)读入自检程序,再进行硬件自检
Ⅲ. 第一类虚拟机监控程序(VMM)直接运行在硬件上
Ⅳ. 时钟中断处理程序在内核态执行
- A. 仅Ⅰ、Ⅲ
- B. 仅Ⅰ、Ⅲ、Ⅳ
- C. 仅Ⅱ、Ⅲ、Ⅳ
- D. Ⅰ、Ⅱ、Ⅲ、Ⅳ
展开解析
考点1.7 内核结构1.7 BIOS 与 POST1.7 虚拟机5.2 中断和异常
B正确的是 Ⅰ、Ⅲ、Ⅳ。
- Ⅰ 对服务之间靠消息经内核转发,每次都要在用户态和内核态之间切换(2023-23)。
- Ⅱ 错自检(POST)程序固化在 ROM 的 BIOS 里,先自检,再读 MBR 中的引导程序。
- Ⅲ 对也叫裸金属型;第二类运行在宿主操作系统之上。
- Ⅳ 对中断处理程序都是内核的一部分,时钟中断还承担时间片计时等任务。
干扰项从哪来:A 是漏选 Ⅳ(以为只有系统调用才进内核);C 是把 Ⅱ 当成对的(把 POST 和读 MBR 的先后顺序颠倒)、漏选 Ⅰ(以为微内核又小又快);D 是把 Ⅱ 当成对的(把 POST 和读 MBR 的先后顺序颠倒)
操作系统(2)进程管理
Ⅰ. 引入线程后,进程是资源分配的基本单位,线程是处理机调度的基本单位
Ⅱ. 同一进程的各线程共享进程的地址空间,但各有自己的栈和寄存器上下文
Ⅲ. 进程可以由阻塞态直接转换为运行态
Ⅳ. 用户级线程之间的切换需要进入内核完成
- A. 仅Ⅰ
- B. 仅Ⅱ
- C. 仅Ⅰ、Ⅱ
- D. 仅Ⅰ、Ⅱ、Ⅲ
Ⅰ. 在处理中断的过程中,不能进行进程调度与切换
Ⅱ. 进程在普通临界区中时,不能被调度
Ⅲ. 时间片轮转调度中,时间片过大会使它退化为先来先服务
Ⅳ. 短作业优先算法的平均等待时间最短,而且不会使任何作业饥饿
- A. 仅Ⅰ
- B. 仅Ⅲ
- C. 仅Ⅰ、Ⅱ
- D. 仅Ⅰ、Ⅲ
展开解析
考点6.6 调度的时机6.7 时间片的大小6.7 各算法的考点
D正确的是 Ⅰ、Ⅲ。
- Ⅰ 对中断处理过程复杂,与硬件密切相关,处理期间不能调度(要等处理完再调度)。
- Ⅱ 错普通临界区(如访问打印机)里可以被调度下 CPU,其他进程想进同一临界区会被互斥机制挡住;不能调度的是内核程序的临界区。
- Ⅲ 对每个进程都能在一个时间片内运行完,轮转就失去了意义。
- Ⅳ 错平均等待时间最短是对的,但长作业可能一直被后来的短作业插队,产生饥饿。
干扰项从哪来:A 是漏选 Ⅲ(以为时间片越大响应越快);B 是漏选 Ⅰ(以为任何时候都可以调度);C 是把 Ⅱ 当成对的(把“普通临界区”和“内核临界区”混为一谈)、漏选 Ⅲ(以为时间片越大响应越快)
Ⅰ. Peterson 算法满足空闲让进、忙则等待和有限等待,但不满足让权等待
Ⅱ. 记录型信号量的值为 −2 时,表示有 2 个进程在该信号量的等待队列中
Ⅲ. 用 TSL(TestAndSet)指令实现互斥时,进不了临界区的进程不会忙等
Ⅳ. 同一时刻只允许一个进程在管程内执行
- A. 仅Ⅰ、Ⅱ、Ⅳ
- B. 仅Ⅰ、Ⅲ、Ⅳ
- C. 仅Ⅱ、Ⅲ、Ⅳ
- D. Ⅰ、Ⅱ、Ⅲ、Ⅳ
展开解析
考点7.2 Peterson 算法7.3 记录型信号量7.2 硬件方法7.5 管程
A正确的是 Ⅰ、Ⅱ、Ⅳ。
- Ⅰ 对进不去时在 while 里空转(忙等),不释放处理机。
- Ⅱ 对value < 0 时,其绝对值就是等待的进程数。
- Ⅲ 错TSL 只保证“测试并设置”的原子性,进不去的进程仍在循环里反复测试,属于忙等,不满足让权等待。
- Ⅳ 对管程的互斥由编译器 / 系统保证,程序员只需用条件变量处理同步。
干扰项从哪来:B 是把 Ⅲ 当成对的(以为硬件指令法就不会忙等)、漏选 Ⅱ(以为负值没有意义);C 是把 Ⅲ 当成对的(以为硬件指令法就不会忙等)、漏选 Ⅰ(以为 Peterson 满足全部四条准则);D 是把 Ⅲ 当成对的(以为硬件指令法就不会忙等)
Ⅰ. 系统处于不安全状态时,一定会发生死锁
Ⅱ. 规定所有进程按资源编号递增的顺序申请资源,可以破坏循环等待条件
Ⅲ. 资源分配图中出现环路,系统就一定发生了死锁
Ⅳ. 银行家算法中,系统处于安全状态时安全序列可能不唯一
- A. 仅Ⅱ
- B. 仅Ⅱ、Ⅳ
- C. 仅Ⅰ、Ⅱ、Ⅳ
- D. 仅Ⅱ、Ⅲ、Ⅳ
操作系统(3)内存管理
Ⅰ. 动态分区分配会产生外部碎片,分页存储管理只可能产生内部碎片
Ⅱ. 分段存储管理的逻辑地址空间是二维的
Ⅲ. 多级页表可以减少页表占用的连续内存空间,也能减少地址变换时的访存次数
Ⅳ. 实现紧凑(拼接)需要动态重定位的支持
- A. 仅Ⅰ、Ⅱ
- B. 仅Ⅰ、Ⅳ
- C. 仅Ⅰ、Ⅱ、Ⅳ
- D. Ⅰ、Ⅱ、Ⅲ、Ⅳ
Ⅰ. FIFO 页面置换算法可能出现 Belady 异常,LRU 算法不会
Ⅱ. 系统发生抖动时,增大磁盘对换区的容量可以有效缓解
Ⅲ. 采用固定分配策略时,不能采用全局置换
Ⅳ. 一条指令执行期间可能发生多次缺页
- A. 仅Ⅰ、Ⅲ
- B. 仅Ⅰ、Ⅳ
- C. 仅Ⅲ、Ⅳ
- D. 仅Ⅰ、Ⅲ、Ⅳ
操作系统(4)文件管理
Ⅰ. 打开文件时,操作系统把文件的 FCB(索引结点)读入内存,之后读写该文件不必再按路径名查目录
Ⅱ. 文件描述符是进程打开文件表中表项的索引
Ⅲ. 隐式链接分配支持对文件的随机访问
Ⅳ. 连续分配的文件,访问第 i 块时不需要先读前面的块
- A. 仅Ⅰ、Ⅱ、Ⅳ
- B. 仅Ⅰ、Ⅲ、Ⅳ
- C. 仅Ⅱ、Ⅲ、Ⅳ
- D. Ⅰ、Ⅱ、Ⅲ、Ⅳ
展开解析
考点10.4 打开文件10.4 打开文件表10.6 链接分配10.6 连续分配
A正确的是 Ⅰ、Ⅱ、Ⅳ。
- Ⅰ 对open 返回文件描述符,以后的 read / write 用它直接找到内存中的打开文件表项。
- Ⅱ 对每个进程有自己的打开文件表,fd 就是它的下标(2020-23 的教材两级表口径)。
- Ⅲ 错要访问第 i 块,必须从第一块开始顺着指针读,只能顺序访问。
- Ⅳ 对起始块号 + i 就是物理块号,可以直接定位。
干扰项从哪来:B 是把 Ⅲ 当成对的(把隐式链接和显式链接(FAT)的特点混淆)、漏选 Ⅱ(以为文件描述符是 inode 号);C 是把 Ⅲ 当成对的(把隐式链接和显式链接(FAT)的特点混淆)、漏选 Ⅰ(以为每次读写都要重新按路径查找);D 是把 Ⅲ 当成对的(把隐式链接和显式链接(FAT)的特点混淆)
Ⅰ. 硬链接与原文件共享同一个索引结点,删除原文件名后仍能通过硬链接访问文件
Ⅱ. 软链接(符号链接)文件中存放的是被链接文件的路径名
Ⅲ. 硬链接可以跨越不同的文件系统
Ⅳ. 位示图的大小与磁盘上已经被占用的块数有关
- A. 仅Ⅰ、Ⅱ
- B. 仅Ⅰ、Ⅲ
- C. 仅Ⅰ、Ⅱ、Ⅲ
- D. 仅Ⅰ、Ⅱ、Ⅳ
展开解析
A正确的是 Ⅰ、Ⅱ。
- Ⅰ 对删除只是让链接计数减 1,计数不为 0 时文件不会被真正删除。
- Ⅱ 对访问软链接时要按这个路径再查一次;原文件删除后软链接就失效了。
- Ⅲ 错硬链接靠 inode 号连接,inode 号只在本文件系统内有意义;跨文件系统只能用软链接。
- Ⅳ 错位示图为每一个盘块设 1 位,大小只取决于盘块总数。
干扰项从哪来:B 是把 Ⅲ 当成对的(把硬链接和软链接的限制记反)、漏选 Ⅱ(以为软链接也指向同一个 inode);C 是把 Ⅲ 当成对的(把硬链接和软链接的限制记反);D 是把 Ⅳ 当成对的(以为只为已用块或空闲块记位)
操作系统(5)输入 / 输出管理
Ⅰ. 设备独立性是指用户程序使用逻辑设备名,由系统完成逻辑设备到物理设备的映射
Ⅱ. 设备驱动程序与具体设备的硬件特性密切相关
Ⅲ. 逻辑设备名到物理设备名的映射由设备驱动程序完成
Ⅳ. 采用双缓冲时,CPU 对一个缓冲区的处理与向另一个缓冲区输入数据可以并行
- A. 仅Ⅰ、Ⅱ
- B. 仅Ⅰ、Ⅳ
- C. 仅Ⅰ、Ⅱ、Ⅳ
- D. Ⅰ、Ⅱ、Ⅲ、Ⅳ
展开解析
考点9.5 设备独立性9.5 I/O 软件层次9.8 逻辑设备表9.7 双缓冲
C正确的是 Ⅰ、Ⅱ、Ⅳ。
- Ⅰ 对程序不依赖具体设备,换设备不用改程序。
- Ⅱ 对驱动程序要直接操作设备控制器的寄存器,一般由设备厂商提供。
- Ⅲ 错映射由设备独立性软件完成(查逻辑设备表);驱动程序只管具体设备的操作。
- Ⅳ 对两个缓冲区交替使用,这是双缓冲比单缓冲快的原因。
干扰项从哪来:A 是漏选 Ⅳ(以为双缓冲只是容量加倍);B 是漏选 Ⅱ(以为驱动程序与设备无关);D 是把 Ⅲ 当成对的(把设备独立性软件和驱动程序的职责记混)
Ⅰ. SSTF 磁盘调度算法可能使某些请求长期得不到服务
Ⅱ. C-SCAN 算法在磁头返回时也为沿途的请求服务
Ⅲ. 磁盘的平均旋转延迟是旋转一周所需时间的一半
Ⅳ. 对固态硬盘进行磁盘调度,可以显著减少寻道时间
- A. 仅Ⅰ
- B. 仅Ⅲ
- C. 仅Ⅰ、Ⅱ
- D. 仅Ⅰ、Ⅲ
展开解析
考点10.2 磁盘调度10.2 SCAN 与 C-SCAN10.1 磁盘访问时间10.1 SSD
D正确的是 Ⅰ、Ⅲ。
- Ⅰ 对磁头总在附近来回服务新到的请求,远处的请求可能“饥饿”。
- Ⅱ 错C-SCAN 返回时直接回到起点、不服务;返回时也服务的是 SCAN。
- Ⅲ 对扇区转到磁头下的等待时间在 0 ~ 一圈之间,平均半圈。
- Ⅳ 错SSD 没有机械寻道,调度意义不大,一般 FCFS 即可(2010-45 第 3 问)。
干扰项从哪来:A 是漏选 Ⅲ(把平均旋转延迟算成转一整圈);B 是漏选 Ⅰ(以为只有 FCFS 才公平);C 是把 Ⅱ 当成对的(把 SCAN 和 C-SCAN 的回程记反)、漏选 Ⅲ(把平均旋转延迟算成转一整圈)
13.3计算题满分专练:固定步骤、单位口径、变式与常见错法
计组和操作系统的计算题,“思路对、结果错”是最常见的丢分方式。这一节把 19 类计算题各压成一个固定步骤,写明单位口径(K、M、G 是 210 还是 103,B 还是 bit,时钟周期还是秒),列出最常见的错法,再给两道变式。每道变式的四个选项里,三个错误答案都是按某种常见错法真实算出来的,解析会写清楚是哪一种——做错了就对照“错法”找到自己的问题。所有数字由 verify/08/p13/calc_cards.py 计算并断言(调度、银行家、置换、磁盘调度用模拟器,与正文 6.7、10.2 的例题核对一致)。
| 量 | 口径 |
|---|---|
| 存储容量 | K = 210、M = 220、G = 230(主存、Cache、页、盘块、文件大小) |
| 速率、频率 | K = 103、M = 106、G = 109(主频、带宽、数据传输率;题目另有说明时按题目) |
| B 与 b | B = 字节、b = 位;总线宽度 32 位 = 4B;位示图按“位”计 |
| 时间 | 1s = 103ms = 106μs = 109ns = 1012ps;主频 f 的时钟周期 = 1/f |
① 整数的机器数与 C 语言类型转换
- 写出原值的 n 位补码(负数 = 2ⁿ − |x|)。
- 判断转换类型:同宽有↔无符号位串不变、只换解释;短→长按原类型扩展(有符号补符号位、无符号补 0);长→短截断保留低位。
- 按目标类型重新解释位串,写出十进制值。
单位口径:位数看类型:char 8、short 16、int 32、long long 64;十六进制一位 = 4 位二进制。
常见错法:短→长时按目标类型扩展(应按源类型:unsigned short → int 补 0);以为有符号转无符号会“取绝对值”;十六进制写错位数(FF9C 和 FFFFFF9C)。
short x = -300; unsigned short y = x; int z = y;(short 为 16 位,int 为 32 位)执行后,z 的值是( )。- A. −300
- B. 65236
- C. 4294966996
- D. 300
展开解析
B两步:−300 → unsigned short 位串不变;unsigned short → int 是“无符号短→长”,补 0。
- −300 的 16 位补码:65536 − 300 = 65236 = FED4H。
- y 是 unsigned short,位串不变,值为 65236。
- z = y:无符号数扩展为 32 位,高位补 0 → 0000 FED4H = 65236(gcc 实测)。
- A 错−300:按 z 的类型(有符号)做了符号扩展,得到 FFFF FED4H;但扩展方式由源类型 unsigned short 决定,应补 0。
- B 对65236。
- C 错4294966996 = 2³² − 300:把 −300 直接当成 32 位无符号数,忽略了中间先截成 16 位的 unsigned short。
- D 错300:以为有符号转无符号是“去掉负号”,实际是位串不变、按无符号重新解释。
char c = 0x9C; int i = c;(char 为 8 位有符号类型,int 为 32 位)。执行后 i 的机器数(十六进制)是( )。- A. 0000 009CH
- B. 8000 009CH
- C. FFFF FF9CH
- D. 0000 FF9CH
展开解析
考点2.2 符号扩展
C有符号短→长:用符号位填满高位。
- 9CH = 1001 1100B,最高位 1 → c 是负数(−100)。
- 扩展为 32 位,高 24 位全填 1 → FFFF FF9CH(仍是 −100,gcc 实测)。
- A 错0000 009CH 是补 0 的零扩展(unsigned char 才这样),值变成了 156。
- B 错8000 009CH:把符号位挪到最高位、其余补 0,这是原码的扩展思路,补码不能这样做。
- C 对FFFF FF9CH = −100。
- D 错0000 FF9CH:只扩展到了 16 位就停了,int 是 32 位。
② IEEE 754 编码、解码
- 编码:十进制 → 二进制 → 规格化成 1.M × 2e。
- S(正 0 负 1)、E = e + 127(双精度 + 1023)写成 8 位、M 写小数点后的部分(不含隐藏的 1),补够 23 位。
- 按 S | E | M 拼接,四位一组写十六进制。解码反过来:先看 E 是否全 0 / 全 1。
单位口径:单精度 1 + 8 + 23、偏置 127;双精度 1 + 11 + 52、偏置 1023;阶码全 0 时指数按 −126、无隐藏位。
常见错法:偏置写成 128(那是教材一般移码);把隐藏的 1 写进尾数字段;符号位、阶码的十六进制拼接错位。
- A. C1DA 0000H
- B. C16D 0000H
- C. 415A 0000H
- D. C15A 0000H
展开解析
考点2.6 编码四步
D1101.101B = 1.101101B × 2³。
- 13.625 = 1101.101B = 1.101101B × 2³。
- S = 1;E = 3 + 127 = 130 = 1000 0010B;M = 1011 0100 0…0(23 位)。
- 1 1000 0010 1011 0100 0…0 → 1100 0001 0101 1010 0…0 = C15A 0000H(Python struct 核对)。
- A 错C1DA 0000H:阶码用了 3 + 128 = 131,偏置记成了 128。
- B 错C16D 0000H:把隐藏的 1 也写进了尾数字段(M 写成 1101101…)。
- C 错415A 0000H:符号位写成 0,这是 +13.625。
- D 对C15A 0000H。
int i = (int)x; 后 i 的值为( )。- A. −90
- B. −45
- C. −26
- D. 90
展开解析
考点2.6 解码
A拆 S、E、M,指数 = E − 127,尾数前补隐藏的 1。
- C2B4 0000H = 1 | 1000 0101 | 011 0100 0…0。
- S = 1;E = 133,指数 = 133 − 127 = 6;1.M = 1.0110100B = 1.40625。
- x = −1.40625 × 2⁶ = −90.0,转 int 得 −90。
- A 对−90。
- B 错−45:指数按 133 − 128 = 5 算,偏置用错。
- C 错−26 = −0.40625 × 64:忘了补隐藏的 1。
- D 错90:机器数最高位 C 的二进制是 1100,符号位 1 表示负数,看漏了。
③ 加减运算与四个标志位
- 减法先变加法:x − y = x + ȳ + 1(Sub = 1)。
- 在 n 位加法器上做二进制加法,记下最高位进位 Cn 和次高位进位 Cn−1。
- OF = Cn ⊕ Cn−1;SF = 结果最高位;ZF = 结果是否全 0;CF = Cn ⊕ Sub。
单位口径:标志只看 n 位结果和进位,不看“数学上应有的结果”;CF 管无符号,OF 管有符号。
常见错法:减法时直接把加法器的进位当 CF(应异或 Sub);用真值的符号当 SF;把 OF 当成某一位的进位。
- A. 1, 0, 0, 1
- B. 0, 0, 0, 1
- C. 1, 1, 0, 1
- D. 1, 0, 0, 0
展开解析
A两个负数(按有符号看)相加得到正数 → 溢出。
- 9AH + B5H = 14FH,8 位结果 4FH,C₈ = 1。
- 低 7 位:1AH + 35H = 4FH < 80H → C₇ = 0。
- OF = 1 ⊕ 0 = 1;SF = 0(4FH 最高位);ZF = 0;CF = 1 ⊕ 0 = 1(程序模拟核对)。
- A 对1, 0, 0, 1。
- B 错OF = 0:把 OF 当成了次高位进位 C₇;OF 要 C₈ ⊕ C₇。
- C 错SF = 1:按“两个负数相加应为负”来写 SF;SF 只看结果 4FH 的最高位,是 0。
- D 错CF = 0:把 CF 取成了次高位进位;无符号 154 + 181 = 335 > 255,确有进位。
- A. 1, 1, 0, 0
- B. 0, 1, 0, 1
- C. 1, 0, 0, 1
- D. 1, 1, 0, 1
展开解析
Dx − y = x + ȳ + 1:50H + 3FH + 1。
- ȳ = 3FH,50H + 3FH + 1 = 90H,C₈ = 0。
- 低 7 位 50H + 3FH + 1 = 90H ≥ 80H → C₇ = 1。
- OF = 0 ⊕ 1 = 1(有符号 80 − (−64) = 144 > 127);SF = 1;ZF = 0;CF = C₈ ⊕ Sub = 0 ⊕ 1 = 1(无符号 80 < 192,有借位)。
- A 错CF = 0:直接拿加法器的进位输出 C₈ = 0 当 CF,忘了减法时 CF = C₈ ⊕ 1。
- B 错OF = 0:以为“正数减负数不会溢出”;减法要看成同号相加,80 + 64 超出 127。
- C 错SF = 0:按真值 +144 的符号写 SF;结果 90H 的最高位是 1。
- D 对1, 1, 0, 1。
④ CPI、MIPS 与执行时间
- 平均 CPI = Σ(各类指令占比 × 该类 CPI)——一定要加权。
- CPU 时间 = 指令条数 × CPI × 时钟周期 = 指令条数 × CPI ÷ 主频。
- MIPS = 主频 ÷ (CPI × 10⁶);比较两台机器用执行时间之比。
单位口径:主频的 G、M 是 10⁹、10⁶;时钟周期 = 1 ÷ 主频(2GHz → 0.5ns)。
常见错法:CPI 求简单平均;把主频当成时钟周期;只比主频或只比 CPI。
- A. 0.441s
- B. 0.49s
- C. 0.21s
- D. 1.764s
展开解析
考点1.8 性能指标
A先加权求平均 CPI,再乘指令数、除主频。
- CPI = 0.5 × 1 + 0.3 × 4 + 0.2 × 2 = 2.1。
- 时间 = 4.2 × 10⁸ × 2.1 ÷ (2 × 10⁹) = 0.441s。
- A 对0.441s。
- B 错0.49s:CPI 取 (1 + 4 + 2) ÷ 3 ≈ 2.33 的简单平均,没按占比加权。
- C 错0.21s:把 CPI 当成 1,只算了指令数 ÷ 主频。
- D 错1.764s:把 2GHz 当成时钟周期 2ns 来乘;时钟周期应为 0.5ns。
- A. M2 比 M1 快,速度是 M1 的 1.33 倍
- B. M1 比 M2 快,速度是 M2 的 1.25 倍
- C. M1 比 M2 快,速度是 M2 的 1.67 倍
- D. M2 比 M1 快,速度是 M1 的 1.25 倍
展开解析
考点1.8 性能指标
B比每条指令的平均时间 = CPI ÷ 主频。
- M1:1.5 ÷ 1.5GHz = 1ns / 条;M2:2.5 ÷ 2GHz = 1.25ns / 条。
- M1 更快,速度之比 = 1.25 ÷ 1 = 1.25。
- A 错1.33 = 2 ÷ 1.5:只比了主频,忽略了 M2 的 CPI 更大。
- B 对M1 是 M2 的 1.25 倍。
- C 错1.67 = 2.5 ÷ 1.5:只比了 CPI,忽略了主频不同。
- D 错数值 1.25 对了,但结论反了:时间短的是 M1。
⑤ 指令格式与扩展操作码
- 从最长地址码的指令开始:本级可用编码数 = 2本级操作码位数(第一级)或“上一级剩下的编码数 × 2扩展位数”。
- 本级用掉若干条,剩下的编码留作扩展前缀。
- 逐级做到最后一级;“最多”就是不再预留。
单位口径:字段位数:寄存器 2ⁿ 个 → n 位;立即数 n 位补码范围 −2ⁿ⁻¹ ~ 2ⁿ⁻¹ − 1。
常见错法:用剩余编码时又减了上一级的条数;只算到编码数没乘 2^扩展位数;立即数范围按无符号或按原码算。
- A. 18 条
- B. 288 条
- C. 80 条
- D. 768 条
展开解析
B三地址:4 位操作码;二地址:8 位;单地址:12 位。
- 三地址:2⁴ = 16 个编码,用 13,剩 3 个作前缀。
- 二地址:3 × 2⁴ = 48 个,用 30,剩 18 个。
- 单地址:18 × 2⁴ = 288 条(程序核对)。
- A 错18 只是二地址剩下的编码数,还要每个扩展 4 位、乘 16。
- B 对288。
- C 错80 = (48 − 13 − 30) × 16:又把三地址的 13 条从二地址的编码里减了一次;那 13 条已在上一级扣过。
- D 错768 = 3 × 16 × 16:忘了减去二地址用掉的 30 条。
- A. −32768 ~ 32767
- B. 0 ~ 32767
- C. −16384 ~ 16383
- D. −16383 ~ 16383
⑥ 寻址方式求有效地址
- 先认方式:立即 / 直接 / 间接 / 寄存器(间接)/ 基址 / 变址 / 相对 / 堆栈。
- 写公式:相对 EA = (PC) + A,PC 已指向下一条指令;变址 EA = (IX) + A;“先变址后间址” EA = ((IX) + A);“先间址后变址” EA = (A) + (IX)。
- 按编址单位和指令长度算 PC 的更新量;偏移量按补码读(注意符号)。
单位口径:按字节编址时 PC 每次加“指令的字节数”;偏移量是补码,E8H = −24。
常见错法:用本条指令的地址当 PC;偏移量当成无符号数;把有效地址当成操作数,或漏一次间址。
- A. 1FE8H
- B. 20EAH
- C. 1FE9H
- D. 1FEAH
展开解析
考点3.2 相对寻址
D目标 = 下一条指令的地址 + 偏移量。
- 取指后 PC = 2000H + 2 = 2002H。
- E8H 是补码,值为 −18H(−24)。
- 目标 = 2002H − 18H = 1FEAH。
- A 错1FE8H = 2000H − 18H:用本条指令的地址当基准,忘了 PC 已经加 2。
- B 错20EAH = 2002H + E8H:把偏移量当成无符号数 232。
- C 错1FE9H = 2001H − 18H:PC 只加了 1,忘了指令长 2 字节、按字节编址。
- D 对1FEAH。
- A. 0042H
- B. 2000H
- C. 3A5CH
- D. 0FFFH
展开解析
C先变址:(IX) + A 得到一个地址;再间址:这个地址单元里存的才是有效地址。
- (IX) + A = 1200H + 0034H = 1234H。
- 间址:EA = (1234H) = 2000H。
- 操作数 = (2000H) = 3A5CH。
- A 错0042H 是“先间址后变址”:EA = (0034H) + 1200H = 2700H,顺序做反了。
- B 错2000H 是有效地址,不是操作数;少读了最后一次主存。
- C 对3A5CH。
- D 错0FFFH = ((0034H)):只做了间址,忘了先加变址寄存器。
⑦ 流水线时间、吞吐率与 CPI
- 时钟周期 = 最慢段的时间 + 段间寄存器延迟。
- 无阻塞时 n 条指令用 (k + n − 1) 个周期;有阻塞就逐条画时空图或用“1 + 平均阻塞周期”。
- 吞吐率 = n ÷ 总时间;加速比 = 不流水的时间 ÷ 流水的时间。
单位口径:ps、ns 换算:1ns = 1000ps;吞吐率常写成“条 / 秒”。
常见错法:周期取各段平均或漏了寄存器延迟;漏了装满流水线的 k − 1 拍;阻塞次数按全部指令而非实际发生的那部分算。
- A. 20.4ns
- B. 24.48ns
- C. 80ns
- D. 24.0ns
展开解析
B周期由最慢段决定,再加寄存器延迟。
- 时钟周期 = 100 + 20 = 120ps。
- 周期数 = 5 + 200 − 1 = 204。
- 时间 = 204 × 120ps = 24480ps = 24.48ns。
- A 错20.4ns = 204 × 100ps:漏了 20ps 的段间寄存器延迟。
- B 对24.48ns。
- C 错80ns = 200 × 400ps:按不流水执行(每条依次走完 5 段)算。
- D 错24.0ns = 200 × 120ps:漏了装满流水线的前 4 个周期。
- A. 1.75
- B. 1.72
- C. 1.57
- D. 1.42
展开解析
CCPI = 1 + Σ(事件频率 × 每次阻塞周期)。
- load-use:0.3 × 0.5 × 1 = 0.15。
- 转移:0.2 × 0.7 × 3 = 0.42。
- CPI = 1 + 0.15 + 0.42 = 1.57。
- A 错1.75:所有转移指令都按阻塞 3 拍算(0.2 × 3 = 0.6),忽略了 30% 不转移的没有阻塞。
- B 错1.72:所有 load 都按阻塞 1 拍算(0.3),忽略了只有一半紧跟使用。
- C 对1.57。
- D 错1.42:漏了 load-use 阻塞,以为“有转发就不阻塞”。
⑧ Cache:地址划分、总容量、平均访问时间
- 块内地址 = log₂(块大小);组数 = 行数 ÷ 路数,组号 = log₂(组数)(全相联没有);标记 = 地址位数 − 组号 − 块内。
- 每行总位数 = 数据 + 标记 + 有效位 [+ 脏位(写回)] [+ 替换位(LRU:log₂ 路数)];总容量 = 行数 × 每行位数。
- 命中率按“每块第一次访问缺失”数;平均时间注意题目给的是“先查 Cache 再访存”还是“同时访问”。
单位口径:Cache 容量的 K = 2¹⁰;按字节编址时块内地址按字节算。
常见错法:组号按行数算(当成直接映射);漏了脏位或 LRU 位;平均时间的两种口径混用。
- A. 531Kb
- B. 532Kb
- C. 512Kb
- D. 534Kb
展开解析
D先切地址求标记位数,再数每行的附加位。
- 块内 6 位;行数 64KB ÷ 64B = 1024,组数 1024 ÷ 4 = 256 → 组号 8 位;标记 = 32 − 8 − 6 = 18 位。
- 每行:512 位数据 + 18 位标记 + 1 位有效 + 1 位脏 + 2 位 LRU(4 路 → log₂4)= 534 位。
- 总容量 = 1024 × 534 = 546816 位 = 534Kb(程序核对)。
- A 错531Kb = 1024 × (512 + 18 + 1):漏了写回法需要的脏位和 LRU 位。
- B 错532Kb:标记算成 16 位——把 1024 行都当成“组”,用直接映射的 10 位行号划分地址。
- C 错512Kb 只算了数据区(64KB = 512Kb),没算任何附加位。
- D 对534Kb。
- A. 8.25ns
- B. 8.0ns
- C. 14.5ns
- D. 6.25ns
展开解析
A每块 8 个 int:第一次缺失、后 7 次命中。
- 命中率 = 7 / 8 = 87.5%。
- 每次都要访问 Cache 2ns;缺失的 12.5% 再加主存 50ns。
- 平均 = 2 + 0.125 × 50 = 8.25ns。
- A 对8.25ns。
- B 错8.0ns = 0.875 × 2 + 0.125 × 50:按“Cache 与主存同时访问”的口径算,和题目说的“先调块再读 Cache”不符。
- C 错14.5ns:把每块算成 4 个元素(命中率 75%),块大小或 int 大小用错。
- D 错6.25ns = 0.125 × 50:只算了缺失的代价,漏了每次都要访问 Cache 的 2ns。
⑨ 主存扩展与片选
- 芯片数 = (目标容量 × 目标字长) ÷ (芯片容量 × 芯片字长);先位扩展凑够字长,再字扩展凑够单元数。
- 片内地址 = log₂(芯片单元数),接低位地址线;剩下的高位接译码器产生片选。
- 某芯片的地址范围:片选位固定、片内地址从全 0 到全 1。
单位口径:4K = 2¹² = 1000H;8K = 2000H;16K = 4000H。
常见错法:只做字扩展不做位扩展;片内地址位数算错导致范围翻倍;译码器输入接的地址线位置看错。
- A. 4 片,3000H ~ 3FFFH
- B. 8 片,2000H ~ 3FFFH
- C. 8 片,3000H ~ 3FFFH
- D. 8 片,3000H ~ 33FFH
展开解析
考点8.2 字位扩展
C2 片并成 8 位一组(位扩展),4 组凑 16K(字扩展)。
- 芯片数 = (16K × 8) ÷ (4K × 4) = 8 片,每 2 片一组、共 4 组。
- 每组 4K = 1000H 个单元:0000H ~ 0FFFH、1000H ~ 1FFFH、2000H ~ 2FFFH、3000H ~ 3FFFH。
- 3A5CH 落在 3000H ~ 3FFFH。
- A 错4 片:只做了字扩展(16K ÷ 4K),忘了 4 位芯片要 2 片并成 8 位。
- B 错2000H ~ 3FFFH 是 8K 的范围:片内地址按 13 位算了,每组其实只有 4K(12 位)。
- C 对8 片,3000H ~ 3FFFH。
- D 错3000H ~ 33FFH 只有 400H = 1K 个单元:把 4K 换算成了 400H。
- A. A000H ~ BFFFH
- B. 5000H ~ 5FFFH
- C. A000H ~ AFFFH
- D. 0A00H ~ 0BFFH
展开解析
AY5 → A15 A14 A13 = 101,片内 13 位从全 0 到全 1。
- 8K = 2¹³,片内地址 A12 ~ A0。
- Y5:A15 A14 A13 = 101。
- 最小 1010 0000 0000 0000B = A000H,最大 1011 1111 1111 1111B = BFFFH。
- A 对A000H ~ BFFFH。
- B 错5000H ~ 5FFFH:把 101 放到了 A14 ~ A12、片内只按 12 位算,相当于译码器接错了位置。
- C 错A000H ~ AFFFH 只有 4K:片内地址按 12 位算,漏了 A12。
- D 错0A00H ~ 0BFFH:把 101 放到了 A11 ~ A9,译码位与片内地址位数都不对。
⑩ 中断方式、DMA 方式的 CPU 占用率
- 每秒发生几次:中断方式 = 设备速率 ÷ 每次中断传的数据量;DMA = 设备速率 ÷ 每块大小。
- 每次花多少时钟周期:中断 = 响应 + 处理;DMA = 预处理 + 后处理。
- 占用率 = 每秒次数 × 每次周期数 ÷ 主频。
单位口径:设备速率的 KB/s、MB/s 一般按 10³、10⁶(题目说明除外);数据宽度 32 位 = 4B。
常见错法:每次中断传的字节数看错;MB 按 2²⁰ 算;DMA 只算预处理或只算后处理。
- A. 50%
- B. 13.1%
- C. 25%
- D. 12.5%
展开解析
D每秒中断次数 × 每次周期数 ÷ 主频。
- 每秒中断次数 = 0.5 × 10⁶B/s ÷ 4B = 1.25 × 10⁵ 次。
- 每秒用于中断的周期 = 1.25 × 10⁵ × 500 = 6.25 × 10⁷。
- 占比 = 6.25 × 10⁷ ÷ (5 × 10⁸) = 12.5%。
- A 错50%:以为每次中断只传 1 字节,中断次数多算了 4 倍。
- B 错13.1%:把 0.5MB/s 按 0.5 × 2²⁰ 算;速率的 M 取 10⁶。
- C 错25%:把 32 位当成 2 字节。
- D 对12.5%。
- A. 0.4%
- B. 0.16%
- C. 0.24%
- D. 4%
展开解析
考点9.4 DMA
ADMA 每块只麻烦 CPU 两次:传送前的预处理、传送完的后处理。
- 每秒 DMA 次数 = 2 × 10⁶ ÷ 1000 = 2000 次。
- 每次占 600 + 400 = 1000 个周期,每秒 2 × 10⁶ 个周期。
- 占比 = 2 × 10⁶ ÷ (5 × 10⁸) = 0.4%。
- A 对0.4%。
- B 错0.16%:只算了后处理(结束时的中断),漏了预处理。
- C 错0.24%:只算了预处理,漏了后处理。
- D 错4%:把 500MHz 换成了 5 × 10⁷Hz,少了一个 0。
⑪ 总线带宽与突发传输
- 带宽 = 每次传送的数据量 × 每秒传送次数;每秒传送次数 = 时钟频率 ÷ 每次传送占用的时钟周期数(DDR 等一个周期传两次要乘 2)。
- 突发传输的“平均”传输率 = 一次事务传送的总字节数 ÷ 整个事务的时间(地址、等待周期都要算)。
单位口径:速率的 M = 10⁶;总线宽度 32 位 = 4B。
常见错法:把峰值带宽当成突发的平均传输率;漏了“每次传送占几个时钟周期”;MB/s 按 2²⁰。
- A. 800MB/s
- B. 400MB/s
- C. 100MB/s
- D. 381.5MB/s
展开解析
考点9.1 总线带宽
B每秒传送次数 = 200M ÷ 2。
- 每秒传送 200 × 10⁶ ÷ 2 = 10⁸ 次。
- 每次 4B → 带宽 = 400MB/s。
- A 错800MB/s:按每个时钟周期传一次算,漏了“每个总线周期占 2 个时钟周期”。
- B 对400MB/s。
- C 错100MB/s:把 32 位当成 1 字节。
- D 错381.5MB/s:用 2²⁰ 去换算 MB;速率的 M 是 10⁶。
- A. 800MB/s
- B. 753MB/s
- C. 640MB/s
- D. 160MB/s
展开解析
考点9.2 突发传输
C整个事务的时间要从送地址算起。
- 事务共 20 个周期 × 5ns = 100ns。
- 传送 16 × 4B = 64B。
- 平均传输率 = 64B ÷ 100ns = 640MB/s。
- A 错800MB/s 是峰值带宽(每周期 4B),没算送地址和等待的 4 个周期。
- B 错753MB/s ≈ 64B ÷ 85ns:漏了 3 个等待周期,只算了地址 + 数据 17 个周期。
- C 对640MB/s。
- D 错160MB/s = 16B ÷ 100ns:以为每次只传 1 字节。
⑫ 磁盘访问时间与地址换算
- 访问时间 = 平均寻道时间 + 平均旋转延迟(转半圈)+ 传输时间(转过要读的扇区)。
- 转一圈的时间 = 60 ÷ 转速(rpm)秒;读 1 个扇区 = 一圈时间 ÷ 每道扇区数。
- 地址换算:编号顺序是“同一磁道 → 同一柱面换磁头 → 换柱面”:柱面 = 号 ÷ 每柱面单元数,余数 ÷ 每磁道单元数 = 磁头,再余 = 道内序号。
单位口径:rpm 是每分钟转数;平均旋转延迟是半圈;1 簇 = 几个扇区要先换算。
常见错法:旋转延迟按一整圈;把簇号当扇区号;编号顺序记反(先换柱面)。
- A. 14.02ms
- B. 6.02ms
- C. 18ms
- D. 10.02ms
展开解析
D一圈 = 60 ÷ 7500 s = 8ms。
- 平均旋转延迟 = 8ms ÷ 2 = 4ms。
- 传输 1 个扇区 = 8ms ÷ 400 = 0.02ms。
- 总计 = 6 + 4 + 0.02 = 10.02ms。
- A 错14.02ms:旋转延迟按转一整圈(8ms)算;平均只要半圈。
- B 错6.02ms:漏了平均旋转延迟(半圈 4ms),以为寻道到位就能立刻读。
- C 错18ms = 6 + 4 + 8:传输时间按转一整圈算,实际只转过 1/400 圈。
- D 对10.02ms。
- A. (33, 1, 16)
- B. (33, 1, 8)
- C. (16, 2, 33)
- D. (133, 0, 16)
展开解析
A先换算成“每磁道 25 簇、每柱面 100 簇”。
- 每磁道 50 ÷ 2 = 25 簇,每柱面 25 × 4 = 100 簇。
- 3333 ÷ 100 = 33 余 33 → 柱面 33。
- 33 ÷ 25 = 1 余 8 → 磁头 1,道内第 8 簇 → 起始扇区 8 × 2 = 16(占 16、17 号扇区)。
- A 对(33, 1, 16)。
- B 错(33, 1, 8):把道内的簇序号 8 当成了扇区号,忘了乘 2。
- C 错(16, 2, 33):把簇号当成扇区号,按每柱面 200 扇区、每道 50 扇区去除。
- D 错(133, 0, 16):编号顺序弄反——以为先编完一个盘面的所有柱面再换磁头。
⑬ 调度:画甘特图算周转、带权周转、等待
- 按算法规则画甘特图:每到一个“事件点”(有进程到达、有进程完成、时间片用完)就重新判断谁运行。
- 周转时间 = 完成时间 − 到达时间;带权周转 = 周转 ÷ 服务时间;等待时间 = 周转 − 服务时间。
- 求平均时除以进程个数;检查甘特图总长 = 最后完成时刻。
单位口径:RR 中“新到达”和“刚用完时间片”同时入队时,本书约定新到达的排前面(题目另有说明按题目)。
常见错法:抢占式算法只在“进程完成时”才重新选择;周转时间忘了减到达时间;RR 入队顺序约定不一致。
- A. 9.5
- B. 7.25
- C. 9.75
- D. 10.25
展开解析
B每当有新进程到达,就比较“剩余时间”。
- 0:只有 P1,运行;1:P2 到(4 < P1 剩 7)→ 抢占,P2 运行到 5。
- 5:P3(2)、P1(7)→ P3 运行到 7;7:P4(3)、P1(7)→ P4 运行到 10;P1 运行到 17。
- 周转:P1 17、P2 4、P3 4、P4 4 → 平均 29 ÷ 4 = 7.25(模拟器核对)。
- A 错9.5 是非抢占 SJF 的结果(P1 先跑完 8 个单位):没有在 P2 到达时抢占。
- B 对7.25。
- C 错9.75 = (17 + 5 + 7 + 10) ÷ 4:用完成时间当周转时间,忘了减到达时间。
- D 错10.25 是先来先服务的结果。
- A. 11.0
- B. 10.25
- C. 10.0
- D. 11.25
展开解析
C逐个时间片画甘特图,注意入队顺序。
- 甘特图:P1(0~2)、P2(2~4)、P1(4~6)、P3(6~8)、P2(8~10)、P4(10~12)、P1(12~14)、P4(14~15)、P1(15~17)。
- 完成时刻:P3 8、P2 10、P4 15、P1 17。
- 周转:17、9、5、9 → 平均 40 ÷ 4 = 10.0(模拟器核对)。
- A 错11.0 是时间片取 3 时的结果,时间片看错。
- B 错10.25 是先来先服务的结果,没有按时间片轮转。
- C 对10.0。
- D 错11.25 是另一种入队约定(被抢占的先回队尾、新到达的排后面)的结果,与题目给的约定相反。
⑭ 银行家算法
- Need = Max − Allocation,先把 Need 矩阵写出来。
- 安全性检查:Work = Available;反复找一个 Need ≤ Work 的进程,假设它运行完,Work += 它的 Allocation;所有进程都能完成 → 安全。
- 处理请求:① Request ≤ Need?② Request ≤ Available?③ 试分配后做安全性检查;三步都过才分配。
单位口径:向量比较要求每一类资源都满足 ≤。
常见错法:用 Max 而不是 Need 去和 Work 比;试分配后忘了做安全性检查;只比较了一类资源。
| 进程 | Allocation(A B C) | Max(A B C) |
|---|---|---|
| P0 | 1 1 2 | 4 3 3 |
| P1 | 2 0 1 | 3 2 2 |
| P2 | 2 2 0 | 5 3 1 |
| P3 | 0 1 1 | 1 3 2 |
| P4 | 1 0 2 | 3 1 4 |
- A. P2, P4, P1, P3, P0
- B. P4, P1, P2, P3, P0
- C. P3, P4, P2, P1, P0
- D. P4, P2, P1, P3, P0
展开解析
D先求 Need:P0 (3,2,1)、P1 (1,2,1)、P2 (3,1,1)、P3 (1,2,1)、P4 (2,1,2),再逐个模拟。
- Work = (2,1,2):只有 P4 的 Need (2,1,2) ≤ Work → P4 完成,Work = (3,1,4)。
- 此时只有 P2 的 Need (3,1,1) ≤ Work(P0、P1、P3 都缺 B)→ Work = (5,3,4)。
- 之后 P1、P3、P0 依次都能满足 → P4, P2, P1, P3, P0 安全(程序枚举:所有安全序列都以 P4、P2 开头,共 6 个)。
- A 错第一步 P2 的 Need (3,1,1) 中 A 需要 3,而 Work 的 A 只有 2——用 Max 或只看了 B、C 两类资源才会觉得能满足。
- B 错P4 完成后 Work = (3,1,4),P1 的 Need (1,2,1) 中 B 需要 2,只有 1,卡在第二步。
- C 错第一步 P3 的 Need (1,2,1) 中 B 需要 2,Work 的 B 只有 1。
- D 对P4, P2, P1, P3, P0。
- A. 立即分配,因为请求不超过 Need,也不超过 Available
- B. 不分配,P0 等待,因为试分配后系统进入不安全状态
- C. 拒绝,因为请求超过了 P0 的 Need
- D. 拒绝,因为请求超过了 Available
展开解析
B三步检查缺一不可。
- (1,0,0) ≤ Need₀ (3,2,1) ✓;(1,0,0) ≤ Available (2,1,2) ✓。
- 试分配:Available = (1,1,2),P0 的 Allocation = (2,1,2)、Need = (2,2,1)。
- 安全性检查:五个进程的 Need 中,P4 缺 A,其余都缺 B,没有一个能满足 → 不安全,不分配(程序核对)。
- A 错只做了前两步检查,漏了试分配后的安全性检查——这正是银行家算法与“有就给”的区别。
- B 对试分配后不安全,P0 等待。
- C 错Need₀ = Max₀ − Allocation₀ = (3,2,1),请求 (1,0,0) 没有超过。
- D 错Available = (2,1,2),请求 (1,0,0) 没有超过。
⑮ 页表大小与多级页表
- 页内偏移 = log₂(页大小);页号位数 = 虚拟地址位数 − 偏移位数;页表项数 = 2页号位数。
- 每级页表占一页时:每级索引位数 = log₂(页大小 ÷ 页表项大小)。
- 级数 = ⌈页号位数 ÷ 每级位数⌉;最高一级用剩下的位数,表的大小 = 2剩余位数 × 表项大小,不一定满一页。
单位口径:KB / MB 的 K、M = 2¹⁰、2²⁰;页表项大小以字节计。
常见错法:忘了减去页内偏移;以为顶级页表也一定占满一页;页表项大小用错(4B / 8B)。
- A. 8KB
- B. 2MB
- C. 1KB
- D. 4KB
展开解析
考点8.5 多级页表
C先算二级页表索引位数,剩下的就是页目录索引。
- 偏移 13 位,页号 19 位。
- 二级页表一页能放 8KB ÷ 4B = 2048 项 → 二级索引 11 位。
- 页目录索引 19 − 11 = 8 位 → 2⁸ × 4B = 1KB。
- A 错8KB:以为页目录也必须占满一个页面;它只需要 256 项。
- B 错2MB = 2¹⁹ × 4B 是单级页表的大小。
- C 对1KB。
- D 错4KB = 2¹⁰ × 4B:套用了“4KB 页、10 + 10 划分”的常见结构,没按本题 8KB 页面重新算。
- A. 3 级,4KB
- B. 4 级,4KB
- C. 5 级,512B
- D. 4 级,64B
展开解析
考点8.5 多级页表
D每级 9 位,页号 30 位。
- 偏移 12 位,页号 42 − 12 = 30 位。
- 每级索引 log₂(4KB ÷ 8B) = 9 位 → 级数 ⌈30 ÷ 9⌉ = 4。
- 低三级用 27 位,最高级剩 3 位 → 2³ × 8B = 64B。
- A 错3 级、4KB:页表项按 4B 算,每级 10 位(30 = 10 + 10 + 10);本题表项是 8B。
- B 错级数对了,但以为最高一级页表也占满一页;它只有 8 项。
- C 错5 级、512B:忘了减去 12 位页内偏移,拿 42 位直接按 9 位一级去分。
- D 对4 级,64B。
⑯ 有效访存时间(TLB、多级页表、缺页)
- 画路径:查 TLB → 命中直接拿页框号;未命中按页表级数访问主存。
- 再访问数据本身一次(再考虑 Cache 时换成 Cache 的平均时间)。
- 加权:EAT = tTLB + (1 − h) × 级数 × tm + tm;有缺页再按缺页率加上缺页处理时间。
单位口径:题目说“TLB 与主存同时查”时,TLB 命中不必再加查 TLB 的时间以外的项,要按题目口径改公式。
常见错法:多级页表只算一次访存;把命中率当缺失率;忘了访问数据本身的那一次。
- A. 103ns
- B. 297ns
- C. 300ns
- D. 105ns
- A. 2.5 × 10−6
- B. 7.5 × 10−6
- C. 2.5 × 10−5
- D. 5 × 10−6
展开解析
A(1 − p) × 100 + p × 2 × 10⁷ ≤ 150。
- 20ms = 2 × 10⁷ns。
- 100 + p × (2 × 10⁷ − 100) ≤ 150 → p ≤ 50 ÷ 19999900 ≈ 2.5 × 10⁻⁶。
- A 对约 2.5 × 10⁻⁶。
- B 错7.5 × 10⁻⁶ = 150 ÷ (2 × 10⁷):直接拿允许的总时间 150ns 去除,没减去不缺页时本来要的 100ns。
- C 错2.5 × 10⁻⁵ = 50 ÷ (2 × 10⁶):把 20ms 换算成了 2 × 10⁶ns,少了一个 0。
- D 错5 × 10⁻⁶ = 50 ÷ 10⁷:把缺页时间当成 10ms,看错了题目数据。
⑰ 页面置换:数缺页次数
- 画表:每列一次访问,行是页框内容,缺页的列打 ×。
- OPT 淘汰“以后最晚再用”的;LRU 淘汰“最久没用”的(往前看);FIFO 淘汰最早装入的;CLOCK 指针扫到访问位为 0 的就换,经过的 1 清 0。
- 数 × 的个数;看清“初始为空、开始的装入算不算缺页”(一般算)。
单位口径:缺页率 = 缺页次数 ÷ 访问次数。
常见错法:LRU 和 FIFO 混淆(命中不更新顺序就是 FIFO);CLOCK 忘了“第二次机会”;漏算开始时装入的缺页。
- A. 6
- B. 8
- C. 9
- D. 5
展开解析
考点8.7 置换算法
B淘汰“最近最久没被访问”的页,命中要更新它的最近访问时间。
- 1、2、4 装入缺 3 次;3 缺页,淘汰 1;2 命中;0 缺页,淘汰 4;3 命中;2 命中。
- 5 缺页,淘汰 0;0 缺页,淘汰 3;3 缺页,淘汰 2;0 命中。
- 共 8 次(模拟器核对;同一序列 OPT 6 次、CLOCK 7 次、FIFO 9 次)。
- A 错6 是 OPT 的结果(淘汰以后最晚使用的页),不是 LRU。
- B 对8 次,其中 3 次是开始时的装入。
- C 错9 是 FIFO 的结果:命中时没有更新页的“最近使用”顺序。
- D 错5 = 8 − 3:漏算了开始时装入 1、2、4 的 3 次缺页。
- A. 9
- B. 8
- C. 7
- D. 4
展开解析
C与 FIFO 的区别:访问位为 1 的页有“第二次机会”。
- 1、2、4 装入(缺 3 次),访问位都是 1,指针回到第 1 个页框。
- 访问 3:扫一圈把三个访问位都清 0,回到页 1 换掉;访问 2 命中(置 1);访问 0:页 2 访问位为 1 → 清 0 跳过,换掉 4。
- 访问 3、2 命中;访问 5:换掉 3;访问 0 命中;访问 3:换掉 2;访问 0 命中。共缺页 7 次(模拟器逐步核对)。
- A 错9 是 FIFO 的结果:没有给访问位为 1 的页第二次机会。
- B 错8 是 LRU 的结果;CLOCK 只是 LRU 的近似,结果可能不同。
- C 对7 次,比 FIFO 少两次:第 6 次访问(页 0)缺页时,页 2 刚被访问过、访问位为 1 被跳过,换出的是 4,所以第 8 次访问页 2 命中;第 11 次访问(页 3)换出的是 2 而不是 0,所以最后的页 0 命中。
- D 错4 = 7 − 3:漏算了开始时的 3 次装入。
⑱ 磁盘调度:移动距离与平均寻道长度
- 写出服务顺序:SSTF 每次选最近的;SCAN 沿当前方向走到端点再回头;LOOK 走到最远请求就回头;C-SCAN 走到端点后直接返回另一端再同向服务;C-LOOK 只到最远请求、返回到最小请求。
- 逐段求 |差| 再相加;C-SCAN 的返回距离本书计入(2010-45 口径)。
- 平均寻道长度 = 总移动距离 ÷ 请求个数。
单位口径:磁道编号范围(如 0 ~ 199)决定 SCAN、C-SCAN 走到哪;方向看题目给的“当前移动方向”。
常见错法:SCAN 与 LOOK 混淆(走不走到端点);C-SCAN 的返回距离计不计入;请求个数数错。
- A. 180
- B. 303
- C. 311
- D. 379
展开解析
D80 → 199(端点)→ 0 → 61。
- 向上服务 95、120、133、150、168,到端点 199:119。
- 返回 0:199。
- 再向上服务 7、15、42、61:61。合计 119 + 199 + 61 = 379(模拟器核对)。
- A 错180 = 379 − 199:返回的 199 个磁道没有计入,与题目要求不符。
- B 错303 是 C-LOOK:只走到最远请求 168,再返回到最小请求 7。
- C 错311 是 SCAN:到端点后掉头向下服务,而不是返回 0 再向上。
- D 对379。
- A. 27.7
- B. 34.6
- C. 91.3
- D. 31.1
展开解析
A每次选离当前磁头最近的请求。
- 80 → 95 → 120 → 133 → 150 → 168 → 61 → 42 → 15 → 7。
- 总距离 = 88 + 161 = 249(向上 88,再折回到 7 走 161)。
- 平均 = 249 ÷ 9 ≈ 27.7。
- A 对27.7。
- B 错34.6 = 311 ÷ 9 是 SCAN 的结果(走到端点 199 再回头)。
- C 错91.3 = 822 ÷ 9 是先来先服务的结果。
- D 错31.1 = 249 ÷ 8:总距离对了,但请求个数数成了 8 个。
⑲ 索引结构、FAT 与位示图的大小
- 每个索引块能放的地址数 N = 块大小 ÷ 地址项大小。
- 逻辑块号 = ⌊偏移 ÷ 块大小⌋;依次减去直接地址数、N、N²……判断落在哪一级;读盘次数 = 该级的索引块数 + 1 次数据块(看清 inode 是否已在内存)。
- 位示图大小 = 盘块总数 位;FAT 表项 ≥ log₂(盘块总数) 位,再按题目要求取整到字节;FAT 大小 = 盘块数 × 表项大小。
单位口径:容量的 K、M、G 是 2¹⁰、2²⁰、2³⁰;位示图以“位”计,换成字节要 ÷ 8。
常见错法:忘了前面的直接地址块;把已在内存的 inode 也算一次读盘;位示图按字节算、FAT 表项不按需要的位数取。
- A. 二次间接,3 次
- B. 一次间接,3 次
- C. 一次间接,2 次
- D. 一次间接,1 次
展开解析
C逻辑块号 = 1050000 ÷ 2048 取整。
- N = 2KB ÷ 4B = 512。逻辑块号 = ⌊1050000 ÷ 2048⌋ = 512。
- 直接地址管 0 ~ 9;一次间接管 10 ~ 521 → 第 512 块在一次间接里。
- 读一次间接索引块 + 读数据块 = 2 次(inode 已在内存,不算)。
- A 错二次间接、3 次:以为一次间接只能管到第 511 块,漏了前面 10 个直接地址(一次间接管到 521)。
- B 错3 次:把已在内存的索引结点也算了一次读盘。
- C 对一次间接,2 次。
- D 错1 次:只读了数据块,漏了要先读出一次间接索引块才能知道数据块在哪。
- A. 8MB,24MB
- B. 1MB,24MB
- C. 1MB,32MB
- D. 1MB,16MB
13.4融合综合题专练:一次访存、一次 I/O、一条指令的一生
本书书名里的“融合”,在综合题里体现得最明显:一道题从硬件讲到操作系统,再回到硬件。下面三条主线是 408 综合题最常见的“全过程”。每条先用一张图把全过程画出来——硬件做什么、操作系统做什么、CPU 在什么态、进程在什么状态——再给两道按 43 ~ 46 题格式自编的综合题(一道偏计组、一道偏操作系统),附满分答案与评分细则。题目的数据和情境都与正文例题、第 11 篇不同;所有数字由 verify/08/p13/fuse.py 计算并断言(时空图由流水线模拟得到)。
主线一:一次访存的全过程
从 CPU 发出一个虚拟地址开始,到拿到数据为止。命中时全是硬件的事;只有缺页时操作系统才出场,而且要绕一大圈:异常 → 内核 → 磁盘 → 中断 → 唤醒 → 重新执行。
某计算机按字节编址,虚拟地址 32 位,物理地址 28 位,页面大小 16KB,采用一级页表,页表项 4B。TLB 共 8 项,采用全相联映射,其中部分表项如下表左。数据 Cache 的数据区容量 32KB,采用 4 路组相联映射,块大小 32B,用物理地址访问;Cache 第 30 组的 4 行如下表右(其余信息略)。
| 有效位 | 虚页号 | 页框号 |
|---|---|---|
| 1 | 0002BH | 3F1H |
| 1 | 0004AH | 2C7H |
| 0 | 0012AH | 1A0H |
| 1 | 0FFE0H | 0D5H |
| 行 | 有效位 | 标记 |
|---|---|---|
| 0 | 1 | 4C2H |
| 1 | 0 | 58FH |
| 2 | 1 | 58FH |
| 3 | 1 | 18FH |
(1) 虚拟地址中虚页号、页内偏移各占几位?用物理地址访问 Cache 时,标记、组号、块内地址各占几位?(3 分)
(2) 虚拟地址 0012 A3C4H 对应的物理地址是多少?访问时 TLB 是否命中?Cache 是否命中?要求写出组号和标记。(4 分)
(3) 设查 TLB 需 1ns,访问 Cache 需 2ns,访问主存需 80ns;Cache 缺失时先用 2ns 查 Cache,再用 80ns 从主存调块,调块的同时把数据送给 CPU;页表项不会出现在 Cache 中。分别求“TLB 命中、Cache 命中”和“TLB 缺失、页在主存、Cache 缺失”两种情况下这次访存所需的时间。(2 分)
(4) 访问虚拟地址 0040 2010H 时发现所在页不在主存。从发现缺页到这条访存指令执行完毕,按时间顺序写出主要步骤,并指出每一步由硬件还是操作系统完成。(3 分)
做完再点开:满分答案与评分细则
考点8.8 一次访存的全过程8.8 命中组合8.3 地址划分8.7 缺页处理
页面 16KB = 214B → 页内偏移 14 位,虚页号 32 − 14 = 18 位。
块 32B → 块内地址 5 位;行数 32KB ÷ 32B = 1024,组数 1024 ÷ 4 = 256 → 组号 8 位;标记 = 28 − 8 − 5 = 15 位。
0012 A3C4H 的低 14 位是页内偏移 = 23C4H,其余高位是虚页号 = 0012A3C4H >> 14 = 0004AH。
TLB 中有有效的 0004AH 项,TLB 命中,页框号 2C7H。物理地址 = 2C7H × 214 + 23C4H = 0B1 E3C4H(28 位)。
0B1E3C4H = 0000 1011 0001 1110 0011 1100 0100B:块内地址 = 低 5 位 00100 = 4;组号 = 接下来 8 位 0001 1110 = 30;标记 = 高 15 位 = 58FH。第 30 组中行 2 有效且标记为 58FH → Cache 命中(行 1 标记相同但有效位为 0,不算)。
常见错误:照搬“4KB 页、12 位偏移”,把虚页号切成 0012AH——正好撞上 TLB 里那个无效的 0012AH 项,于是错判为 TLB 缺失。页面多大,偏移就几位,每题都要重算。
TLB 命中、Cache 命中:1 + 2 = 3ns。
TLB 缺失、页在主存、Cache 缺失:查 TLB 1ns + 访存查页表 80ns + 查 Cache 2ns + 主存调块 80ns = 163ns。
① 硬件(MMU)查页表发现存在位为 0,产生缺页异常;② 硬件保存断点(PC 指向这条访存指令)和 PSW,切换到内核态,转缺页异常处理程序;③ 操作系统找空闲页框,没有则按置换算法选淘汰页,淘汰页被修改过就先写回磁盘;④ 操作系统启动磁盘把所缺的页读入页框,进程阻塞、调度别的进程(读盘由磁盘控制器 / DMA 完成,完成后发中断);⑤ 操作系统的中断处理程序唤醒进程,进程再次运行时修改页表项(存在位置 1、填页框号)和 TLB;⑥ 中断返回(硬件恢复 PC、PSW,回到用户态),重新执行这条访存指令,这次能在页表中找到页框号,完成访存。
缺页处理完要回到引起缺页的那条指令重新执行,不是执行下一条——缺页是“故障”,不是“自陷”。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | 虚页号 18 位、页内偏移 14 位 | 1 |
| 标记 15 位、组号 8 位、块内 5 位(每错一个扣 1 分,扣完为止) | 2 | |
| (2) | 虚页号 0004AH、偏移 23C4H | 1 |
| 物理地址 0B1E3C4H | 1 | |
| TLB 命中 | 1 | |
| 组号 30、标记 58FH,Cache 命中(指出行 1 无效) | 1 | |
| (3) | 3ns | 1 |
| 163ns | 1 | |
| (4) | 步骤完整、顺序正确(含“重新执行该指令”) | 2 |
| 正确指出硬件、操作系统各自完成的步骤 | 1 | |
| 合计 | 12 |
一次访存的四关:TLB → 页表 → Cache → 主存,前三关的地址划分每题都要按本题参数重新算;缺页是唯一要操作系统出场的情况。
某系统采用请求分页存储管理,局部置换,进程 P 分得的页框已全部占满。P 在用户态执行一条访存指令时,所需的页 X 不在内存,按上面“一次访存的全过程”图的流程处理。回答:
(1) 缺页异常与一般的外部中断有哪些不同?(至少答两点)(2 分)
(2) 若采用页面缓冲算法,页 X 刚被淘汰不久、仍在空闲页链表中,图中哪些步骤可以省去?进程 P 还会阻塞吗?(2 分)
(3) 已知 P 的缺页率为 2 × 10−6,主存访问时间 100ns;缺页时若被淘汰的页未被修改,处理时间平均 8ms,若被修改过(须先写回)平均 16ms;被淘汰的页中有 40% 被修改过。不考虑 TLB 和 Cache,求 P 的有效访存时间。(2 分)
(4) 若操作系统换出页 Y 后,忘了作废 TLB 中 Y 的表项,会出现什么错误?(2 分)
做完再点开:满分答案与评分细则
考点8.7 缺页处理8.7 页面缓冲8.9 缺页率与 EAT
① 来源:缺页由正在执行的指令自己引起(内部异常),外部中断来自 CPU 以外的设备;② 检测时机:缺页在指令执行过程中(取指或访存时)检测并立即处理,外部中断在一条指令执行结束后才响应;③ 返回点:缺页处理完返回本条指令重新执行,外部中断返回下一条指令;④ 一条指令执行期间可能多次缺页。
可以省去从磁盘读页的整段:⑥ 启动磁盘读页、⑦ DMA 传送、⑧ 磁盘中断唤醒都不需要了;⑤ 中也不必选淘汰页、写回脏页,只需把 X 从空闲页链表摘下来。
P 不会阻塞:缺页处理程序直接修改页表(存在位置 1、填页框号)后返回,重新执行该指令。缺页次数不变,但每次缺页的代价大大降低。
平均缺页处理时间 tf = 0.6 × 8 + 0.4 × 16 = 11.2ms = 1.12 × 107ns。
EAT = (1 − 2 × 10−6) × 100 + 2 × 10−6 × 1.12 × 107 ≈ 100 + 22.4 = 122.4ns。
脏页要先写回,等于缺页一次做两次磁盘 I/O,所以平均处理时间要按比例加权;直接用 8ms 会得到 116ns。
Y 的旧表项还留在 TLB 里,以后访问 Y 的虚页时 TLB 命中,得到的仍是旧页框号——而这个页框现在装的是 X。于是读到的是 X 的数据(读错),写操作会破坏 X 的内容,而且不会产生缺页,Y 也永远调不回来。所以换出一页时必须同时作废 TLB 中对应的项(多核时还要通知其他核)。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | 答出任意两点不同(来源、检测时机、返回点、可多次) | 2 |
| (2) | 指出读盘相关步骤(启动磁盘、DMA 传送、中断唤醒)可省 | 1 |
| P 不阻塞,只改页表;缺页次数不变 | 1 | |
| (3) | 平均处理时间 11.2ms | 1 |
| EAT ≈ 122.4ns | 1 | |
| (4) | TLB 命中得到旧页框号,访问到 X 的数据 | 1 |
| 读错或破坏 X、且不产生缺页;应作废 TLB 项 | 1 | |
| 合计 | 8 |
页面缓冲、写回脏页、TLB 作废,都是“缺页处理”这一段里的细节;能把它们放回全过程图的哪一步,就能答对。
主线二:一次 I/O 的全过程
以“读磁盘”为例。注意三件事:① 进程在启动设备之后就阻塞了,CPU 去运行别的进程;② 设备完成后是中断处理程序唤醒它,它只进入就绪态;③ 数据先到内核缓冲区,进程再次运行时才复制到用户区。
进程 P 调用 write(fd, s, 80) 向打印机输出 80 个字符。打印机采用中断驱动方式:驱动程序每次向打印机控制器的数据寄存器送 1 个字符,打印完这个字符后控制器发一次中断。下面是这次输出中发生的事件(顺序已打乱):
a. 驱动程序把第 1 个字符写入控制器的数据寄存器并启动打印,P 进入阻塞态,调度程序选中进程 Q 运行
b. P 执行 write 对应的陷入指令,CPU 由用户态进入内核态
c. 控制器打印完一个字符,向 CPU 发出中断请求
d. 中断处理程序把下一个字符送入数据寄存器
e. 最后一个字符打印完毕,中断处理程序唤醒 P
f. 设备独立性软件把 80 个字符从 P 的用户区复制到内核缓冲区,再调用打印机驱动程序
g. P 被调度运行,write 返回,CPU 回到用户态
(1) 写出这些事件的正确顺序(c、d 可以重复出现,用“(c d)×k”表示)。(2 分)
(2) 这次输出共引起多少次打印机中断?若改用 DMA 方式(一次传送整行),共引起几次?(2 分)
(3) 写出 P 在整个过程中的状态变化;事件 a ~ g 中哪些在内核态执行?(2 分)
(4) 为什么要先把字符复制到内核缓冲区,而不让驱动程序直接从 P 的用户区取字符?(2 分)
做完再点开:满分答案与评分细则
考点9.5 I/O 软件层次9.6 一次 scanf 的全过程9.4 中断方式与 DMA
b → f → a → (c d)×79 → c → e → g:陷入内核 → 复制到内核缓冲区并调用驱动 → 驱动送第 1 个字符、P 阻塞 → 每打完一个字符中断一次、送下一个(共 79 轮)→ 第 80 个字符打完的那次中断里唤醒 P → P 被调度,write 返回。
中断驱动方式每打印完 1 个字符中断一次:80 次(最后一次用于唤醒 P)。改用 DMA,整行传送完才中断一次:1 次。
P:运行 →(a)阻塞 →(e)就绪 →(g)运行。
b 的陷入指令本身在用户态执行,执行后 CPU 进入内核态;f、a、d、e 以及 g 中 write 返回之前的部分都在内核态执行;c 是硬件动作(打印机控制器发请求),不属于“CPU 态”。
① P 在等待期间被阻塞,它的页可能被换出,中断处理程序运行时 CPU 正在执行别的进程,地址空间也不是 P 的,无法直接访问 P 的用户区;② 复制后 P 的用户区可以立即被重用(write 返回后 P 可以改 s);③ 缓和 CPU 与慢速打印机之间的速度差,并能把多次小的输出合并。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | 顺序正确(b f a 在前,c d 循环,e g 在后) | 2 |
| (2) | 80 次 | 1 |
| DMA 1 次 | 1 | |
| (3) | 状态序列正确 | 1 |
| 正确指出内核态执行的事件 | 1 | |
| (4) | 答出“中断时不在 P 的地址空间 / P 的页可能被换出” | 1 |
| 答出用户区可重用或速度匹配 | 1 | |
| 合计 | 8 |
I/O 全过程的骨架都一样:陷入 → 设备独立层 → 驱动启动设备、进程阻塞 → 设备完成发中断 → 中断处理唤醒 → 进程复制数据返回;中断驱动与 DMA 的区别只在“中断几次”。
某计算机 CPU 主频 1GHz,总线宽 32 位、总线时钟 200MHz,每个总线周期占 1 个总线时钟、传送一个 32 位字。某磁盘的数据传输率为 40MB/s(M = 106),与主机交换数据时以 32 位字为单位。
(1) 若采用中断方式,每传送一个字中断一次,每次中断的响应和处理共需 300 个 CPU 时钟周期。CPU 用于该磁盘 I/O 的时间占比是多少?这种方式可行吗?(3 分)
(2) 改用 DMA 方式,每次 DMA 传送 4096B,预处理需 1000 个时钟周期,后处理(含中断)需 1500 个时钟周期。CPU 用于该磁盘 I/O 的时间占比是多少?(3 分)
(3) DMA 采用周期挪用方式,每传送一个字挪用一个总线周期。该总线的带宽是多少?磁盘传送占用了总线时间的百分之几?(2 分)
(4) 若同一总线上还有一个以 600MB/s 进行 DMA 传送的 SSD,两个设备能否同时以最大速率工作?此时留给 CPU 访存的总线带宽最多是多少?(2 分)
做完再点开:满分答案与评分细则
考点9.4 三种 I/O 方式9.4 DMA 与周期挪用9.1 总线带宽9.8 并发速率
每秒中断次数 = 40 × 106B/s ÷ 4B = 107 次;每秒需要 107 × 300 = 3 × 109 个时钟周期。
占比 = 3 × 109 ÷ 109 = 300% > 100%,CPU 全部用来处理中断也来不及,会丢数据,不可行。
每秒 DMA 次数 = 40 × 106 ÷ 4096 ≈ 9765.6 次;每次 1000 + 1500 = 2500 个周期。
占比 = 9765.6 × 2500 ÷ 109 ≈ 2.44%。
数据块 4096B 是容量单位(212),速率 40MB/s 是 106,两者单位不同,不能约成“40MB ÷ 4KB = 104”。
带宽 = 4B × 200 × 106/s = 800MB/s。磁盘每秒挪用 107 个总线周期,占 107 ÷ (2 × 108) = 5%(也可以按 40 ÷ 800 计算)。
40 + 600 = 640MB/s ≤ 800MB/s,可以同时以最大速率工作;留给 CPU 访存的带宽最多 800 − 640 = 160MB/s(实际还要扣除仲裁等开销)。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | 每秒中断次数 10⁷ | 1 |
| 占比 300% | 1 | |
| 结论:不可行(来不及、丢数据) | 1 | |
| (2) | 每秒 DMA 次数约 9765.6 | 1 |
| 每次 2500 周期 | 1 | |
| 占比约 2.44% | 1 | |
| (3) | 带宽 800MB/s | 1 |
| 占用 5% | 1 | |
| (4) | 可以(640 ≤ 800) | 1 |
| 剩余 160MB/s | 1 | |
| 合计 | 10 |
占用率 = 每秒次数 × 每次周期 ÷ 主频;中断方式算出来超过 100% 就是“不可行”的标准理由。
主线三:一条指令的一生
一条指令在流水线里走过五段,每一段都可能“出事”:阻塞、冲刷、缺页、溢出、越权;走完以后还要看一眼有没有外部中断。把这些可能性和处理方式放在一张图上,过程分析题、流水线题、异常题就是同一件事。
某 32 位 RISC 处理器采用 IF、ID、EX、MEM、WB 五段流水线,按序发射。数据冒险用转发解决,但 load 指令后紧跟使用其结果的指令需阻塞 1 个周期;寄存器“前半周期写、后半周期读”;分支指令在 EX 段末确定是否转移,采用“预测不转移”,预测错误时冲刷已取入的指令。按字节编址,每条指令 4B,程序如下:
1000H I1: lw r1, 0(r2) 1004H I2: add r3, r1, r4 1008H I3: sw r3, 4(r2) 100CH I4: beq r3, r0, L ; L = 1020H 1010H I5: sub r6, r6, r1
(1) 找出所有的写后读(RAW)数据相关,写出相关的指令对和寄存器。(2 分)
(2) 若 I4 不转移,从 I1 取指开始到 I5 写回结束共需多少个时钟周期?画出时空图。(3 分)
(3) 若 I4 发生转移,哪些指令被冲刷?转移目标指令在第几个时钟周期取指?(2 分)
(4) 若 I1 在 MEM 段(第 4 个时钟周期)访问数据时发生缺页:这属于哪一类异常?此时流水线中的其他指令怎样处理?缺页处理完后从哪个地址重新执行?该地址由谁保存?(3 分)
(5) 若 I3 执行期间到来一个外部中断请求(中断允许),CPU 在什么时候响应?中断返回后从哪个地址继续执行?(2 分)
做完再点开:满分答案与评分细则
考点4.4 冒险的对策4.4 数阻塞4.4 精确异常5.3 异常的分类5.4 硬件与软件的分工
I1 → I2(r1)、I2 → I3(r3)、I2 → I4(r3)、I1 → I5(r1)。
sw 也要“读” r3(要把它存进内存),beq 要读 r3 做比较——它们都是读者;I1 → I5 的距离远,不会引起阻塞,但仍是相关。
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
|---|---|---|---|---|---|---|---|---|---|---|
| I1 | IF | ID | EX | MEM | WB | |||||
| I2 | IF | ID | 阻 | EX | MEM | WB | ||||
| I3 | IF | 阻 | ID | EX | MEM | WB | ||||
| I4 | IF | ID | EX | MEM | WB | |||||
| I5 | IF | ID | EX | MEM | WB |
I1 的数据在第 4 周期 MEM 末才取到,I2 的 EX 推迟到第 5 周期(阻塞 1 拍),I3 跟着在 IF 多停 1 拍;I2 → I3、I2 → I4 的 r3 都能转发,不再阻塞。共 10 个周期(= 5 + 4 + 1,程序模拟核对)。
I4 在第 7 周期 EX 末确定转移。此前按“预测不转移”已取入的 I5(第 6 周期取指)和 1014H 处的指令(第 7 周期取指)被冲刷;目标指令(1020H)在第 8 个周期取指。
属于故障(可恢复的内部异常)。为保证精确异常,I1 本身和它后面已进入流水线的 I2(在 ID)、I3(在 IF)全部作废,不改变任何寄存器和内存。缺页处理完后从 I1 的地址 1000H 重新执行;该断点(以及 PSW)由硬件在异常响应时保存。
常见错误:认为返回到 I2(1004H)继续——那是自陷(如 syscall)的返回方式。
外部中断在一条指令执行结束后才响应(中断周期检查):I3 执行完毕时响应。断点是下一条指令 I4 的地址 100CH,中断返回后从 I4 继续执行。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | 四对相关全对(每漏一对扣 0.5 分) | 2 |
| (2) | 时空图正确(I2 阻塞 1 拍、I3 随之停顿) | 2 |
| 共 10 个周期 | 1 | |
| (3) | 冲刷 I5 和 1014H 处指令 | 1 |
| 目标在第 8 周期取指 | 1 | |
| (4) | 故障 | 1 |
| I2、I3 作废 | 1 | |
| 从 1000H 重新执行,断点由硬件保存 | 1 | |
| (5) | I3 执行结束时响应 | 1 |
| 返回到 100CH(I4) | 1 | |
| 合计 | 12 |
一条指令可能在任一段“出事”:数据相关 → 阻塞;分支猜错 → 冲刷;缺页 → 作废并重来;外部中断 → 等它做完再说。
进程 P 在用户态运行,先后遇到下列四个事件:
① 执行 mov eax, [ebx] 时,所访问的页不在内存
② 执行整数除法指令 idiv ecx 时,ecx = 0
③ 执行 syscall 指令请求 write 服务
④ P 的时间片用完时到来一次时钟中断
(1) 四个事件各属于哪一类(故障、自陷、终止、外部中断)?分别在什么时候被 CPU 检测或响应?(4 分)
(2) 四个事件处理完后,CPU 分别返回到哪里(本条指令 / 下一条指令 / 不返回)?处理之后 P 处于什么状态?(4 分)
做完再点开:满分答案与评分细则
考点5.3 中断和异常的分类5.4 一次中断的完整接力6.3 信号5.6 系统调用
| 事件 | 类型 | 何时检测 / 响应 |
|---|---|---|
| ① 缺页 | 故障 | 指令执行过程中(访存时)由 MMU 检测,立即处理 |
| ② 除 0 | 故障(不可恢复) | 执行除法指令时由运算部件检测,立即处理 |
| ③ syscall | 自陷(陷阱) | 执行这条指令本身就是“有意”进入内核 |
| ④ 时钟中断 | 外部中断 | 当前指令执行结束后,在中断周期响应 |
除 0 按王道口径也归为“故障”,但它无法通过处理程序修复,所以不返回(见第 (2) 问);有的教材把这类归入“终止”,答题时写出“不可恢复、终止进程”就不会扣分。
| 事件 | 返回到 | 处理后 P 的状态 |
|---|---|---|
| ① 缺页 | 本条指令(重新执行 mov) | 读盘期间阻塞,读完就绪,再被调度时运行 |
| ② 除 0 | 不返回 | 内核向 P 发 SIGFPE 信号;P 没有捕获时默认动作是终止 → 终止态 |
| ③ syscall | 下一条指令 | write 完成前若需等待设备则阻塞;完成后返回,继续运行 |
| ④ 时钟中断 | 下一条指令(断点) | 时间片用完 → 就绪态,排到就绪队列尾;以后被调度时从断点继续 |
④ 的返回点虽然是下一条指令,但不一定“马上”返回:调度程序会先让别的进程运行。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | 四个事件的类型各 0.5 分 | 2 |
| 四个事件的检测 / 响应时机各 0.5 分 | 2 | |
| (2) | 四个事件的返回点各 0.5 分 | 2 |
| 四个事件处理后 P 的状态各 0.5 分 | 2 | |
| 合计 | 8 |
同一个“进内核”,返回点由类型决定:故障回本条、自陷回下一条、外部中断回断点(下一条)、不可恢复的故障不回来;信号是内核把“结果”告诉进程的方式。
13.5全真模拟卷 A(计组 + 操作系统 80 分 · 限时 100 分钟)
本卷按 408 的题号和分值出题:计组单选第 12 ~ 22 题(22 分)、操作系统单选第 23 ~ 32 题(20 分)、计组综合第 43、44 题(11 + 12 分)、操作系统综合第 45、46 题(8 + 7 分),合计 80 分,对应 408 全卷中计组 45 分、操作系统 35 分。建议用时 100 分钟:选择题 35 分钟,综合题 65 分钟。也可以在上方的工具条里选择“只做计组”(55 分钟)或“只做操作系统”(45 分钟)。所有数字和程序结论都已核对(verify/08/p13/mock_a.py、mock/a_c.c、mock/a45_pv.py)。
一、单项选择题 · 计组(第 12 ~ 22 题,每题 2 分,共 22 分)
- A. 4s
- B. 4.8s
- C. 6s
- D. 3.33s
展开解析
考点1.8 性能指标
B先由原来的数据求出指令条数,再用新的 CPI 和主频算时间。
- 指令条数 = 5 × 1.6 × 10⁹ ÷ 2 = 4 × 10⁹。
- 新时间 = 4 × 10⁹ × 2.4 ÷ (2 × 10⁹) = 4.8s。
- A 错4s = 5 × 1.6 ÷ 2:只按主频的比例缩短,忽略了 CPI 变大。
- B 对4.8s。
- C 错6s = 5 × 2.4 ÷ 2:只按 CPI 变大的比例延长,忽略了主频提高。
- D 错3.33s = 5 × (1.6/2) × (2/2.4):CPI 的比值倒过来用了,CPI 变大应使时间变长。
int x = -3; unsigned u = 3;(int 为 32 位补码),下列表达式中值为“真”的是( )。Ⅰ. x < u Ⅱ. x + u == 0 Ⅲ. (int)(x * u) == -9 Ⅳ. x / 2 == (x >> 1)
- A. 仅Ⅰ、Ⅱ
- B. 仅Ⅰ、Ⅳ
- C. 仅Ⅱ、Ⅲ
- D. 仅Ⅱ、Ⅲ、Ⅳ
展开解析
C有符号与无符号一起运算时,有符号数先转成无符号数(位串不变)。
- Ⅰ 错x 转成 unsigned 是 2³² − 3 = 4294967293,大于 3,所以 x < u 为假(gcc 实测)。
- Ⅱ 对(2³² − 3) + 3 = 2³²,按 32 位取模得 0,等于 0。
- Ⅲ 对x × u 的低 32 位与 −9 的补码相同,强制转回 int 就是 −9。
- Ⅳ 错x / 2 向 0 取整得 −1;x >> 1 是算术右移、向下取整得 −2,两者不等。
干扰项从哪来:A 是按数学上 −3 < 3 判 Ⅰ 为真,忘了 x 被转成了无符号数;B 是误判 Ⅰ,又以为负数的除法和算术右移结果一样;D 是以为 x / 2 和 x >> 1 总相等(只对非负数或能整除时成立)
- A. 0080 0000H
- B. 0000 0001H
- C. 7F80 0000H
- D. 7FC0 0000H
- A. 45
- B. −18
- C. −13
- D. −19
展开解析
考点2.3 算术移位
D补码算术右移:高位补符号位,相当于除以 4 后向下取整。
- B5H = 1011 0101B,真值 −75。
- 算术右移 2 位:1110 1101B = EDH,真值 −19(−75 ÷ 4 = −18.75,向下取整)。
- A 错45 = 0010 1101B:做成了逻辑右移,高位补 0。
- B 错−18:按 C 语言除法“向 0 取整”算的;算术右移是向下取整。
- C 错−13:把 B5H 当成原码(−53),再按原码的数值部分右移、向 0 取整。
- D 对−19。
- A. 2400ns
- B. 780ns
- C. 600ns
- D. 840ns
展开解析
考点8.2 多体交叉
B低位交叉:第一个字要一个完整存取周期,以后每隔一个总线周期出一个字。
- 满足流水条件:240 = 4 × 60。
- 时间 = 240 + (10 − 1) × 60 = 780ns。
- A 错2400ns = 10 × 240:按单体(或高位交叉)顺序读,没有利用多体流水。
- B 对780ns。
- C 错600ns = 10 × 60:以为每个字都只要一个总线周期,忘了第一个字要等满 240ns。
- D 错840ns = 240 + 10 × 60:间隔数多算了一个,10 个字之间只有 9 个间隔。
- A. 34
- B. 11
- C. 17
- D. 2
展开解析
考点8.3 映射方式
D组号 = 主存块号 mod 组数。
- 块号 = 1A2BH ÷ 16 = 1A2H = 418。
- 组数 = 64 ÷ 2 = 32,418 mod 32 = 2。
- A 错34 = 418 mod 64:按 64 行的直接映射算了,2 路组相联只有 32 组。
- B 错11 = 6699 mod 32:直接拿字节地址取模,没先去掉块内地址。
- C 错17 = (1A2BH ÷ 32) mod 32:块内地址按 5 位算了,块大小 16B 只要 4 位。
- D 对第 2 组。
- A. (003H, 003H, 123H)
- B. (00CH, 031H, 23H)
- C. (000H, C03H, 123H)
- D. (00CH, 03H, 123H)
展开解析
考点8.5 多级页表
A写成二进制后按 10 | 10 | 12 切,而不是按十六进制数字切。
- 00C0 3123H = 0000 0000 11 | 00 0000 0011 | 0001 0010 0011B。
- 页目录号 = 00 0000 0011B = 003H;页表索引 = 00 0000 0011B = 003H;偏移 = 123H。
- A 对(003H, 003H, 123H)。
- B 错按十六进制数字 3 | 3 | 2 位切开,没换成二进制按 10 | 10 | 12 位切。
- C 错把页目录号当成 8 位、页表索引当成 12 位来切。
- D 错把页目录号当成 12 位、页表索引当成 8 位来切。
- A. 7FF4H
- B. 7FEFH
- C. 7FECH
- D. 7FF0H
展开解析
考点3.3 过程调用
C压栈 = 先 SP 减 4、再写入。
- A 错7FF4H:当成栈向高地址增长,SP 加了 4。
- B 错7FEFH:只减了 1,忘了返回地址占 4 个字节。
- C 对SP = 7FF0H − 4 = 7FECH,返回地址存放在 7FECH ~ 7FEFH。
- D 错7FF0H:以为 call 不改变 SP(把返回地址存进寄存器的机器才这样,与题设不符)。
- A. 26 位
- B. 28 位
- C. 49 位
- D. 25 位
- A. 1010
- B. 1111
- C. 0101
- D. 1000
展开解析
考点5.5 屏蔽字
A某级的屏蔽字:屏蔽自己和处理优先级比自己低的,不屏蔽比自己高的。
- 处理优先级比 L0 高的是 L1、L3 → 对应位为 0;L0 自己和比它低的 L2 → 1。
- 屏蔽字(L0 L1 L2 L3)= 1 0 1 0。
- A 对1010。
- B 错1111:按响应优先级设(L0 响应最高,就屏蔽所有),题目要的是新的处理优先级。
- C 错0101:把 1 和 0 的含义弄反了。
- D 错1000:只屏蔽了自己,没屏蔽处理优先级更低的 L2。
- A. 800MB/s
- B. 640MB/s
- C. 66.7MB/s
- D. 533MB/s
一、单项选择题 · 操作系统(第 23 ~ 32 题,每题 2 分,共 20 分)
Ⅰ. 修改页表基址寄存器的值 Ⅱ. 执行陷入(trap)指令 Ⅲ. 关中断 Ⅳ. 读取通用寄存器的值
- A. 仅Ⅰ、Ⅲ
- B. 仅Ⅰ、Ⅱ
- C. 仅Ⅱ、Ⅳ
- D. 仅Ⅰ、Ⅱ、Ⅲ
展开解析
考点5.1 特权指令
A会影响整个系统安全的操作才是特权操作。
- Ⅰ 对页表基址决定当前进程能访问哪些内存,用户若能改它就能访问别人的内存,必须在内核态执行。
- Ⅱ 错陷入指令正是在用户态执行、用来主动进入内核的,它不是特权指令。
- Ⅲ 对关中断会让操作系统失去对 CPU 的控制(时钟中断都进不来),是特权指令。
- Ⅳ 错读通用寄存器是最普通的操作,用户程序随时都在做。
干扰项从哪来:B 是把“用来进入内核的陷入指令”当成了特权指令,又漏了 Ⅲ;C 是把特权与非特权完全判反:选了陷入指令和读寄存器这两个普通操作,漏了真正的特权操作 Ⅰ、Ⅲ;D 是把陷入指令也当成特权指令
- A. T1、T2、T3 都进入阻塞态
- B. 只有 T1 进入阻塞态,T2 或 T3 可以被调度运行
- C. 进程 P 的 PCB 被移入磁盘的阻塞队列
- D. T2、T3 可以与 T1 同时在 CPU 上运行
展开解析
考点6.4 线程
B一对一模型下,内核调度的单位是内核级线程。
- A 错这是多对一模型(用户级线程)的情况:内核只看见进程,一个线程阻塞整个进程都阻塞。
- B 对内核看得见每个线程,T1 阻塞后可以调度同一进程的 T2 或 T3。
- C 错阻塞的是 T1 这个调度实体,挂进阻塞队列的是 T1 的 TCB,而不是整个进程。
- D 错单处理器同一时刻只能运行一个线程;T1 已经阻塞,也不在 CPU 上。
- A. J1
- B. J3
- C. J4
- D. J2
展开解析
考点6.7 调度算法
D响应比 = (等待时间 + 要求服务时间) ÷ 要求服务时间。
- J1:(8 + 6) / 6 ≈ 2.33;J2:(5 + 2) / 2 = 3.5;J3:(2 + 1) / 1 = 3;J4:(1 + 4) / 4 = 1.25。
- 最大的是 J2。
- A 错J1 最早到达:按先来先服务选的。
- B 错J3 运行时间最短:按短作业优先选的。
- C 错J4:把响应比算成了“要求服务时间 ÷ 等待时间”(4 ÷ 1 最大),公式倒过来了。
- D 对J2,响应比 3.5。
- A. 0 个、2 个
- B. 3 个、2 个
- C. 1 个、2 个
- D. 2 个、0 个
展开解析
Avalue ≥ 0 时表示可用资源数;value < 0 时没有可用资源,其绝对值是等待进程数。
- A 对值为负说明资源已全部分出去,可用 0 个;|−2| = 2 个进程在等。
- B 错把初值 3 当成现在可用的资源数;初值只是开始时的数量。
- C 错3 − 2 = 1:把 −2 理解成“又用掉了 2 个”,其实值本身就表示当前状态。
- D 错把 −2 的绝对值当成可用资源数,正好弄反。
- A. 10
- B. 15
- C. 11
- D. 6
展开解析
C最坏情况:每个进程都只差 1 个,此时再有 1 个就能让一个进程完成。
- 每个进程先拿到 3 − 1 = 2 个:共 10 个,全部卡住。
- 再多 1 个,某个进程就能完成并释放资源 → m ≥ 5 × 2 + 1 = 11。
- A 错10 = 5 × 2:恰好让每个进程都差 1 个而全部等待,正是会死锁的最坏情况。
- B 错15 = 5 × 3:满足所有进程同时的最大需求,太多了,不是“至少”。
- C 对11:10 个时可能全部卡住,再多 1 个就一定有进程能拿够 3 个完成。
- D 错6 = 5 + 1:以为每个进程只要先拿到 1 个即可,没有按最大需求考虑最坏情况。
- A. 首次适应
- B. 最佳适应
- C. 最坏适应
- D. 三种算法都能满足
展开解析
C逐个算法模拟。
- 首次适应:60K → 300K 处(剩 60K);25K → 100K 处(剩 25K);70K → 500K 处 → 都能满足。
- 最佳适应:60K → 500K 处(剩 20K);25K → 200K 处(剩 5K);70K → 300K 处 → 都能满足。
- 最坏适应:60K → 300K 处(剩 60K);25K → 500K 处(剩 55K);70K 时最大空闲区只有 60K → 无法满足(程序模拟核对)。
- A 错首次适应第三次从低地址找到 500K 处的 80K 分区,能满足。
- B 错最佳适应把 120K 的大分区留到了最后,70K 能放进去。
- C 对最坏适应总挑最大的分区,把大分区切碎了,留不下 70K。
- D 错最坏适应不能满足,见左边模拟。
- A. {1, 2, 3}
- B. {1, 2, 5}
- C. {1, 2, 4, 5}
- D. {1, 2, 3, 4, 5}
展开解析
考点8.7 工作集
B工作集 = 最近 Δ 次访问中出现过的页面。
- 最近 4 次访问是 5, 2, 5, 1 → 工作集 = {1, 2, 5}。
- A 错{1, 2, 3} 是从序列开头数的 4 次(3, 1, 2, 1),方向弄反了。
- B 对{1, 2, 5}。
- C 错{1, 2, 4, 5}:把窗口理解成“最近 4 个不同的页面”(往前数到 4 才凑够 4 个),窗口数的是访问次数。
- D 错把整个序列出现过的页都算进去了,这是“全部访问过的页”,不是工作集。
- A. 3 个表项,计数各为 1
- B. 1 个表项,计数为 2
- C. 2 个表项,计数各为 1
- D. 1 个表项,计数为 3
展开解析
D系统打开文件表中一个文件只有一项;打开计数记录被打开的次数。
- A 错以为每次 open 都新建一个系统表项;系统表里一个文件只占一项(2020-23)。
- B 错按“打开它的进程个数”计数;P1 打开了两次,计数应把每次 open 都算上。
- C 错按进程建系统表项;每个进程各有一张自己的打开文件表,那才是按进程的。
- D 对1 项,打开计数 3;P1 的打开文件表中有 2 项、P2 中有 1 项,各自记读写指针。
- A. 9,1 次
- B. 3,3 次
- C. 3,1 次
- D. 2,1 次
展开解析
考点10.6 FAT
C沿 FAT 链在内存中查找:逻辑块 0 → 5,1 → 9,2 → 3。
- A 错9 是逻辑块 1:把逻辑块号从 1 开始数了。
- B 错3 次:把 FAT 当成隐式链接,以为要逐块读盘才能找到下一块;FAT 在内存里,只读目标块。
- C 对物理块 3,读 1 次。
- D 错以为逻辑块号就是物理块号,那是连续分配从 0 号块开始时才有的情况。
- A. 1000μs
- B. 1500μs
- C. 870μs
- D. 1050μs
展开解析
D单缓冲时每块的“周期” = max(C, T) + M,再加上第一块和最后一块的首尾时间。
- 第 1 块:T + M = 100μs 后进入用户区,此后每 max(80, 50) + 20 = 100μs 出一块。
- 总时间 = T + M + C + (10 − 1) × 100 = 80 + 20 + 50 + 900 = 1050μs(事件模拟核对)。
- A 错1000μs = 10 × 100:每块按周期 100μs 算,漏了最后一块的 CPU 处理 50μs。
- B 错1500μs = 10 × (80 + 20 + 50):完全串行,没有利用“CPU 处理与磁盘读入并行”。
- C 错870μs 是双缓冲的结果(周期变为 max(T, M + C) = 80μs)。
- D 对1050μs。
二、综合应用题(第 43 ~ 46 题,共 38 分)
做完四道题后再展开答案,按评分细则给自己打分,把分数填进每题答案末尾的“自评得分”框;点上方的“交卷”时,选择题自动判分,综合题按你填的自评分计入,计组、操作系统分别统计。
某 64 位计算机按字节编址,采用小端方式,C 语言中 char、short、int、double 分别占 1、2、4、8 个字节,int 用补码表示,float、double 分别采用 IEEE 754 单精度、双精度格式。编译器按“每个成员按自身大小对齐、结构体总长按最大成员的大小对齐”的规则安排结构体。有如下程序段:
struct rec { char c; int i; short s; double d; } r; // r 的起始地址为 0000 1000H
r.i = -10;
r.s = (short) r.i;
r.c = (char) 300;
r.d = r.i;
float f = 16777217;(1) r 的各成员相对于 r 的起始地址的偏移量分别是多少?sizeof(r) 是多少?若调整成员的先后顺序,sizeof 最小可以是多少?(3 分)
(2) 执行 r.i = -10; 后,地址 0000 1004H ~ 0000 1007H 中依次存放的是什么(用十六进制表示)?(2 分)
(3) 程序段执行后,r.s 和 r.c 的值各是多少?说明理由。(2 分)
(4) r.d 的机器数是多少(用十六进制表示)?f 的机器数是多少?f 的值与 16777217 是否相等?为什么?(4 分)
做完再点开:满分答案与评分细则
考点2.7 大小端与对齐2.2 类型转换2.6 IEEE 754
c 偏移 0;i 要按 4 对齐 → 偏移 4(中间填 3 字节);s 偏移 8;d 要按 8 对齐 → 偏移 16(10 ~ 15 填充);共 24 字节,已是 8 的倍数 → sizeof(r) = 24。
按 double、int、short、char 的顺序排:0、8、12、14,共 15 字节,补齐到 8 的倍数 → 最小 16(gcc 实测)。
−10 的 32 位补码为 FFFF FFF6H。小端方式低字节放低地址:1004H ~ 1007H 依次为 F6H、FFH、FFH、FFH。
小端只颠倒字节的顺序,字节内部的位顺序不变,不能写成 6FH。
r.s:int → short 截断保留低 16 位 FFF6H,按补码读是 −10(数值没变,因为 −10 在 short 范围内)。
r.c:300 = 0000 012CH,截断保留低 8 位 2CH = 44。
−10 = −1.01B × 2³:S = 1,阶码 = 3 + 1023 = 1026 = 100 0000 0010B,尾数 0100…0 → r.d = C024 0000 0000 0000H。
16777217 = 2²⁴ + 1 = 1.000…0001B × 2²⁴,需要 24 位小数,float 只有 23 位:舍去的部分正好是“一半”,按就近舍入到偶数,尾数末位保持 0 → f = 1.0 × 2²⁴,阶码 24 + 127 = 151 = 1001 0111B → f = 4B80 0000H,值为 16777216,与 16777217 不相等(gcc 实测)。原因:float 只有 24 位有效位。
双精度的偏置是 1023,不是 127;阶码字段 11 位。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | 四个偏移量正确 | 1 |
| sizeof = 24 | 1 | |
| 最小 16 | 1 | |
| (2) | F6H FFH FFH FFH(顺序正确) | 2 |
| (3) | r.s = −10 | 1 |
| r.c = 44,说明截断 | 1 | |
| (4) | r.d 机器数 | 2 |
| f 机器数 4B800000H | 1 | |
| 不相等,说明有效位不足 / 舍入 | 1 | |
| 合计 | 11 |
类型转换题的三件套:宽度变化(截断 / 扩展)、表示方法变化(整数 ↔ 浮点、舍入)、存放方式(大小端、对齐)。
某计算机按字节编址,主存地址 32 位。数据 Cache 的数据区容量 2KB,采用 2 路组相联映射,块大小 32B,LRU 替换,写回法,写不命中时采用写分配,初始为空。int 型占 4 字节。二维数组 A、B 均为 int[32][32],按行优先存放,A 的起始地址为 0001 0000H,B 紧接在 A 之后。有如下程序(i、j 都在寄存器中,只有对 A、B 的访问经过数据 Cache):
for (i = 0; i < 32; i++)
for (j = 0; j < 32; j++)
B[j][i] = A[i][j]; // 矩阵转置:读 A 一次、写 B 一次(1) 主存地址中 Cache 的标记、组号、块内地址各占几位?(2 分)
(2) A[i][j] 与 B[i][j] 是否映射到同一个 Cache 组?为什么?(2 分)
(3) 执行该程序时,读 A 的命中率和写 B 的命中率各是多少?说明理由。(4 分)
(4) 改用 8 × 8 分块转置:外两层循环按 8 × 8 的子块遍历,内两层在子块内按行读 A、按列写 B。写 B 的缺失次数和命中率各变为多少?为什么分块能提高命中率?(4 分)
做完再点开:满分答案与评分细则
考点8.3 地址划分8.3 组相联11.6 按行与按列8.9 程序的局部性
块 32B → 块内地址 5 位;行数 2KB ÷ 32B = 64,组数 64 ÷ 2 = 32 → 组号 5 位;标记 32 − 5 − 5 = 22 位。
B 的起始地址比 A 大 32 × 32 × 4B = 4KB。组号由地址的第 9 ~ 5 位决定,地址每增加 32 组 × 32B = 1KB,组号就循环一圈;4KB 是 1KB 的整数倍,所以 A[i][j] 和 B[i][j] 地址相差 4KB、组号相同,映射到同一组。
读 A:按行访问,一块装 8 个 int,每块第一次访问缺失、后 7 次命中(这一块每次循环都被访问,在 LRU 下不会被换出)→ 命中率 7/8 = 87.5%。
写 B:按列访问,B[j][i] 与 B[j+1][i] 相差一行 128B,每次都落在不同的块。固定 i 时一列要用到 32 个块;块号相差 4(128B ÷ 32B),组号依次相差 4,只用到 32 ÷ 4 = 8 个组,每组要放 4 个块,而每组只有 2 行(还要与 A 的块共用)。等到下一列 i + 1 要用同一批块时,它们早已被替换 → 每次写都缺失,命中率 0(模拟核对:读 A 896 / 1024,写 B 0 / 1024)。
常见错误:只看“块里有 8 个元素”就写 B 的命中率也是 7/8;按列访问要先算“同一组要放几个块、放不放得下”。
在一个 8 × 8 子块里,写 B 的 8 列只涉及 B 的 8 行,每行的 8 个元素正好在同一块中:8 个块分在 8 个不同的组,每组只放 1 块 B(加上 A 的块也不超过 2 路),处理完这个子块之前不会被换出。于是 B 的每个块只在第一次写时缺失,缺失次数 = B 的块数 = 32 × 32 ÷ 8 = 128 次,命中率 = 1 − 128/1024 = 87.5%(模拟核对)。
原因:分块把“访问同一批块”的时间拉近了,让被访问的块在被替换之前就把它用完(提高了时间局部性)。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | 标记 22、组号 5、块内 5 | 2 |
| (2) | 同组,理由(相差 4KB 是 1KB 的整数倍) | 2 |
| (3) | 读 A 87.5% 及理由 | 2 |
| 写 B 0 及理由(每组要放的块数超过 2 路) | 2 | |
| (4) | 缺失 128 次、命中率 87.5% | 2 |
| 说明分块提高了局部性 | 2 | |
| 合计 | 12 |
按列访问的 Cache 题,关键一步是“同一组要放几个块”:块号的间隔决定用到几个组,组数 × 路数决定放不放得下。
某数据库服务器上有若干查询进程和更新进程共享数据库 DB。要求:① 多个查询进程可以同时查询 DB,但同时进行查询的进程不能超过 3 个;② 更新进程必须独占 DB,更新时不能有查询或其他更新;③ 写者优先:只要有更新进程在等待,新到的查询进程就不能开始查询。
(1) 请用信号量机制实现查询进程和更新进程的同步与互斥,说明所用信号量的含义和初值。(6 分)
(2) 若把更新进程中的两个 P 操作次序颠倒为 P(rw); P(w);,可能出现什么问题?(2 分)
做完再点开:满分答案与评分细则
考点7.4 PV 五步法11.9 PV 训练营7.3 信号量
semaphore cap = 3; // 同时进行的查询不超过 3 个
semaphore w = 1; // 写优先:更新进程一来就先占住它,后来的查询进不了
semaphore rw = 1; // 数据库互斥(查询作为一个整体与更新互斥)
semaphore mutex = 1; // 保护 count
int count = 0; // 正在查询的进程数
查询进程() { 更新进程() {
P(cap); P(w);
P(w); P(rw);
P(mutex); 更新数据库;
if (count == 0) P(rw); V(rw);
count++; V(w);
V(mutex); }
V(w);
查询数据库;
P(mutex);
count--;
if (count == 0) V(rw);
V(mutex);
V(cap);
}思路:cap 限制同时查询的人数(计数信号量);count + mutex + rw 是读者—写者的经典结构;再加一个 w,读、写进程都先 P(w),更新进程拿到 w 之后一直到更新完才释放,期间新来的查询全被挡在 P(w) 上,从而写者优先。
程序验证:对 4 个查询进程、2 个更新进程各执行 2 轮做状态空间穷举(约 189 万个状态),没有出现超过 3 个同时查询、更新与查询同时进行、两个更新同时进行,也没有死锁(verify/08/p13/mock/a45_pv.py)。
查询进程必须在开始查询之前就 V(w),否则所有查询只能一个一个进行,违反“可以同时查询”。
可能死锁。设查询进程 R 已 P(cap)、P(w),正在 P(mutex) 之前;更新进程 U 先执行 P(rw) 成功,再执行 P(w) 被阻塞(w 在 R 手里);R 接着做 P(mutex)、发现 count == 0,执行 P(rw) 被阻塞(rw 在 U 手里)。R 等 U 的 rw,U 等 R 的 w,形成循环等待。穷举程序也找到了这样的死锁状态。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | 信号量 cap、w、rw、mutex 及 count 的含义与初值 | 2 |
| 查询进程:cap 的 P / V、计数与 rw 的处理正确 | 2 | |
| 更新进程正确,且实现写者优先(先 P(w) 再 P(rw)) | 2 | |
| (2) | 会死锁,并给出循环等待的过程 | 2 |
| 合计 | 8 |
复杂的读者—写者题 = 经典结构(count + mutex + rw)+ 额外约束各加一个信号量(人数上限 → 计数信号量,写者优先 → 排队用的 w)。
某系统按字节编址,虚拟地址 32 位,页面大小 4KB,采用二级页表:页目录号 10 位、页表索引 10 位、页内偏移 12 位,页表项 4B,页目录和用到的二级页表都常驻内存。进程 P 依次访问下列虚拟地址(十六进制):
0040 2A10、0040 4C08、0080 1C30、0040 4C0C、0040 2A14、0041 2000、0080 1C34、0040 2A18、0040 3B20、0080 1C38
(1) 虚拟地址 0080 1C30H 的页目录号、页表索引、页内偏移分别是多少?(2 分)
(2) 访问上述地址,P 至少需要几张二级页表?页目录和这些二级页表共占多少内存?(2 分)
(3) 系统为 P 的数据页分配 3 个页框(初始为空,页表不占这 3 个页框),采用 LRU 置换算法。写出每次访问是否缺页(缺页时写出被淘汰的页),共缺页几次?(2 分)
(4) 若改用 FIFO 置换算法,共缺页几次?(1 分)
做完再点开:满分答案与评分细则
0080 1C30H = 0000 0000 10 | 00 0000 0001 | 1100 0011 0000B:页目录号 2,页表索引 1,页内偏移 C30H。
10 个地址的虚页号(高 20 位)为 00402、00404、00801、00404、00402、00412、00801、00402、00403、00801(十六进制),页目录号(虚页号的高 10 位)只有 1 和 2 两种 → 需要 2 张二级页表。每张页表、页目录各占 210 × 4B = 4KB,共 3 × 4KB = 12KB。
页框内容(按最近使用排序)与缺页:00402 缺;00404 缺;00801 缺;00404 命中;00402 命中;00412 缺,淘汰 00801;00801 缺,淘汰 00404;00402 命中;00403 缺,淘汰 00412;00801 命中。共缺页 6 次(程序模拟核对)。
00412 进来时淘汰最早装入的 00402,于是第 8 次访问 00402 时又缺页……共缺页 7 次(程序模拟核对)。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | 页目录号 2、页表索引 1、偏移 C30H | 2 |
| (2) | 2 张二级页表 | 1 |
| 共 12KB | 1 | |
| (3) | 过程正确 | 1 |
| 6 次 | 1 | |
| (4) | 7 次 | 1 |
| 合计 | 7 |
多级页表题先把虚拟地址写成二进制按位切;“需要几张二级页表”看出现了几种页目录号。
答案速查(卷 A)
计组:12 B 13 C 14 A 15 D 16 B 17 D 18 A 19 C 20 B 21 A 22 D
操作系统:23 A 24 B 25 D 26 A 27 C 28 C 29 B 30 D 31 C 32 D
综合题分值:第 43 题 11 分,第 44 题 12 分,第 45 题 8 分,第 46 题 7 分
13.6全真模拟卷 B(计组 + 操作系统 80 分 · 限时 100 分钟)
本卷题号与分值同卷 A:计组单选第 12 ~ 22 题、操作系统单选第 23 ~ 32 题,计组综合第 43、44 题(11 + 12 分),操作系统综合第 45、46 题(8 + 7 分),合计 80 分,限时 100 分钟。第 43 题的反汇编来自真实编译(WSL gcc -m32 -O0),第 44、45 题由流水线模拟和 PV 状态空间穷举核对(verify/08/p13/mock_b.py、mock/b43.c、mock/pipesim.py、mock/b45_pv.py)。
一、单项选择题 · 计组(第 12 ~ 22 题,每题 2 分,共 22 分)
- A. 50%
- B. 200%
- C. 25%
- D. 75%
展开解析
A峰值 = 主频 × 每周期浮点运算次数;实测 = 运算次数 ÷ 时间。
- 峰值 = 2 × 10⁹ × 4 = 8 GFLOPS。
- 实测 = 6 × 10⁹ ÷ 1.5s = 4 GFLOPS。
- 4 ÷ 8 = 50%。
- A 对50%。
- B 错200%:峰值没乘 SIMD 宽度 4,按 2 GFLOPS 算,超过 100% 本身就说明算错了。
- C 错25%:峰值按“乘加算两次”取了 16 GFLOPS,题目说的是每周期最多 4 次。
- D 错75% = 6 ÷ 8:实测值忘了除以用时 1.5s。
- A. 0,不溢出
- B. 0,溢出
- C. 4294967296,不溢出
- D. 2147483647,溢出
展开解析
考点2.5 乘法溢出
B真实乘积 2³² 需要 33 位,低 32 位全是 0。
- A 错结果 0 对,但“结果为 0 就没溢出”是错的:判断乘法溢出要看完整乘积能否用 32 位表示,2³² 超出了 int 的范围。
- B 对低 32 位为 0(WSL gcc -fwrapv 实测),真实乘积 4294967296 超出 int 范围,溢出。
- C 错4294967296 = 2³² 根本放不进 32 位的 int。
- D 错以为溢出时结果会“饱和”到最大值,补码运算只保留低位,不会饱和。
float f = 0.1f;(IEEE 754 单精度,就近舍入),f 在机器中实际存放的值与十进制数 0.1 相比( )。- A. 小于 0.1
- B. 等于 0.1
- C. 大于 0.1
- D. 无法确定,与机器有关
展开解析
C0.1 = 0.000110011001100…B 无限循环,要舍入到 24 位有效位。
- 0.1 = 1.1001 1001 1001 1001 1001 100 | 1100…B × 2⁻⁴(竖线后是要舍去的部分)。
- 舍去部分 1100… 大于一半 → 进 1,尾数末段 …100 变成 …101 → 机器数 3DCC CCCDH。
- 实际值 0.100000001490…,大于 0.1(gcc 实测)。
- A 错以为舍入就是截断(只舍不入);就近舍入时舍去部分超过一半要进位。
- B 错0.1 化成二进制是无限循环小数,float 不能精确表示。
- C 对舍入时进了 1,存的值比 0.1 略大。
- D 错IEEE 754 规定了格式和默认舍入方式,结果在所有遵循标准的机器上都一样。
- A. C₄ = 0
- B. C₄ = 1,由 G₃ 项产生
- C. C₄ = 1,由 P₃P₂P₁P₀C₀ 项产生
- D. C₄ = 1,由 P₃P₂G₁ 项产生
展开解析
D先求各位的 G、P,再看哪一项为 1。
- G = (G₃G₂G₁G₀) = 0010,P = 1101。
- G₃ = 0,P₃G₂ = 0,P₃P₂G₁ = 1 · 1 · 1 = 1,C₄ = 1(核对:1011 + 0110 = 10001,确有进位)。
- A 错只看了最高位 A₃B₃ = 1 · 0 = 0 有没有进位,忽略了低位产生、一路传上来的进位。
- B 错G₃ = A₃B₃ = 0,第 3 位自己不产生进位。
- C 错C₀ = 0,这一项为 0;而且 P₁ = 0。
- D 对第 1 位产生进位(G₁ = 1),经第 2、3 位传递(P₂ = P₃ = 1)到 C₄。
- A. 64ms
- B. 62.5μs
- C. 31.25μs
- D. 62.5ns
展开解析
考点8.2 刷新
B异步刷新:把 64ms 平均分给 1024 行。
- 间隔 = 64ms ÷ 1024 = 0.0625ms = 62.5μs。
- A 错64ms 是刷新周期(所有行都刷一遍的期限),集中刷新时才在这段时间里集中刷一次。
- B 对62.5μs。
- C 错31.25μs = 64ms ÷ 2048:多除了一个 2,以为每行要刷新两次。
- D 错62.5ns:把 ms 换算成 ns 时少乘了 10³。
- A. 220
- B. 820
- C. 700
- D. 600
展开解析
考点8.3 写策略
C全写法每次写都写主存;非写分配写缺失时不调块。
- 读缺失 1000 × 10% = 100 次,各调入一块:100 次。
- 写:全写法 600 次都要写主存(命中与否都写),非写分配不调块:600 次。
- 共 700 次。
- A 错220 = 100 + 120:把写也当成只在缺失时才访存(写回法的思路)。
- B 错820 = 100 + 600 + 120:写缺失时又调入一块,那是写分配。
- C 对700。
- D 错600 只算了写,漏了 100 次读缺失调块。
- A. 4MB,256
- B. 4KB,1024
- C. 1MB,256
- D. 4MB,1
展开解析
考点8.6 段页式
A段内地址 = 页号 + 偏移;每个段一张页表。
- A 对段内地址 22 位 → 段长最大 2²² = 4MB;段号 8 位 → 最多 256 个段,每段一张页表 → 256 张。
- B 错把段长算成一页(4KB)、页表数按段内页号位数算成 1024,把“段”和“页”混了。
- C 错1MB = 2²⁰:段内页号和页内偏移各当成 10 位,偏移应是 12 位。
- D 错以为整个进程只有一张页表,那是纯分页;段页式是每个段一张。
mov eax, 0x12345678 的操作码为 B8H,其后紧跟 32 位立即数。该机器采用小端方式,这条指令按地址从低到高的机器码为( )。- A. B8 12 34 56 78
- B. B8 78 56 34 12
- C. B8 87 65 43 21
- D. 78 56 34 12 B8
ADD R1, (R2)(R1 ← (R1) + ((R2)))时,执行阶段至少需要的节拍数为( )。- A. 3
- B. 5
- C. 6
- D. 4
展开解析
D一个节拍内总线只能传一个数据;读主存那一拍总线空着,可以顺便把 R1 送到 Y。
- ① R2out,MARin。
- ② 读主存:M(MAR) → MDR;同时 R1out,Yin(内部总线空闲,可并行)。
- ③ MDRout,ALU 加,Zin。
- ④ Zout,R1in。共 4 拍。
- A 错3:以为 MDR 能直接加到 R1 上;ALU 的两个输入一个来自 Y、一个来自总线,结果必须经 Z 再送回 R1。
- B 错5:把 R1 → Y 单独安排了一拍,没利用读主存时内部总线空闲的那一拍。
- C 错6:又把 MDR 先送到 Y、再把 R1 经总线送 ALU,多了一次总线传送。
- D 对4 拍。
- A. 002CH
- B. 0058H
- C. 00B0H
- D. 02C0H
展开解析
C向量地址 = 表的起始地址 + 类型号 × 每项字节数。
- 2CH × 4 = B0H → 向量地址 00B0H,该单元里存的才是服务程序的入口地址。
- A 错002CH:直接拿类型号当地址,忘了乘每项的字节数。
- B 错0058H = 2CH × 2:每项按 2 字节算了。
- C 对00B0H。
- D 错02C0H = 2CH × 16:把类型号左移了 4 位(当成十六进制多补一个 0)。
- A. 1%
- B. 4%
- C. 1.024%
- D. 2%
展开解析
考点9.4 程序查询
A每秒至少查询(读取)的次数 = 数据率 ÷ 每次读的字节数。
- 每秒至少 20 × 10³ ÷ 4 = 5000 次。
- 5000 × 400 = 2 × 10⁶ 个周期,占 2 × 10⁶ ÷ (2 × 10⁸) = 1%。
- A 对1%:每秒 5000 次、每次 400 周期。
- B 错4%:以为每次只读 1 字节,查询次数多算了 4 倍。
- C 错1.024%:把 20KB/s 按 20 × 2¹⁰ 算;题目已说明 K = 10³。
- D 错2%:把“查询”和“读数据”各算了 400 个周期,题目说的是两者合计 400 个周期。
一、单项选择题 · 操作系统(第 23 ~ 32 题,每题 2 分,共 20 分)
- A. 系统调用通过执行陷入(trap)指令进入内核
- B. 系统调用的参数可以通过通用寄存器传递给内核
- C. 用户程序每调用一次库函数 printf,都会立即引起一次 write 系统调用
- D. 系统调用完成后,返回到陷入指令的下一条指令继续执行
展开解析
考点5.6 系统调用
C库函数在用户态运行,它自己决定什么时候真正发起系统调用。
- A 对陷入指令在用户态执行,CPU 随即切到内核态。
- B 对寄存器传参最常用,参数多时也可以放在内存里、只传地址。
- C 错本题选它。printf 先把字符串格式化到用户态的缓冲区里,缓冲区满、遇到换行(终端)或调用 fflush 时才发起 write 系统调用。
- D 对陷入属于自陷,断点是下一条指令。
for (int i = 0; i < 2; i++)
fork();
printf("x\n");- A. 2
- B. 3
- C. 6
- D. 4
展开解析
考点6.3 fork
D每次 fork 后,父子进程都继续执行后面的代码。
- i = 0:1 个进程 fork → 2 个;i = 1:2 个各 fork → 4 个。
- 循环结束后 4 个进程各执行一次 printf → 4 行(WSL 实测)。
- A 错2:只数了 fork 调用的次数。
- B 错3:fork 次数加上原来的进程,没想到第二轮两个进程都会 fork。
- C 错6 = 2 + 4 是 printf 写在循环里面时的结果,本题 printf 在循环之后。
- D 对4 行。
- A. 第 2 级,2 次
- B. 第 3 级,3 次
- C. 第 3 级,2 次
- D. 第 1 级,6 次
展开解析
B逐级扣除时间片。
- 第 1 级运行 2,剩 9,降到第 2 级。
- 第 2 级运行 4,剩 5,降到第 3 级。
- 第 3 级时间片 8 ≥ 5,运行完成 → 第 3 级、共 3 次。
- A 错第 2 级结束时只运行了 2 + 4 = 6,还差 5。
- B 对第 3 级,3 次。
- C 错级数对了,但第 1、2、3 级各被调度一次,共 3 次。
- D 错以为只有一个进程时就一直留在第 1 级按时间片 2 轮转;多级反馈用完时间片就降级,与有没有别的进程无关。
- A. 4 个,2 次
- B. 3 个,1 次
- C. 2 个,2 次
- D. 4 个,1 次
展开解析
考点7.4 前驱关系
A每条边一个信号量:前驱执行完 V,后继开始前 P。
- A 对4 条边 → 4 个信号量;S4 有两个前驱 S2、S3,要 P 两次(两个都完成才执行)。
- B 错按结点个数减 1 设信号量,又以为 S4 只等一个前驱。
- C 错按“有后继的结点”设信号量(S1、S2/S3 合用),这样 S2、S3 共享信号量后无法区分是谁完成。
- D 错信号量个数对了,但 S4 只 P 一次就会在 S2、S3 只完成一个时提前执行。
- A. 没有死锁
- B. P1、P2
- C. P1、P2、P3、P4
- D. P3、P4
展开解析
考点7.7 死锁检测
B先算 Available,再反复找“请求 ≤ Work”的进程,假设它完成并释放资源。
- Available = (2,2,1) − (2,2,1) = (0,0,0)。
- P3 请求为 0 → 完成,Work = (1,0,1);P4 请求 (0,0,1) ≤ Work → 完成,Work = (1,1,1)。
- P1 要 2 个 R2、P2 要 2 个 R1,都不能满足 → P1、P2 死锁(程序核对)。
- A 错以为资源图中没有环就没死锁,或误把“请求”当成可以先满足一部分;P1、P2 各要 2 个、只能拿到 1 个,永远等下去。
- B 对P1、P2。
- C 错看到 Available = (0,0,0) 就判全部死锁;P3 不再请求资源,能运行完并释放。
- D 错把能完成的进程和死锁进程弄反了:P3、P4 恰恰可以完成。
- A. (0, 499)
- B. (3, 79)
- C. (2, 1000)
- D. (1, 119)
展开解析
C段内地址的合法范围是 0 ~ 段长 − 1。
- A 错499 < 500,合法,物理地址 2499。
- B 错79 < 80,合法,物理地址 5079。
- C 对段 2 的段长 1000,段内地址只能是 0 ~ 999,1000 越界。
- D 错119 < 120,合法,物理地址 3219。
- A. P8
- B. P3
- C. P5
- D. P6
展开解析
D第一轮只找 (0, 0),不改任何位。
- 从 P8 开始第一轮:P8 (1,0)、P3 (1,1)、P5 (0,1) 都不是,P6 (0,0) 是 → 淘汰 P6(程序模拟核对)。
- A 错P8:按指针位置先换(FIFO 的想法),或以为第一轮就要把访问位清 0 再回到 P8。
- B 错P3 (1,1) 刚被访问又被修改过,是最不该淘汰的。
- C 错P5 是简单 CLOCK 的结果(只看访问位,清掉 P8、P3 后第一个 A = 0 的是 P5);改进型优先找没被修改过的 (0, 0)。
- D 对P6:第一轮扫描就找到了 (0, 0) 的页,不必清任何访问位。
- A. 7
- B. 4
- C. 6
- D. 8
展开解析
A每进一级目录:读该目录的索引结点 + 读目录文件;最后读 c 的索引结点和数据块。
- 读根目录文件(1)→ 找到 a 的索引结点号,读 a 的索引结点(2)→ 读 a 的目录文件(3)→ 读 b 的索引结点(4)→ 读 b 的目录文件(5)→ 读 c 的索引结点(6)→ 读 c 的第 0 块(7)。
- A 对7 次。
- B 错4:只数了三个目录文件和一个数据块,忘了 FCB 与索引结点分离时每一级都要先读索引结点。
- C 错6:漏了最后读 c 的数据块(或漏了读 c 的索引结点)。
- D 错8:把已在内存的根目录索引结点也算了一次读盘。
- A. 12500B
- B. 25000B
- C. 200000B
- D. 50000B
- A. 用户层 I/O 软件
- B. 设备驱动程序
- C. 设备独立性软件
- D. 中断处理程序
展开解析
C逻辑设备名 → 物理设备名,靠逻辑设备表 LUT。
- A 错用户层只负责发出请求(如调用库函数),它看到的只有逻辑设备名。
- B 错驱动程序面对的已经是某一台具体的物理设备,只管怎么操作它的控制器。
- C 对设备独立性软件查逻辑设备表,把逻辑名映射为物理设备,这正是“设备独立性”的实现。
- D 错中断处理程序只在设备完成或出错时被唤起,不参与设备的选择。
二、综合应用题(第 43 ~ 46 题,共 38 分)
做完四道题后再展开答案,按评分细则给自己打分,把分数填进每题答案末尾的“自评得分”框;点上方的“交卷”时,选择题自动判分,综合题按你填的自评分计入,计组、操作系统分别统计。
下列 C 函数统计数组 a 中负数的个数。在 IA-32 机器上用 gcc(-m32 -O0)编译后,count_neg 的反汇编如下(Intel 格式,第二列为机器码,十六进制)。
int count_neg(int a[], int n) {
int i, c = 0;
for (i = 0; i < n; i++)
if (a[i] < 0) c++;
return c;
}08049186 <count_neg>: 8049186: 55 push ebp 8049187: 89 e5 mov ebp,esp 8049189: 83 ec 10 sub esp,0x10 804918c: c7 45 fc 00 00 00 00 mov DWORD PTR [ebp-0x4],0x0 8049193: c7 45 f8 00 00 00 00 mov DWORD PTR [ebp-0x8],0x0 804919a: eb 1d jmp 80491b9 804919c: 8b 45 f8 mov eax,DWORD PTR [ebp-0x8] 804919f: 8d 14 85 00 00 00 00 lea edx,[eax*4+0x0] 80491a6: 8b 45 08 mov eax,DWORD PTR [ebp+0x8] 80491a9: 01 d0 add eax,edx 80491ab: 8b 00 mov eax,DWORD PTR [eax] 80491ad: 85 c0 test eax,eax 80491af: 79 04 jns 80491b5 80491b1: 83 45 fc 01 add DWORD PTR [ebp-0x4],0x1 80491b5: 83 45 f8 01 add DWORD PTR [ebp-0x8],0x1 80491b9: 8b 45 f8 mov eax,DWORD PTR [ebp-0x8] 80491bc: 3b 45 0c cmp eax,DWORD PTR [ebp+0xc] 80491bf: 7c db jl 804919c 80491c1: 8b 45 fc mov eax,DWORD PTR [ebp-0x4] 80491c4: c9 leave 80491c5: c3 ret
(1) 局部变量 i、c 分别存放在什么位置(用相对 ebp 的地址表示)?参数 a、n 呢?(2 分)
(2) jl 指令的机器码为 7C DB,其中 DB 是什么?说明目标地址 804919CH 是怎样算出来的。jns 的转移目标又是怎样得到的?(3 分)
(3) 若调用 count_neg(b, 6),数组 b 中恰有 3 个负数,则地址为 80491BFH 的 jl 指令共执行几次?其中几次发生转移?地址为 80491B1H 的 add 指令执行几次?(3 分)
(4) 指令 mov eax, DWORD PTR [eax] 和 add DWORD PTR [ebp-0x4], 0x1 执行时各访问主存(数据)几次?(2 分)
(5) 从这段代码看,该处理器是 CISC 还是 RISC?给出一条依据。(1 分)
做完再点开:满分答案与评分细则
考点3.3 机器级代码3.2 相对寻址11.3 指令与机器级代码
先执行的 mov [ebp-0x4], 0 对应 c = 0;循环变量 i 在 [ebp−0x8](它在 80491B9H 处与 n 比较、在 80491B5H 处加 1)。所以 i 在 [ebp−8]、c 在 [ebp−4]。参数 a 在 [ebp+8]、n 在 [ebp+0CH]([ebp+4] 是返回地址)。
DB 是 8 位补码表示的相对偏移量,值为 −25H(−37)。jl 自身占 2 字节,执行时 PC 已指向下一条 80491C1H,目标 = 80491C1H − 25H = 804919CH。jns 的偏移 04:下一条地址 80491B1H + 4 = 80491B5H(跳过 c++)。
偏移量相对的是下一条指令的地址;按 jl 自身的地址 80491BFH 去减会差 2。
开始先 jmp 到 80491B9H 判断 i < n。i = 0 ~ 5 时条件成立 6 次、i = 6 时不成立 1 次 → jl 执行 7 次,转移 6 次。80491B1H 的 c++ 只在 a[i] < 0 时执行 → 3 次。
mov eax, [eax]:读一次 → 1 次。add [ebp−4], 1:先读出 c、加 1 后写回 → 2 次(一读一写)。
CISC。依据:① 指令长度不固定(1 ~ 7 字节);② 运算指令可以直接访问存储器(如 add [ebp−4], 1、cmp eax, [ebp+0CH]),不是只有 Load / Store 才访存。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | i、c 的位置 | 1 |
| a、n 的位置 | 1 | |
| (2) | DB 是补码偏移 −37(−25H) | 1 |
| jl 目标的计算(以下一条指令地址为基准) | 1 | |
| jns 目标 80491B5H | 1 | |
| (3) | jl 执行 7 次 | 1 |
| 转移 6 次 | 1 | |
| add 执行 3 次 | 1 | |
| (4) | 1 次 | 1 |
| 2 次 | 1 | |
| (5) | CISC 及依据 | 1 |
| 合计 | 11 |
读反汇编先找循环的“骨架”:初始化 → 跳到条件判断 → 循环体 → 回到条件判断;再按“下一条指令地址 + 偏移”核对每个转移。
某 RISC 处理器采用 IF、ID、EX、MEM、WB 五段流水线,按序发射,每段 1 个时钟周期。寄存器堆“前半周期写、后半周期读”;分支指令在 EX 段末确定是否转移,在此之前暂停取下一条指令(阻塞到确定为止)。下面的循环对数组求和(x10 指向数组,x11 为元素个数,x6 为累加和):
loop: lw x5, 0(x10) # I1
add x6, x6, x5 # I2
addi x10, x10, 4 # I3
addi x11, x11, -1 # I4
bne x11, x0, loop # I5(1) 写出循环中所有的写后读(RAW)相关,包括相邻两轮之间的相关。(2 分)
(2) 若不采用转发,画出第一轮循环的时空图。从一轮的 I1 取指到下一轮的 I1 取指相隔几个时钟周期?(4 分)
(3) 若采用转发(ALU 结果和 load 数据都能转发到 EX 段输入,但 load 后紧跟使用其结果的指令仍须阻塞 1 个周期),每轮相隔几个时钟周期?(3 分)
(4) 在 (3) 的条件下循环执行 100 次,共需多少个时钟周期?平均 CPI 是多少?若把 I3 调到 I1 与 I2 之间,每轮相隔几个周期?为什么调整后结果仍然正确?(3 分)
做完再点开:满分答案与评分细则
考点4.4 冒险的对策4.4 三种约定数阻塞4.4 实际 CPI11.5 流水线题
本轮内:I1 → I2(x5),I4 → I5(x11)。相邻两轮之间:I3 → 下一轮 I1(x10),I2 → 下一轮 I2(x6),I4 → 下一轮 I4(x11)。
只有距离很近的相关才会造成阻塞,但题目问“所有”相关时,跨轮的也要写。
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| lw | IF | ID | EX | MEM | WB | ||||||||
| add | IF | 阻 | 阻 | ID | EX | MEM | WB | ||||||
| addi x10 | IF | 阻 | 阻 | ID | EX | MEM | WB | ||||||
| addi x11 | IF | ID | EX | MEM | WB | ||||||||
| bne | IF | 阻 | 阻 | ID | EX | MEM | WB | ||||||
| 下一轮 lw | IF |
I2 要读 I1 写的 x5:I1 在第 5 周期 WB,I2 最早在第 5 周期(后半周期)读,ID 阻塞 2 拍;I3、I4 随之后移;I5 要读 I4 写的 x11,I4 在第 10 周期 WB,I5 的 ID 在第 10 周期;I5 在第 11 周期 EX 段末确定转移,下一轮 I1 在第 12 周期取指。每轮相隔 11 个周期(流水线模拟核对)。
I2 用 I1 的 load 结果:阻塞 1 拍;I5 用 I4 的 x11:EX 段转发,不阻塞;但 I5 的 EX 在第 8 周期,下一轮 I1 第 9 周期才能取指。每轮相隔 8 个周期。
100 轮共 802 个周期(最后一轮的 I5 在第 802 周期写回,程序模拟核对),500 条指令,CPI = 802 ÷ 500 ≈ 1.6。
调为 I1、I3、I2、I4、I5 后,I2 与 I1 之间隔了一条指令,load 结果可以及时转发,不再阻塞,每轮相隔 7 个周期(100 轮 702 周期)。结果仍正确:I3 改的是 x10,而 I1 在调整前后都在 I3 之前读 x10;I2 与 I3 之间没有相关。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | 本轮两对、跨轮相关(至少写出 I3 → 下一轮 I1) | 2 |
| (2) | 时空图正确 | 2 |
| 11 个周期 | 2 | |
| (3) | 8 个周期,说明 load-use 阻塞 1 拍、分支等 EX | 3 |
| (4) | 802 周期、CPI 约 1.6 | 1 |
| 调序后 7 个周期 | 1 | |
| 说明调序不改变结果 | 1 | |
| 合计 | 12 |
循环题按“每轮相隔几拍”算总时间:数据相关决定阻塞,分支决定下一轮何时取指;调整指令顺序是编译器的“软件插空”。
某生产线有三类进程:生产进程 P1 每次生产 1 个零件放入缓冲区 B1(最多放 3 个零件);组装进程 P2 每次从 B1 取出 2 个零件组装成 1 个部件,放入缓冲区 B2(最多放 2 个部件);包装进程 P3 每次从 B2 取出 1 个部件包装。B1、B2 都只允许一个进程同时访问。
(1) 系统中 P1、P2、P3 各有一个,请用信号量机制实现它们的同步与互斥,说明信号量的含义和初值。(6 分)
(2) 若系统中有 3 个组装进程 P2,按 (1) 的写法可能出现什么问题?怎样修改?(2 分)
做完再点开:满分答案与评分细则
semaphore empty1 = 3, full1 = 0, mutex1 = 1; // B1:空位、零件数、互斥
semaphore empty2 = 2, full2 = 0, mutex2 = 1; // B2:空位、部件数、互斥
P1() { P2() { P3() {
while (1) { while (1) { while (1) {
生产一个零件; P(full1); P(full1); P(full2);
P(empty1); P(mutex1); P(mutex2);
P(mutex1); 从 B1 取 2 个零件; 从 B2 取 1 个部件;
放入 B1; V(mutex1); V(mutex2);
V(mutex1); V(empty1); V(empty1); V(empty2);
V(full1); 组装成 1 个部件; 包装;
} P(empty2); }
} P(mutex2); }
放入 B2;
V(mutex2);
V(full2);
}
}要点:P2 要连续两次 P(full1),确认有 2 个零件后才进入 B1 取件;取完释放 2 个空位。同步信号量的 P 都放在互斥信号量的 P 之前。程序验证:对一个 P1、一个 P2、一个 P3 做状态空间穷举,B1、B2 都不越界、无死锁(verify/08/p13/mock/b45_pv.py)。
常见错误:P2 只做一次 P(full1) 就去取 2 个零件,B1 里只有 1 个时会取空。
可能死锁:B1 中有 3 个零件时,3 个 P2 各自完成了第一次 P(full1)(full1 变为 0),都在第二次 P(full1) 上阻塞;而 B1 已满(empty1 = 0),P1 也无法再放零件。3 个 P2 都在等永远不会来的第二个零件。穷举程序找到了这种状态。
修改:增加信号量 take = 1,把两次 P(full1) 放进 P(take) … V(take) 之间,保证一个组装进程“一次拿两个”时别的组装进程不插进来(相当于 AND 型信号量 Swait(full1, full1))。穷举验证修改后无死锁。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | 信号量含义与初值 | 2 |
| P1、P3 正确 | 2 | |
| P2 两次 P(full1)、两次 V(empty1),P 的顺序正确 | 2 | |
| (2) | 指出死锁及原因 | 1 |
| 给出可行的修改 | 1 | |
| 合计 | 8 |
“一次要多个资源”的进程,多次 P 之间可能被别人插进来——一个进程时没问题,多个进程就要用互斥把这几次 P 合成一次。
某文件系统的索引结点中有 10 个直接地址项、1 个一次间接地址项、1 个二次间接地址项,盘块大小 1KB,盘块号占 4B。磁盘每个柱面 100 个盘块,盘块号 b 所在柱面号为 ⌊b ÷ 100⌋。文件 F 的索引结点已在内存中,有关地址项为:直接地址第 0 项 = 4500;一次间接地址 = 7020,该索引块第 2 项 = 1530;二次间接地址 = 8830,该块第 0 项 = 9910,盘块 9910 的第 34 项 = 2240。
(1) 该文件系统支持的单个文件最大长度是多少 KB?(2 分)
(2) 进程依次读 F 的第 0、12、300 个逻辑块(逻辑块号从 0 开始),各由哪一级地址项索引?依次要读哪些盘块?(3 分)
(3) 磁头当前在 50 号柱面。按 (2) 的读盘顺序,磁头共移动多少个柱面?有人建议用 SSTF 把这些读盘请求重新排序以减少移动距离,可行吗?为什么?(2 分)
做完再点开:满分答案与评分细则
考点10.6 混合索引10.2 磁盘调度11.12 文件系统题
每块可放 1KB ÷ 4B = 256 个盘块号。最大 = (10 + 256 + 256²) × 1KB = 65802KB。
第 0 块:直接地址第 0 项 → 读 4500。
第 12 块:12 − 10 = 2,在一次间接内 → 读索引块 7020,取第 2 项 → 读 1530。
第 300 块:300 − 10 − 256 = 34,在二次间接内,34 ÷ 256 = 0 余 34 → 读 8830 取第 0 项 9910 → 读 9910 取第 34 项 → 读 2240。
依次读:4500、7020、1530、8830、9910、2240,共 6 次。
柱面依次为 45、70、15、88、99、22:|50−45| + |45−70| + |70−15| + |15−88| + |88−99| + |99−22| = 5 + 25 + 55 + 73 + 11 + 77 = 246。
不能简单用 SSTF 重排:索引块必须先读出来,才知道下一个盘块号(读 7020 才知道 1530,读 8830、9910 才知道 2240),这些请求之间有依赖,不是同时摆在队列里的独立请求。(若把它们当成独立请求用 SSTF,距离只有 119,但这在本题中做不到。)改进办法是把索引块和数据块尽量放在相邻柱面上,或把常用的索引块缓存在内存里。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | 65802KB | 2 |
| (2) | 三个逻辑块的索引级别正确 | 1 |
| 6 个盘块号及顺序正确 | 2 | |
| (3) | 246 | 1 |
| 不能重排及理由(有依赖关系) | 1 | |
| 合计 | 7 |
磁盘调度只能重排“已经知道地址的独立请求”;同一个文件的多级索引,必须一级一级读。
答案速查(卷 B)
计组:12 A 13 B 14 C 15 D 16 B 17 C 18 A 19 B 20 D 21 C 22 A
操作系统:23 C 24 D 25 B 26 A 27 B 28 C 29 D 30 A 31 B 32 C
综合题分值:第 43 题 11 分,第 44 题 12 分,第 45 题 8 分,第 46 题 7 分
13.7全真模拟卷 C(计组 + 操作系统 80 分 · 限时 100 分钟)
本卷题号与分值同卷 A:计组单选第 12 ~ 22 题、操作系统单选第 23 ~ 32 题,计组综合第 43、44 题(11 + 12 分),操作系统综合第 45、46 题(7 + 8 分),合计 80 分,限时 100 分钟。第 44 题的 TLB 与 Cache 并行查找、第 45 题的过程分析、第 46 题的“凑满再出发”同步,都是 13.0 预测表里的重点方向。数字和结论由 verify/08/p13/mock_c.py、mock/c46_pv.py 核对。
一、单项选择题 · 计组(第 12 ~ 22 题,每题 2 分,共 22 分)
Ⅰ. 采用微程序控制的计算机中,微程序机器级位于传统机器语言级之下
Ⅱ. 汇编语言程序要经过汇编程序翻译成机器语言程序才能执行
Ⅲ. 操作系统级可以看作在传统机器语言级之上扩充出来的虚拟机
Ⅳ. 高级语言程序必须先编译成汇编语言程序才能执行
- A. 仅Ⅰ、Ⅱ
- B. 仅Ⅱ、Ⅲ
- C. 仅Ⅰ、Ⅱ、Ⅲ
- D. Ⅰ、Ⅱ、Ⅲ、Ⅳ
展开解析
C层次从下到上:微程序级 → 机器语言级 → 操作系统级 → 汇编语言级 → 高级语言级。
- Ⅰ 对每条机器指令由一段微程序解释执行,微程序级在机器语言级下面。
- Ⅱ 对机器只认识机器语言,汇编程序(汇编器)完成翻译。
- Ⅲ 对操作系统提供系统调用等“广义指令”,把裸机扩充成功能更强的虚拟机。
- Ⅳ 错高级语言程序可以直接编译成机器语言目标程序,也可以由解释程序逐条解释执行,不一定经过汇编语言。
干扰项从哪来:A 是对操作系统级的定位不清楚,漏选 Ⅲ;B 是把微程序级和机器语言级的上下位置记反,漏选 Ⅰ;D 是以为编译一定要先生成汇编代码(gcc 的默认流程是这样,但不是“必须”)
- A. 4
- B. 3
- C. 5
- D. 8
展开解析
考点2.8 校验码
Ak 位校验位要能区分“哪一位错了或没错”:2ᵏ ≥ n + k + 1。
- k = 3:2³ = 8 < 8 + 3 + 1 = 12,不够。
- k = 4:2⁴ = 16 ≥ 8 + 4 + 1 = 13 → 至少 4 位。
- A 对4 位。
- B 错3:只要求 2ᵏ ≥ n(8 个数据位),忘了校验位自己也可能出错、还要表示“无错”。
- C 错5:这是能纠 1 位错、同时检 2 位错的扩展海明码(再加 1 位总校验位)所需的位数。
- D 错8:以为每个数据位都要一个校验位。
- A. 00.0001,不溢出
- B. 01.0001,正溢出
- C. 10.0001,负溢出
- D. 01.0001,不溢出
展开解析
考点2.4 溢出判断
B两个符号位不同就溢出:01 正溢出、10 负溢出。
- 00.1011 + 00.0110 = 01.0001。
- 符号位 01 → 正溢出(真值 11/16 + 6/16 = 17/16 ≥ 1)。
- A 错只保留了一位符号位,把进到第二个符号位的 1 丢了,结果“看起来”很小。
- B 对01.0001,正溢出。
- C 错把两个符号位的位置看反了;第一个符号位是 0(真正的符号),第二个是 1。
- D 错只看最高符号位 0 就认为是正数、不溢出;变形补码要比较两个符号位是否相同。
- A. 能,0008 0000H
- B. 不能,会下溢为 0
- C. 能,0000 0010H
- D. 能,0080 0000H
展开解析
A最小的规格化正数是 2⁻¹²⁶,比它小的还可以用非规格化数(阶码全 0、指数按 −126、无隐藏位)表示。
- 2⁻¹³⁰ = 0.0001B × 2⁻¹²⁶ → 阶码全 0,尾数 0001 000…0(小数点后第 4 位为 1)。
- 尾数字段的值 = 2²³⁻⁴ = 2¹⁹ = 80000H → 机器数 0008 0000H(Python struct 解码核对)。
- A 对0008 0000H。
- B 错以为比最小规格化数 2⁻¹²⁶ 小就一定下溢;非规格化数一直能表示到 2⁻¹⁴⁹。
- C 错0000 0010H = 2⁻¹⁴⁵:把“小数点后第 4 位”数成了尾数字段从右往左的第 4 位。
- D 错0080 0000H 是最小的规格化数 2⁻¹²⁶,比 2⁻¹³⁰ 大 16 倍。
- A. 16 根,1 根
- B. 8 根,8 根
- C. 16 根,8 根
- D. 8 根,1 根
展开解析
D64K = 2¹⁶,行、列地址分两次送,引脚减半。
- A 错16 根:忘了 DRAM 行、列地址复用,地址引脚可以减半。
- B 错数据引脚 8 根:把 64K × 1 位看成了 64K × 8 位。
- C 错既没有考虑地址复用,又把位宽看错了。
- D 对地址 16 ÷ 2 = 8 根(配合 RAS、CAS),数据 1 根。
- A. 5
- B. 6
- C. 8
- D. 4
展开解析
考点8.3 替换算法
DLRU:淘汰最久没被访问的块;命中时也要更新“最近访问时间”。
- 2、5、7 缺失;2 命中;9 缺失(装满);5 命中;4 缺失,淘汰 7。
- 2 命中;7 缺失,淘汰 9;9 缺失,淘汰 5;5 缺失,淘汰 4;2 命中。
- 共命中 4 次(程序模拟核对)。
- A 错5 是 FIFO 的命中次数(命中时不更新顺序)。
- B 错6 是 OPT 的命中次数(淘汰以后最晚用到的块)。
- C 错8 是 LRU 的缺失次数,问的是命中次数。
- D 对4 次。
- A. 20 位
- B. 15 位
- C. 17 位
- D. 18 位
展开解析
CTLB 用虚页号查找:虚页号 = 组号 + 标记。
- 虚页号 32 − 12 = 20 位。
- 组数 32 ÷ 4 = 8 → 组号 3 位;标记 20 − 3 = 17 位。
- A 错20 位是全相联 TLB 的标记(整个虚页号)。
- B 错15 位:把 32 项都当成组(直接映射),组号取了 5 位。
- C 对17 位。
- D 错18 位:把“4 路”当成 4 组,组号只取了 2 位。
- A. 2F80H ~ 307FH
- B. 2F82H ~ 3081H
- C. 2F81H ~ 3080H
- D. 3002H ~ 3101H
展开解析
考点3.2 相对寻址
B目标 = (PC) + 偏移,PC = 3000H + 2 = 3002H,偏移 −128 ~ +127。
- A 错以本条指令的地址 3000H 为基准,忘了 PC 已经指向下一条。
- B 对3002H − 80H = 2F82H,3002H + 7FH = 3081H。
- C 错PC 只加了 1,没按指令长 2 字节更新。
- D 错把偏移量当成 0 ~ 255 的无符号数,只能向前跳。
- A. 23 位
- B. 21 位
- C. 19 位
- D. 20 位
展开解析
C32 位 = 操作控制字段 + 判断测试字段 + 下地址字段。
- 下地址字段:512 = 2⁹ → 9 位;判断测试字段 4 位。
- 操作控制字段 = 32 − 9 − 4 = 19 位。
- A 错23 = 32 − 9:漏了判断测试字段。
- B 错21:把 4 个条件编码成 2 位;题目规定每个条件用 1 位。
- C 对19 位。
- D 错20:下地址字段算成 8 位;512 个单元要 9 位地址。
Ⅰ. 关中断 Ⅱ. 保存断点 Ⅲ. 保存通用寄存器的内容 Ⅳ. 引出中断服务程序(把入口地址送 PC)
- A. 仅Ⅰ、Ⅱ
- B. 仅Ⅱ、Ⅲ、Ⅳ
- C. Ⅰ、Ⅱ、Ⅲ、Ⅳ
- D. 仅Ⅰ、Ⅱ、Ⅳ
展开解析
D硬件只做“必须一气呵成、软件没法做”的几步。
- Ⅰ 对必须先关中断,否则保存断点时又来新中断会把现场弄乱。
- Ⅱ 对断点(PC)只能由硬件保存:一进入服务程序 PC 就变了。
- Ⅲ 错通用寄存器由中断服务程序用指令保存(保护现场),属于软件;需要保存哪些由服务程序自己决定。
- Ⅳ 对按中断类型号找到向量,把入口地址送 PC,由硬件完成。
干扰项从哪来:A 是漏了“引出服务程序”,以为转到服务程序是软件跳转;B 是漏了关中断,又把保护现场当成硬件的事;C 是把保存通用寄存器也算给硬件
- A. 10%
- B. 40%
- C. 10.5%
- D. 1%
展开解析
考点9.4 周期挪用
A每秒挪用的周期数 × 每个周期的时间 = 每秒被占用的时间。
- 每秒传送 4 × 10⁶ ÷ 4 = 10⁶ 个字,挪用 10⁶ 个存取周期。
- 10⁶ × 100ns = 0.1s → 每秒中有 10% 的主存时间给了 DMA。
- A 对10%。
- B 错40%:以为每传 1 个字节挪用一次,次数多算了 4 倍。
- C 错10.5%:把 4MB/s 按 4 × 2²⁰ 算;题目说明 M = 10⁶。
- D 错1%:存取周期按 10ns 算,单位换算错了。
一、单项选择题 · 操作系统(第 23 ~ 32 题,每题 2 分,共 20 分)
① CPU 执行 ROM 中的 BIOS 程序,进行硬件自检
② 读入磁盘第一个扇区的主引导记录(MBR),执行其中的引导程序
③ 根据分区表找到活动分区,读入其分区引导记录(PBR)
④ 引导程序把操作系统内核装入内存并转去执行
- A. ②①③④
- B. ①②③④
- C. ①③②④
- D. ①②④③
展开解析
BROM 里的程序最先执行;分区表在 MBR 里,所以要先读 MBR 才能找分区。
- A 错先读 MBR 再自检:开机时只有 ROM 里的 BIOS 可以执行,读磁盘本身也要 BIOS 先完成初始化。
- B 对自检 → MBR → PBR → 装入内核。
- C 错分区表就存放在 MBR 里,没读 MBR 就无法知道哪个是活动分区。
- D 错内核要由活动分区的引导程序(PBR 中的)装入,必须先读 PBR。
- A. 进程请求 I/O 操作
- B. 进程执行 P 操作后信号量值小于 0
- C. 进程的时间片用完
- D. 进程被其他进程的 V 操作唤醒
展开解析
考点6.2 状态转换
C运行 → 就绪:进程还能运行,只是 CPU 被收回了。
- A 错请求 I/O 要等设备完成,进程由运行态转为阻塞态。
- B 错P 操作后 value < 0 表示资源不够,进程阻塞在该信号量的队列上。
- C 对时间片用完(或被更高优先级抢占)时 CPU 被收回,进程回到就绪队列。
- D 错被唤醒是由阻塞态转为就绪态,它原来并不在运行。
- A. 3.33
- B. 5.67
- C. 3.0
- D. 2.33
展开解析
D每当有进程到达就比较优先级,更高的抢占。
- 0 ~ 1:P1;1 时 P2 到(优先数 3 最大)抢占,运行到 4;4 ~ 6:P3(2);6 ~ 10:P1 运行完。
- 等待时间:P1 = 10 − 0 − 5 = 5,P2 = 0,P3 = 6 − 2 − 2 = 2 → 平均 7 ÷ 3 ≈ 2.33(程序模拟核对)。
- A 错3.33 是非抢占式的结果(P1 先运行完,等待 0、4、6)。
- B 错5.67 = (10 + 3 + 4) ÷ 3 是平均周转时间,不是等待时间。
- C 错3.0:把“优先数越大优先级越高”看反了,P1 一直不被抢占。
- D 对约 2.33。
Ⅰ. 关中断法只适用于单处理机,常用于内核中很短的临界区
Ⅱ. TestAndSet 指令的执行过程不会被中断
Ⅲ. Peterson 算法满足让权等待准则
Ⅳ. 用记录型信号量实现互斥时,进不了临界区的进程会阻塞而不忙等
- A. 仅Ⅰ、Ⅱ、Ⅳ
- B. 仅Ⅰ、Ⅱ
- C. 仅Ⅱ、Ⅲ、Ⅳ
- D. Ⅰ、Ⅱ、Ⅲ、Ⅳ
展开解析
A让权等待 = 进不去就让出 CPU;只有阻塞式的方法能做到。
- Ⅰ 对多处理机上关一个 CPU 的中断挡不住别的 CPU;而且把关中断交给用户很危险。
- Ⅱ 对TestAndSet 是一条原子指令,执行期间不响应中断(多处理机还要锁总线)。
- Ⅲ 错Peterson 进不去时在 while 循环里空转(忙等),不满足让权等待。
- Ⅳ 对记录型信号量的 P 操作在资源不够时调用 block 阻塞自己,让出 CPU。
干扰项从哪来:B 是漏了记录型信号量能做到让权等待;C 是以为 Peterson 满足全部四条准则,又漏了关中断法;D 是把 Peterson 也当成满足让权等待
- A. P0 请求 3 个
- B. P2 请求 3 个
- C. P1 请求 2 个
- D. P2 请求 6 个
展开解析
CAvailable = 14 − 10 = 4,Need = (5, 2, 5);逐个试分配后做安全性检查。
- P1 请求 2:试分配后 Available = 2,P1 的 Need = 0 → P1 完成,Available = 2 + 5 = 7 → P0(Need 5)完成 → 11 → P2 完成 → 安全,可以分配(程序核对)。
- A 错P0 请求 3:试分配后 Available = 1,三个进程的 Need 分别为 2、2、5,没有一个能满足 → 不安全,不能分配。
- B 错P2 请求 3:试分配后 Available = 1,Need 为 5、2、2,也找不到能完成的进程 → 不安全。
- C 对可以分配。
- D 错P2 的 Need 只有 5,请求 6 个超过了它声明的最大需求,直接出错拒绝。
- A. 0A3CH
- B. 5A3CH
- C. 323CH
- D. 0E3CH
展开解析
D1KB 页 → 页内偏移 10 位。
- 0A3CH = 0000 10 | 10 0011 1100B:页号 2,偏移 23CH。
- 页框 3 → 物理地址 = 3 × 1024 + 23CH = 0C00H + 23CH = 0E3CH。
- A 错0A3CH:没有做地址变换,直接把逻辑地址当物理地址。
- B 错5A3CH:按 4KB 页切地址(页号 0、偏移 A3CH),再用页 0 的页框 5 拼接。
- C 错323CH:页框号左移了 12 位(按 4KB 页拼接),偏移却按 10 位取。
- D 对0E3CH。
Ⅰ. 修改位供置换时判断是否需要把该页写回外存
Ⅱ. 访问位由操作系统在每次访存时设置
Ⅲ. 外存地址指出该页在外存中的位置,供调页时使用
Ⅳ. 存在位为 0 时访问该页会产生缺页异常
- A. 仅Ⅰ、Ⅲ
- B. 仅Ⅰ、Ⅲ、Ⅳ
- C. 仅Ⅱ、Ⅳ
- D. Ⅰ、Ⅱ、Ⅲ、Ⅳ
展开解析
B每次访存都要做的事只能由硬件做。
- Ⅰ 对没被修改过的页换出时不必写回,省一次磁盘 I/O。
- Ⅱ 错访问位由硬件(MMU)在访存时自动置 1,操作系统只在置换算法里读它、清它;每次访存都让操作系统介入根本来不及。
- Ⅲ 对缺页时操作系统按外存地址找到这一页,把它读入内存。
- Ⅳ 对存在位 = 0 表示不在内存,MMU 产生缺页异常。
干扰项从哪来:A 是只记得修改位和外存地址,漏了存在位为 0 正是产生缺页的条件(Ⅳ);C 是把置位访问位当成操作系统的工作,又漏了修改位和外存地址;D 是以为访问位也由操作系统设置
- A. B 能,C 不能,计数为 1
- B. B 能,C 能,计数为 2
- C. B 不能,C 能,计数为 0
- D. B 能,C 不能,计数为 2
展开解析
A硬链接共享索引结点、计入链接计数;软链接只记路径名。
- A 对建立硬链接后计数为 2;A 删除后减为 1,索引结点和数据还在,B 能访问;C 的软链接里记的是 A 目录下的路径,路径已不存在,找不到。
- B 错以为软链接也指向同一个索引结点、也计入计数。
- C 错把硬链接与软链接的特点弄反了。
- D 错能否访问判断对了,但计数没有随 A 的删除减 1(或把软链接也计进去了)。
- A. 278
- B. 248
- C. 311
- D. 238
展开解析
DLOOK:沿当前方向服务到最远的请求就回头,不走到磁盘端点。
- 向下:90、61、32、20(移动 100 − 20 = 80)。
- 回头向上:105、133、150、178(移动 178 − 20 = 158)。
- 共 80 + 158 = 238(模拟器核对)。
- A 错278 是 SCAN:先走到 0 号磁道再回头(多走了 20 × 2 = 40)。
- B 错248 是 SSTF 的结果(先到 105)。
- C 错311 是 C-LOOK:向下服务到 20 后跳回 178 再向下服务。
- D 对238。
Ⅰ. 在有通道的系统中,分配设备后还要依次分配控制器和通道,都分配成功才能启动 I/O
Ⅱ. 独占设备可以通过 SPOOLing 技术改造成可供多个进程共享的虚拟设备
Ⅲ. 采用动态分配方式时不可能发生死锁
Ⅳ. 采用安全分配方式时,进程发出 I/O 请求后便进入阻塞态,直到该 I/O 完成
- A. 仅Ⅰ、Ⅱ
- B. 仅Ⅱ、Ⅳ
- C. 仅Ⅰ、Ⅱ、Ⅳ
- D. Ⅰ、Ⅱ、Ⅲ、Ⅳ
展开解析
考点9.8 设备分配
C静态分配(一次分配全部设备)才不会死锁;动态分配要靠安全分配方式避免。
- Ⅰ 对设备、控制器、通道三级都要分配到,I/O 才能真正进行。
- Ⅱ 对这正是 SPOOLing 的作用:把打印机等独占设备虚拟成共享设备。
- Ⅲ 错动态分配是“用时才申请”,进程可能占着一台设备再申请另一台,可能死锁;不会死锁的是静态分配。
- Ⅳ 对安全分配方式让进程一发出 I/O 请求就阻塞,不会“占着设备再申请”,从而避免死锁,但进程推进较慢。
干扰项从哪来:A 是不了解安全分配方式“一请求就阻塞”的特点,漏选 Ⅳ;B 是漏了设备、控制器、通道三级分配(Ⅰ);D 是把“动态分配”当成“静态分配”,以为都不会死锁
二、综合应用题(第 43 ~ 46 题,共 38 分)
做完四道题后再展开答案,按评分细则给自己打分,把分数填进每题答案末尾的“自评得分”框;点上方的“交卷”时,选择题自动判分,综合题按你填的自评分计入,计组、操作系统分别统计。
某计算机主频 500MHz,I/O 端口采用独立编址,用 IN、OUT 指令访问。键盘接口有数据端口 60H 和状态端口 64H(读 64H 时第 0 位为 1 表示有新字符;写 64H 可以向接口发控制命令)。接口中只有 1 个字节的数据缓冲,新字符到来时若上一个字符还没被取走就会丢失。
(1) 独立编址与统一编址相比有什么特点?为什么状态端口和控制端口可以共用地址 64H?(2 分)
(2) 采用定时查询方式:每次查询并在有字符时读取,共需 200 个时钟周期。用户最快每秒输入 20 个字符。为不丢失字符,查询间隔最长为多少?此时 CPU 用于查询的时间占比是多少?(3 分)
(3) 改用中断方式,键盘的中断类型号为 09H,中断向量表从地址 0 开始、每个中断向量占 4 字节。键盘的向量地址是多少?中断响应时 CPU 硬件完成哪些操作?键盘中断服务程序主要做哪些事?(3 分)
(4) 系统中还有时钟(L0)和磁盘(L1)两个中断源,键盘为 L2。硬件的响应优先级为 L1 > L0 > L2,现要求处理优先级为 L0 > L2 > L1。写出 L0、L1、L2 的中断服务程序中应设置的屏蔽字(1 表示屏蔽,从左到右对应 L0、L1、L2)。正在执行 L1 的服务程序时键盘中断到来,会怎样?(3 分)
做完再点开:满分答案与评分细则
考点9.3 I/O 接口与端口编址9.4 程序查询与中断5.4 向量地址5.5 屏蔽字
独立编址:I/O 端口有自己的地址空间,不占主存地址,要用专门的 I/O 指令(IN / OUT)访问,端口地址位数少、程序中一眼能区分访存与 I/O;缺点是指令种类少、灵活性差。(统一编址则用访存指令访问端口,端口占用部分主存地址。)
状态端口只读、控制端口只写,读写方向不同,接口可以用读 / 写控制信号区分,所以能共用 64H。
为不丢字符,两次查询之间最多只能来 1 个字符:间隔 ≤ 1 ÷ 20 = 50ms(每秒至少查询 20 次)。
CPU 占比 = 20 × 200 ÷ (500 × 10⁶) = 4000 ÷ 5 × 10⁸ = 0.0008%。
对慢速设备,定时查询的开销很小;真正浪费 CPU 的是“一直循环查询、什么也不干”的忙等方式。
向量地址 = 09H × 4 = 24H(24H ~ 27H 中存放服务程序入口)。
中断响应(硬件,中断隐指令):关中断;保存断点(PC)和 PSW;按类型号从 24H 取出入口地址送 PC,转去执行服务程序。
服务程序:保护现场(保存要用到的寄存器)→(多重中断时设屏蔽字、开中断)→ 用 IN 指令从 60H 读出字符,放入键盘缓冲区,必要时唤醒等待输入的进程 → 恢复现场 → 中断返回。
处理优先级 L0 > L2 > L1:每个服务程序屏蔽自己和处理优先级比自己低的。
L0:屏蔽 L0、L2、L1 → 111;L1(最低):只屏蔽自己 → 010;L2:屏蔽自己和 L1 → 011。
执行 L1 服务程序时(屏蔽字 010,且已开中断),键盘 L2 的请求不被屏蔽,CPU 会暂停 L1 的服务程序,转去处理键盘中断,处理完再回到 L1——实现了“键盘比磁盘先处理”。
屏蔽字按处理优先级设,与硬件的响应优先级无关。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | 独立编址的特点 | 1 |
| 共用 64H 的理由(读写方向不同) | 1 | |
| (2) | 间隔 50ms | 1 |
| 占比 0.0008%(列式正确) | 2 | |
| (3) | 向量地址 24H | 1 |
| 硬件响应的操作 | 1 | |
| 服务程序的主要步骤 | 1 | |
| (4) | 三个屏蔽字 | 2 |
| L2 能打断 L1 | 1 | |
| 合计 | 11 |
I/O 题四件套:端口怎么编址、查询要多频繁、中断向量在哪、屏蔽字怎么设。
某计算机按字节编址,虚拟地址 32 位,物理地址 30 位,页面大小 8KB,采用二级页表。TLB 共 32 项,采用 2 路组相联映射。L1 数据 Cache 的数据区容量 16KB,采用 2 路组相联映射,块大小 64B,用物理地址的标记进行比较。设计者让 CPU 在查 TLB 的同时就用虚拟地址的低位去选择 Cache 组。TLB 第 13 组(左)和 Cache 第 87 组(右)的内容如下:
| 路 | 有效位 | 标记 | 页框号 |
|---|---|---|---|
| 0 | 1 | 502H | 0F11AH |
| 1 | 1 | 520H | 1C3A5H |
| 路 | 有效位 | 标记 |
|---|---|---|
| 0 | 1 | 1C3A5H |
| 1 | 1 | 0C3A5H |
(1) 虚拟地址中虚页号、页内偏移各几位?TLB 的标记、组号各几位?物理地址中 Cache 的标记、组号、块内地址各几位?(3 分)
(2) 为什么本设计可以在 TLB 完成地址变换之前就用虚拟地址选择 Cache 组?若把 Cache 容量改为 32KB(仍为 2 路组相联、块 64B),这种做法会出现什么问题?怎样调整 Cache 的组织才能继续这样做?(3 分)
(3) 访问虚拟地址 0A41 B5C8H:TLB 是否命中?物理地址是多少?Cache 是否命中?(3 分)
(4) 设查 TLB 需 1ns,读一组 Cache 并比较需 2ns;TLB 命中率 99%,缺失时查二级页表需 160ns;Cache 命中率 95%,缺失时再访问主存需 80ns。分别求“TLB 与 Cache 并行查找”和“先查 TLB 再查 Cache”两种方式的平均访存时间。(3 分)
做完再点开:满分答案与评分细则
考点8.8 一次访存的全过程8.5 TLB8.3 地址划分11.7 虚存 + TLB + Cache
页面 8KB → 页内偏移 13 位,虚页号 32 − 13 = 19 位。TLB:32 ÷ 2 = 16 组 → 组号 4 位,标记 19 − 4 = 15 位。
Cache:块 64B → 块内 6 位;组数 16KB ÷ 64B ÷ 2 = 128 → 组号 7 位;标记 30 − 7 − 6 = 17 位(正好就是页框号)。
Cache 的组号 + 块内地址共 7 + 6 = 13 位,恰好落在页内偏移里;页内偏移在地址变换中原样不变,所以虚拟地址的低 13 位就是物理地址的低 13 位,可以先用它选组,同时查 TLB,拿到页框号后再与该组各行的标记比较。
改为 32KB、2 路:组数 256 → 组号 8 位,组号 + 块内 = 14 位 > 13 位,组号的最高位来自虚页号,变换前后可能不同,用虚拟地址选的组可能不对(同一物理块可能出现在两个组里)。调整:把相联度提高到 4 路(32KB ÷ 64B ÷ 4 = 128 组,仍是 7 位组号),或者减小 Cache 容量。拓展:这种 Cache 叫“虚拟地址索引、物理地址标记”。
0A41 B5C8H:页内偏移 = 低 13 位 = 15C8H,虚页号 = 520DH;TLB 组号 = 虚页号低 4 位 = 1101B = 13,标记 = 520H。第 13 组第 1 路有效且标记为 520H → TLB 命中,页框号 1C3A5H。
物理地址 = 1C3A5H × 2¹³ + 15C8H = 3874 B5C8H(30 位)。Cache 组号 = (15C8H >> 6) 的低 7 位 = 87,标记 = 1C3A5H;第 87 组第 0 路有效且标记相同 → Cache 命中。
常见错误:TLB 组号取了页内偏移的位、或把第 0 路的 502H 看成 520H。
并行:命中时间 = max(1, 2) = 2ns;平均 = 2 + 0.01 × 160 + 0.05 × 80 = 7.6ns。
串行:命中时间 = 1 + 2 = 3ns;平均 = 3 + 1.6 + 4 = 8.6ns。并行查找每次访存省 1ns。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | 虚页号 19、偏移 13 | 1 |
| TLB 标记 15、组号 4 | 1 | |
| Cache 标记 17、组号 7、块内 6 | 1 | |
| (2) | 说明组号与块内地址都在页内偏移中 | 1 |
| 32KB 时组号超出页内偏移、可能选错组 | 1 | |
| 提高相联度等调整 | 1 | |
| (3) | TLB 命中及组号、标记 | 1 |
| 物理地址 3874B5C8H | 1 | |
| Cache 命中 | 1 | |
| (4) | 7.6ns | 2 |
| 8.6ns | 1 | |
| 合计 | 12 |
TLB 与 Cache 能不能“同时查”,只看一个条件:Cache 的组号 + 块内地址是否不超过页内偏移的位数。
单处理器系统采用非抢占式(只在当前进程阻塞、结束或时间片用完时才调度)的时间片轮转调度。开始时进程 P 在用户态运行,进程 Q 处于就绪态。之后依次发生:
t1:P 执行 read 系统调用读键盘,此时键盘缓冲区为空;
t2:用户按下一个键,键盘控制器发出中断请求(此时 Q 正在用户态运行);
t3:Q 的时间片用完,时钟中断到来;
t4:P 再次运行,read 把字符复制到用户缓冲区后返回;
t5:P 访问的数据所在页不在内存。
(1) t1 ~ t5 中,哪些时刻 CPU 由用户态进入内核态?分别由什么事件引起(系统调用、外部中断还是异常)?(3 分)
(2) 写出 P 在 t1 ~ t5 期间的状态变化。t2 时刻 P 被唤醒后为什么不立即运行?(2 分)
(3) t1 ~ t5 中,哪些时刻会发生进程切换?t5 之后 P 的状态如何变化?(2 分)
做完再点开:满分答案与评分细则
考点9.6 一次 scanf 的全过程5.2 中断和异常6.2 状态转换11.14 过程分析题
t1:P 执行陷入指令,系统调用(自陷)→ 进入内核态。t2:键盘外部中断 → Q 从用户态进入内核态执行中断服务程序。t3:时钟中断(外部中断)→ 进入内核态,调度程序运行。t5:缺页异常(故障)→ 进入内核态执行缺页处理。t4 不是“进入”内核:P 在内核态中继续执行 read,结束后返回用户态。
t1:运行 → 阻塞(等键盘输入);t2:中断服务程序把 P 唤醒,阻塞 → 就绪;t3 之后:被调度,就绪 → 运行(t4 时在运行);t5:缺页需要读盘 → 阻塞。
t2 时 P 只是进入就绪队列:调度是非抢占式的,当前运行的 Q 时间片还没用完,中断返回后继续运行 Q。
t1:P 阻塞,调度程序选中 Q → 切换(P → Q)。t3:Q 时间片用完回到就绪队列,调度选中 P → 切换(Q → P)。t2 的中断处理完回到 Q,不切换;t4 是 P 自己从内核态返回用户态,不切换。
t5 之后:缺页处理程序启动磁盘读页,P 由运行态变为阻塞态,调度 Q 运行(又一次切换);读完后磁盘中断唤醒 P 进入就绪态,再次被调度时重新执行引起缺页的指令。
“进入内核态”和“进程切换”是两回事:中断处理完回到原进程,只有模式切换、没有进程切换。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | t1 系统调用、t2 外部中断、t3 时钟中断、t5 缺页异常(每错 1 个扣 1 分) | 3 |
| (2) | 状态序列正确 | 1 |
| 非抢占、Q 继续运行 | 1 | |
| (3) | t1、t3 切换,t2、t4 不切换 | 1 |
| t5 之后阻塞 → 就绪的过程 | 1 | |
| 合计 | 7 |
过程分析题列一张表:每个时刻“谁在用 CPU、CPU 什么态、为什么进内核、各进程什么状态、有没有切换”,五列填完就不会漏分。
某渡口有一条渡船和若干乘客。规则:① 渡船最多载 3 人,必须坐满 3 人才开船;② 到对岸后,乘客才能下船;③ 3 名乘客都下船后,船才能返回此岸接下一批乘客。乘客进程的动作为“上船、下船”,船夫进程的动作为“开船到对岸、返回”。
(1) 请用信号量机制实现乘客进程和船夫进程的同步与互斥,说明信号量的含义和初值。(6 分)
(2) 若船夫不等乘客下完就返回并重新放出 3 个座位,会出现什么问题?(2 分)
做完再点开:满分答案与评分细则
考点7.4 PV 五步法7.4 新场景:屏障11.9 PV 训练营
semaphore seat = 3; // 船上的空座位
semaphore full = 0; // 坐满 3 人,通知船夫开船
semaphore land = 0; // 到岸,允许乘客下船
semaphore off = 0; // 已下船的乘客数
semaphore mutex = 1; // 保护 count
int count = 0; // 已上船人数
乘客() { 船夫() {
P(seat); while (1) {
P(mutex); P(full); // 等满 3 人
上船; count++; 开船,到对岸;
if (count == 3) V(full); V(land); V(land); V(land);
V(mutex); P(off); P(off); P(off); // 等 3 人都下船
P(land); 返回;
下船; P(mutex); count = 0; V(mutex);
V(off); V(seat); V(seat); V(seat);
} }
}思路:seat 控制上船人数(计数信号量);第 3 个上船的乘客负责 V(full) 通知船夫(凑满 3 人是一个“屏障”);land 让乘客等到岸;off 让船夫等 3 人都下船;返回后重置 count 并放出 3 个座位。程序验证:6 名乘客、船夫往返 2 次的状态空间穷举,船上不超过 3 人、开船时恰好 3 人、未到岸不下船、下完船才返回,且无死锁(verify/08/p13/mock/c46_pv.py)。
count 的重置要在放出座位之前,否则下一批第一个人上船时 count 可能还是 3。
船夫若不执行三次 P(off) 就返回并放出座位:船还在对岸、上一批乘客可能还没下船,下一批乘客就能“上船”,船上人数可能超过 3,而且新乘客可能在船没开时就收到 land 而下船。穷举程序找到了这类违例。
| 小问 | 得分点 | 分值 |
|---|---|---|
| (1) | 信号量含义与初值(seat、full、land、off、mutex) | 2 |
| 乘客进程:限 3 人、第 3 人通知开船、等到岸再下船 | 2 | |
| 船夫进程:等满员、通知到岸、等下完、重置后放座位 | 2 | |
| (2) | 指出人数超限或时序错乱 | 2 |
| 合计 | 8 |
“凑满 N 个才行动”用计数 + 第 N 个负责通知;“全部完成才继续”用 N 次 P 或计数信号量。
答案速查(卷 C)
计组:12 C 13 A 14 B 15 A 16 D 17 D 18 C 19 B 20 C 21 D 22 A
操作系统:23 B 24 C 25 D 26 A 27 C 28 D 29 B 30 A 31 D 32 C
综合题分值:第 43 题 11 分,第 44 题 12 分,第 45 题 7 分,第 46 题 8 分
13.8全书知识点细目表(满分清单)
这张表把全书压成 178 条“能直接拿来做题的结论”,按考纲顺序排列(中断、异常与系统调用单独成一组,因为它同时属于计组和操作系统)。考前一周逐条过:能说出结论、能说出理由、能举一个例子,就勾上;勾不上的点链接回原文。勾选只保存在你这台电脑的浏览器里。右列是直接考过这一条的真题(2009—2026 年计组、操作系统全部 451 道都已挂在表中),点题号可以弹出原题和讲解。
总进度:
计组 · 计算机系统概述 ()
| ✓ | 知识点(结论) | 讲解 | 真题 |
|---|---|---|---|
| 二进制、位、字节与“开 / 关”的对应;408 只考这些基本单位的换算 | 去看 | 2018-12 | |
| 进制转换:除基取余(整数)、乘基取整(小数);二、八、十六进制按位分组互换 | 去看 | — | |
| 2 的幂与单位:容量的 K/M/G = 2¹⁰/2²⁰/2³⁰,速率的 k/M/G = 10³/10⁶/10⁹ | 去看 | — | |
| 地址位数 = log₂(单元数);按字节 / 按字编址时单元数不同 | 去看 | — | |
| 冯·诺依曼机:五大部件、存储程序、指令和数据同等存放、按地址访问;CPU 靠指令周期的不同阶段区分指令与数据 | 去看 | 2009-11 2019-12 | |
| 主机 = CPU + 主存;辅存、I/O 设备属于外设 | 去看 | — | |
| PC、IR、MAR、MDR、ACC 的作用;MAR 位数决定可寻址单元数,MDR 位数 = 存储字长 | 去看 | — | |
| 对汇编程序员透明的寄存器(MAR、MDR、IR、微指令寄存器)与可见的寄存器(PC、通用寄存器、PSW、基址 / 变址寄存器) | 去看 | 2010-18 2021-17 | |
| 机器字长、指令字长、存储字长、数据总线宽度的区别与换算 | 去看 | 2016-18 2020-12 | |
| 一条指令的执行:取指(PC → MAR → 读 → IR,PC + 1)→ 译码 → 取数 → 执行 → 写回;每条指令至少访存一次 | 去看 | 2011-19 | |
| 计算机系统的层次:微程序机器 → 传统机器 → 操作系统 → 汇编语言 → 高级语言;软硬件逻辑功能等价 | 去看 | — | |
| 编译、解释、汇编三种翻译程序:解释程序不生成目标程序 | 去看 | 2015-12 2016-12 2024-13 | |
| 体系结构(ISA,程序员可见:指令集、寄存器、寻址方式)vs 组成(实现:乘法器、流水线、Cache) | 去看 | 2022-18 2025-16 2026-12 2026-16 | |
| 系列机与兼容:同一 ISA 多种实现,新机向后兼容旧机的软件 | 去看 | — | |
| CPU 时间 = 指令条数 × CPI × 时钟周期;MIPS = 主频 ÷ (CPI × 10⁶) | 去看 | 2010-12 2014-12 2022-12 2023-12 2025-18 | |
| MIPS 不能比较指令集不同的机器;MFLOPS / GFLOPS / TFLOPS 数的是浮点运算次数 | 去看 | 2011-12 2013-12 2021-12 | |
| 吞吐量、响应时间、基准程序;Amdahl 定律(拓展) | 去看 | 2012-12 2017-12 2019-16 |
计组 · 数据的表示和运算 ()
| ✓ | 知识点(结论) | 讲解 | 真题 |
|---|---|---|---|
| 逻辑运算与位运算:与、或、异或、取反;用掩码取出 / 清除某几位 | 去看 | — | |
| 原码、反码、补码、移码的定义;补码比原码多表示一个数(−2ⁿ⁻¹) | 去看 | 2018-13 2021-13 2023-13 | |
| n 位定点整数、定点小数在四种编码下的表示范围 | 去看 | 2015-13 2022-13 | |
| 补码与真值、[x]补 与 [−x]补、补码与移码(符号位取反)的速算 | 去看 | — | |
| C 语言类型转换:同宽有↔无符号位串不变;短→长按源类型扩展(符号扩展 / 零扩展);长→短截断 | 去看 | 2009-12 2024-12 | |
| 有符号与无符号混合运算 / 比较时按无符号进行,比较结果可能“反常” | 去看 | 2011-43 2012-13 2016-13 2019-13 2025-12 | |
| int → float 可能丢精度(24 位有效位),float → int 向 0 截断,int → double 精确 | 去看 | — | |
| 逻辑移位补 0;算术移位:补码右移补符号位、左移可能溢出(移出位与新符号位不同) | 去看 | 2013-14 2018-16 2026-13 | |
| 原码、反码、补码各自的算术移位补位规则;循环移位(大循环 / 小循环) | 去看 | — | |
| 全加器、串行进位与先行进位,ALU 是组合逻辑;补码减法 A − B = A + B̄ + 1 | 去看 | — | |
| OF = Cₙ ⊕ Cₙ₋₁、SF、ZF、CF = Cout ⊕ Sub;CF 管无符号、OF 管有符号;双符号位 01 正溢出、10 负溢出 | 去看 | 2011-17 2014-13 2018-19 2023-16 2025-14 | |
| 乘法、除法的原理与电路结构(移位 + 加减,计数器控制次数) | 去看 | 2024-15 | |
| 原码一位乘(符号单独异或)、补码一位乘 Booth(拓展) | 去看 | — | |
| 乘法溢出:2n 位积的高 n 位不全等于低 n 位的符号;除法:除数为 0 或 −2ⁿ⁻¹ ÷ −1 溢出 | 去看 | 2010-13 2020-43 2025-44 | |
| 恢复余数法与加减交替法(不恢复余数法) | 去看 | — | |
| IEEE 754 单精度 1 + 8 + 23、偏置 127,双精度 1 + 11 + 52、偏置 1023;规格化数隐含最高位 1 | 去看 | — | |
| 编码四步:十进制 → 二进制 → 规格化 → 拼 S、E、M;解码反过来 | 去看 | 2013-13 2017-43 2020-13 2022-14 2023-14 2025-13 2026-14 | |
| 阶码全 1:±∞ / NaN;阶码全 0:±0 与非规格化数(指数 −126、无隐藏位) | 去看 | 2012-14 2018-14 | |
| 浮点加减五步:对阶(小阶向大阶)→ 尾数加减 → 规格化(左规 / 右规)→ 舍入 → 判溢出(看阶码) | 去看 | 2009-13 2015-14 | |
| 就近舍入到偶数;float 间距 = 2^(指数 − 23),大数吃小数、加法不满足结合律 | 去看 | 2010-14 2011-13 2014-14 2021-14 2024-14 | |
| 大端(高字节在低地址)/ 小端;结构体成员按自身大小对齐,总长按最大成员对齐 | 去看 | 2012-15 2016-14 2018-15 2019-15 2020-14 2025-15 | |
| BCD、ASCII;奇偶校验只能发现奇数个错;海明码 2ᵏ ≥ n + k + 1;CRC 模 2 除法 | 去看 | 2013-15 |
计组 · 存储器层次结构 ()
| ✓ | 知识点(结论) | 讲解 | 真题 |
|---|---|---|---|
| 时间局部性与空间局部性;存储层次“Cache—主存”管速度、“主存—辅存”管容量 | 去看 | 2017-14 | |
| RAM / ROM、SRAM(触发器、不刷新)/ DRAM(电容、破坏性读出、要刷新)、Flash;存取时间 < 存取周期 | 去看 | 2011-14 2012-16 | |
| DRAM 集中 / 分散 / 异步刷新,按行刷新;行列地址复用使地址引脚减半 | 去看 | 2010-16 2010-22 2014-15 2015-17 2018-17 2021-15 2022-17 2023-15 | |
| 位扩展、字扩展、字位同时扩展:芯片数 = 总位数 ÷ 单片位数 | 去看 | 2009-15 2010-15 2011-15 2016-16 2017-13 2026-15 | |
| 线选法与译码片选法;由片选位求某芯片的地址范围 | 去看 | — | |
| 低位交叉(低位作体号、可流水,T + (m − 1)r)与高位交叉;单体多字、双端口 RAM(同一单元一写一读才冲突) | 去看 | 2015-18 | |
| 直接映射、全相联、组相联:组号 = 块号 mod 组数,全相联没有组号 | 去看 | 2009-14 2016-15 2026-18 | |
| 地址划分:标记 + 组号 + 块内地址;Cache 总容量 = 行数 × (数据 + 标记 + 有效位 + 脏位 + 替换位) | 去看 | 2010-44 2015-15 2016-45 2020-44 2021-16 2021-44 2022-16 | |
| 命中率、平均访问时间的两种口径(先查后访 / 同时访问) | 去看 | 2009-21 2014-16 2023-43 2025-43 | |
| 替换算法 FIFO、LRU(位数 = log₂ 路数)、LFU、随机 | 去看 | 2012-17 | |
| 写回法(脏位)+ 写分配,全写法 + 非写分配 + 写缓冲 | 去看 | 2015-16 | |
| 多级 Cache、指令 / 数据 Cache 分离;Cache 行、TLB 项、页表项各存什么 | 去看 | — | |
| 磁盘结构(盘面、磁道、扇区、柱面);访问时间 = 寻道 + 半圈旋转 + 传输;传输率 = 每秒转数 × 每道容量 | 去看 | 2012-32 2013-21 2015-20 2019-20 2022-44 | |
| RAID 0(条带)、RAID 1(镜像)、RAID 5(分布式校验,容量 (n − 1)C、容错 1 块) | 去看 | 2013-20 | |
| SSD:页写块擦、闪存翻译层、磨损均衡(由 SSD 控制器做)、随机读快 | 去看 | — |
计组 · 指令系统 ()
| ✓ | 知识点(结论) | 讲解 | 真题 |
|---|---|---|---|
| 指令格式:操作码 + 地址码;零 / 一 / 二 / 三地址指令;定长与变长 | 去看 | 2017-16 2026-43 | |
| 扩展操作码:短码不能是长码的前缀,逐级用剩余编码扩展 | 去看 | 2010-43 2014-17 2020-16 2021-43 2022-19 | |
| 指令种类与条件码:cmp / test 设标志,条件转移看标志(无符号看 CF、有符号看 SF ⊕ OF) | 去看 | 2026-17 | |
| 十种寻址方式与有效地址:立即、直接、间接、寄存器、寄存器间接、基址、变址、相对、堆栈、隐含 | 去看 | 2023-17 | |
| 基址寄存器面向系统(OS 设定)、变址寄存器面向用户(数组);相对寻址的 PC 已指向下一条 | 去看 | 2009-16 2011-16 2013-17 2013-44 2016-17 2017-15 2018-18 | |
| 堆栈寻址:push / pop 时 SP 的变化与访存次数 | 去看 | — | |
| IA-32 常用指令与 Intel / AT&T 格式;读反汇编:循环骨架、条件转移、偏移量 = 目标 − 下一条地址 | 去看 | 2014-44 2024-44 | |
| 过程调用:call 压返回地址、ret 弹出;栈帧中参数在 ebp + 8 起、局部变量在 ebp − 4 起 | 去看 | 2017-44 2019-45 2019-46 2023-44 | |
| CISC(变长、多寻址方式、微程序)vs RISC(定长、Load/Store、寄存器多、硬布线、利于流水) | 去看 | 2009-17 2011-18 2025-17 |
计组 · 中央处理器 ()
| ✓ | 知识点(结论) | 讲解 | 真题 |
|---|---|---|---|
| 指令周期 ⊃ 机器周期 ⊃ 时钟周期;取指、间址、执行、中断四个周期的数据流 | 去看 | — | |
| 单周期(CPI = 1、周期按最慢指令)、多周期、流水线 CPU 的比较 | 去看 | 2016-20 2020-17 2026-20 | |
| 单总线、多总线、专用数据通路;单总线上一拍只能传一个数据 | 去看 | 2019-17 2025-19 | |
| 单总线微操作序列:寄存器 → 总线 → 寄存器、读写主存、ALU 运算(Y、Z 暂存器) | 去看 | 2009-44 2015-43 2015-44 2017-19 2021-18 2022-43 2024-43 2026-44 | |
| 寄存器堆两读一写;组合逻辑元件 vs 时序逻辑元件 | 去看 | 2023-18 | |
| 硬布线控制器:控制信号由组合逻辑直接产生,快、难改,RISC 多用 | 去看 | — | |
| 微程序控制器:一条机器指令对应一段微程序;控制存储器(ROM)在 CPU 内;水平型 / 垂直型 | 去看 | 2009-19 2012-18 2014-18 2017-18 | |
| 微命令编码:直接、字段直接(互斥的放同一字段、留一个不发命令的编码)、字段间接;下址字段与断定方式 | 去看 | — | |
| 流水线时间 (k + n − 1)Δt;吞吐率、加速比、效率;周期 = 最慢段 + 寄存器延迟 | 去看 | 2009-18 2018-20 | |
| 结构冒险(资源冲突,指令 / 数据 Cache 分离)、数据冒险(RAW,转发、load-use 阻塞 1 拍)、控制冒险(分支预测、延迟转移) | 去看 | 2010-19 2012-44 2013-18 2016-19 2019-18 2023-19 2024-19 | |
| 实际 CPI = 1 + 平均阻塞周期;数阻塞拍数的三种约定 | 去看 | — | |
| 超标量、超流水线、VLIW;动态流水线:乱序执行、按序提交 | 去看 | 2017-17 | |
| Flynn 分类 SISD / SIMD / MISD / MIMD;向量处理器属于 SIMD | 去看 | — | |
| 硬件多线程(细粒度、粗粒度、SMT)、多核、SMP(共享单一物理地址空间)、UMA / NUMA | 去看 | 2022-22 |
融合 · 中断、异常与系统调用(计组 + 操作系统) ()
| ✓ | 知识点(结论) | 讲解 | 真题 |
|---|---|---|---|
| 内核态 / 用户态:特权指令只能在内核态执行;用户态 → 内核态只能经中断或异常 | 去看 | 2011-24 2014-25 2021-23 2026-23 | |
| 模式切换:越权执行特权指令引起异常;内核态 → 用户态用特权指令(中断返回) | 去看 | 2022-27 2026-21 | |
| 中断(外中断,来自 CPU 外)vs 异常(内中断,由当前指令引起) | 去看 | 2012-23 2013-28 2015-24 | |
| 故障(缺页,回本条;除 0 不可恢复)、自陷(trap,回下一条)、终止;可屏蔽 / 不可屏蔽中断 | 去看 | 2009-22 2015-22 2016-22 2019-14 2020-18 2020-20 2021-21 2023-21 2025-22 | |
| 中断响应条件:有请求、开中断、一条指令执行结束;DMA 请求在存取周期结束时就能响应 | 去看 | 2018-29 | |
| 中断隐指令(硬件):关中断、保存断点(PC、PSW)、引出服务程序;通用寄存器由服务程序保存 | 去看 | 2010-21 2012-22 2012-24 2015-23 2020-25 2026-22 | |
| 中断判优(链式排队 / 软件查询);类型号 → 向量地址 → 中断向量(入口地址) | 去看 | 2022-24 2023-24 | |
| 多重中断:保护现场后开中断;屏蔽字(1 表示屏蔽)改变的是处理优先级,响应优先级由硬件决定 | 去看 | 2017-22 2020-21 2021-22 2024-21 | |
| 画多重中断时间线:响应顺序 ≠ 处理顺序 | 去看 | 2011-21 | |
| 系统调用:用户态发出(陷入指令)、内核态执行、返回下一条;参数经寄存器 / 内存传递 | 去看 | 2012-28 2017-24 2019-25 2021-32 2022-31 2023-26 2024-23 2024-29 | |
| 系统调用 vs 库函数 vs 普通函数调用 | 去看 | — |
计组 · 总线与输入 / 输出 ()
| ✓ | 知识点(结论) | 讲解 | 真题 |
|---|---|---|---|
| 片内、系统(数据 / 地址 / 控制)、通信总线;总线宽度指数据线位数 | 去看 | 2010-20 2011-20 2012-21 2013-19 2017-20 | |
| 总线带宽 = 每次传送字节数 × 每秒传送次数;复用省引脚不提速 | 去看 | 2009-20 2012-20 2014-19 2016-21 2018-21 2019-19 2020-19 2024-20 2025-20 | |
| 总线事务:申请、仲裁、寻址、传送、释放;突发传送只送一次地址 | 去看 | 2013-43 2018-22 2023-20 | |
| 同步定时、异步定时(不互锁 / 半互锁 / 全互锁 4 次握手)、半同步、分离式 | 去看 | 2012-19 2014-20 2015-19 2021-19 | |
| 集中仲裁:链式 3 根、计数器 ⌈log₂n⌉ + 2 根、独立请求 2n + 1 根(拓展) | 去看 | — | |
| I/O 接口:数据、状态、控制端口(状态与控制可共用地址);统一编址(访存指令)vs 独立编址(IN / OUT) | 去看 | 2014-21 2015-21 2017-21 2021-20 | |
| 程序查询(忙等、串行)、中断(每个数据中断一次)、DMA(每块中断一次);CPU 占用率 = 每秒次数 × 每次周期 ÷ 主频 | 去看 | 2014-22 | |
| DMA 控制器组成与传送过程:预处理 → 数据传送(不需 CPU)→ 后处理(中断);停止 CPU / 周期挪用 / 交替访存 | 去看 | 2009-43 2012-43 2013-22 2017-32 2018-43 2019-22 2020-22 2023-22 2024-22 2025-21 | |
| 四种 I/O 控制方式(含通道)的比较 | 去看 | 2011-22 2016-44 2019-21 2022-21 |
操作系统 · 概述与程序运行环境 ()
| ✓ | 知识点(结论) | 讲解 | 真题 |
|---|---|---|---|
| 操作系统的定义、功能(处理机、存储器、设备、文件管理 + 接口)与目标 | 去看 | 2009-23 | |
| 四个特征:并发(同一时间间隔)、共享(互斥共享 / 同时访问)、虚拟(时分 / 空分复用)、异步;并发与共享互为条件 | 去看 | 2018-23 | |
| 命令接口(联机 / 脱机)、程序接口(系统调用)、GUI | 去看 | 2010-23 | |
| 单道 / 多道批处理、分时(及时、交互)、实时(硬实时 / 软实时)、网络与分布式 OS | 去看 | — | |
| 多道程序设计提高 CPU 利用率的计算 | 去看 | 2016-23 2017-28 2022-23 | |
| 编译 → 汇编 → 链接 → 装入(编译器、汇编器、链接器的作用);静态链接、装入时动态链接、运行时动态链接 | 去看 | 2011-30 2022-20 | |
| 绝对装入、静态重定位(装入时改地址)、动态重定位(运行时 + 重定位寄存器) | 去看 | — | |
| 进程地址空间:代码段、数据段、堆、栈、共享库、内核区;变量放在哪个段 | 去看 | 2023-28 2025-46 | |
| 分层、模块化、宏内核、微内核(频繁切换、可靠)、外核(拓展) | 去看 | — | |
| 内核功能:时钟管理、中断处理、原语、进程 / 存储器 / 设备管理的数据结构 | 去看 | 2023-23 | |
| 开机引导:BIOS 自检(POST)→ MBR → 活动分区 PBR → 引导程序 → 内核 | 去看 | 2013-29 | |
| 虚拟机:第一类 VMM(裸金属)vs 第二类(宿主) | 去看 | 2025-24 |
操作系统 · 进程与线程、调度 ()
| ✓ | 知识点(结论) | 讲解 | 真题 |
|---|---|---|---|
| 进程 = 程序段 + 数据段 + PCB;PCB 是进程存在的唯一标志;进程的特征与进程 vs 程序 | 去看 | 2020-29 2025-25 | |
| 五状态转换:就绪 ↔ 运行 → 阻塞 → 就绪;阻塞 → 运行、就绪 → 阻塞不可能 | 去看 | 2014-26 2015-25 2018-27 2019-24 2022-28 2023-27 | |
| 七状态:就绪挂起、阻塞挂起,中级调度 | 去看 | — | |
| 创建(空白 PCB → 分配资源 → 初始化 → 就绪队列)、终止、阻塞(主动)、唤醒(被动)原语 | 去看 | 2010-24 2021-24 | |
| 进程通信:共享存储(自己同步)、消息传递(直接 / 信箱)、管道(半双工、满写阻塞、空读阻塞) | 去看 | 2014-31 | |
| 信号:内核置位,返回用户态前处理;默认 / 忽略 / 捕获,SIGKILL 不能捕获;同种不排队 | 去看 | — | |
| fork 调用一次返回两次(子进程 0、父进程子 PID),循环 n 次得 2ⁿ 个进程(拓展) | 去看 | 2024-24 | |
| 线程是调度单位、进程是资源分配单位;线程共享地址空间,栈与寄存器私有 | 去看 | 2011-25 2024-28 | |
| 用户级线程(内核不可见、一个阻塞全阻塞)vs 内核级线程;多对一、一对一、多对多 | 去看 | 2012-31 2019-23 2026-25 | |
| 上下文切换:保存 / 恢复 PC、PSW、寄存器到 PCB;模式切换 ≠ 进程切换 | 去看 | 2021-25 2024-25 2025-23 | |
| 三级调度;调度时机;中断处理、原语、内核临界区中不能调度,普通临界区可以 | 去看 | 2012-30 2021-27 | |
| 抢占与非抢占;周转、带权周转、等待、响应时间 | 去看 | — | |
| 闲逛进程、多处理机调度(负载均衡、处理器亲和性) | 去看 | — | |
| FCFS、SJF(平均等待最短、可能饥饿)、HRRN(不饥饿)、优先级、RR(时间片过大退化为 FCFS)、多级反馈 | 去看 | 2009-24 2010-26 2011-23 2012-29 2013-31 2014-23 2016-24 2016-46 2017-23 2017-27 2020-26 2022-25 2026-45 | |
| 画甘特图求周转、带权周转、等待时间;RR 中新到达的排在刚用完时间片的前面 | 去看 | 2018-24 2019-27 2023-29 2024-30 |
操作系统 · 同步互斥与死锁 ()
| ✓ | 知识点(结论) | 讲解 | 真题 |
|---|---|---|---|
| 临界资源与临界区;同步(直接制约)与互斥(间接制约);四准则:空闲让进、忙则等待、有限等待、让权等待 | 去看 | 2011-32 2016-30 2018-25 2020-32 2026-27 | |
| 单标志法违反空闲让进、双标志先检查违反忙则等待、双标志后检查可能饥饿;Peterson 不满足让权等待 | 去看 | 2010-27 | |
| 关中断(只适合单处理机内核)、TestAndSet、Swap(原子、忙等);互斥锁与自旋锁 | 去看 | 2016-27 2021-45 2023-45 | |
| 记录型信号量:P 先减 1 再判 < 0 阻塞,V 先加 1 再判 ≤ 0 唤醒;值为负时绝对值 = 等待进程数 | 去看 | 2010-25 2018-32 2026-26 | |
| 整型信号量忙等,记录型信号量让权等待 | 去看 | — | |
| PV 五步法:找关系 → 设信号量(互斥 1、同步 0、资源 n)→ 放 P / V → 同步 P 在前、互斥 P 在后 → 检查(V 顺序可换) | 去看 | 2009-25 2009-45 2011-45 2013-45 2015-45 2017-46 2020-45 2022-46 2024-46 2025-45 | |
| 经典模型:生产者—消费者、读者—写者(读优先 / 写优先)、哲学家、吸烟者、理发师 | 去看 | 2014-47 2019-43 | |
| 多类资源 + 容量、屏障(凑满再出发)等新场景 | 去看 | — | |
| 管程:任一时刻只有一个进程在管程内;条件变量 wait / signal(没有值,无等待者时 signal 无效) | 去看 | 2016-32 2018-28 | |
| 死锁四条件:互斥、不剥夺、请求并保持、循环等待;死锁 vs 饥饿 vs 死循环 | 去看 | 2014-24 | |
| 资源分配图:单实例有环 ⇔ 死锁,多实例有环不一定 | 去看 | — | |
| 不死锁的最少资源数 = Σ(各进程最大需求 − 1) + 1 | 去看 | 2016-25 2021-31 | |
| 预防(破坏条件)、避免(银行家)、检测(化简资源分配图、死锁定理)、解除(剥夺、撤销、回退) | 去看 | — | |
| 银行家算法:Need = Max − Allocation;安全状态一定不死锁、不安全不一定死锁;安全序列可不唯一 | 去看 | 2011-27 2012-27 2013-32 2018-26 2019-30 2020-27 2022-26 | |
| 死锁检测与解除 | 去看 | 2015-26 |
操作系统 · 内存管理(含计组虚存) ()
| ✓ | 知识点(结论) | 讲解 | 真题 |
|---|---|---|---|
| 逻辑地址 / 物理地址、重定位、内存保护(上下限 / 重定位 + 界地址寄存器)、覆盖与交换 | 去看 | — | |
| 单一连续、固定分区(内部碎片)、动态分区(外部碎片、紧凑需动态重定位) | 去看 | 2010-28 2017-25 2019-32 | |
| 首次、最佳(小碎片多)、最坏、邻近适应;回收的四种合并情况;伙伴算法 | 去看 | 2009-26 2024-27 | |
| 分页:页号 = A ÷ L、偏移 = A mod L;页表项只存页框号;物理地址 = 页框号 ∥ 偏移 | 去看 | 2022-15 | |
| TLB(快表,相联存储):EAT = tTLB + tm + (1 − h)tm | 去看 | 2009-46 2013-16 2024-17 2026-19 | |
| 多级页表:每级页表占一页,每级位数 = log₂(页大小 ÷ 表项大小);反置页表(拓展) | 去看 | 2010-29 2013-46 2014-32 2015-46 2017-45 2018-45 2019-31 2020-46 2021-29 2024-45 2026-28 | |
| 分段(二维地址、便于共享保护、外部碎片)vs 分页(一维);段页式访存三次 | 去看 | 2009-27 2016-28 2019-28 2023-30 2026-30 | |
| 段式 / 页式 / 段页式虚拟存储器 | 去看 | — | |
| 虚拟内存:多次性、对换性、虚拟性;最大容量受地址位数限制 | 去看 | 2010-17 2014-28 2024-18 2025-27 | |
| 请求分页页表项:存在位、访问位、修改位、外存地址;缺页是故障,一条指令可多次缺页 | 去看 | 2011-28 2012-45 2013-30 2014-30 2014-45 2016-26 2020-28 2022-29 2022-30 2025-26 2026-24 | |
| OPT(不可实现)、FIFO(Belady 异常)、LRU、CLOCK、改进型 CLOCK | 去看 | 2010-46 2015-27 2015-30 2019-29 2021-28 | |
| 驻留集、固定 / 可变分配与局部 / 全局置换;抖动;工作集 | 去看 | 2011-29 2012-25 2016-29 2026-29 | |
| 页面缓冲算法、预调页与请求调页;对换区 vs 文件区 | 去看 | — | |
| 内存映射文件、写时复制 | 去看 | 2025-30 | |
| 一次访存全过程:TLB → 页表 → Cache → 主存 → 缺页;TLB 与 Cache 命中互不决定,缺页时两者都不命中 | 去看 | 2011-44 2018-44 | |
| 虚存性能:EAT 由缺页率主导;页面大小、页框数、置换算法、程序局部性;页缓冲只降缺页代价 | 去看 | — | |
| Cache—主存与主存—辅存对照:交换单位、谁管理、映射、写策略(虚存只能写回) | 去看 | 2020-15 2024-16 |
操作系统 · 文件管理 ()
| ✓ | 知识点(结论) | 讲解 | 真题 |
|---|---|---|---|
| FCB 与索引结点(inode 使目录项变小、检索快);文件属性 | 去看 | 2009-30 2017-30 2025-29 | |
| 文件操作:create、open(读入 FCB、建表项、返回 fd)、read / write、close、delete | 去看 | 2009-31 2014-29 2017-31 2020-23 2021-30 2023-31 | |
| 逻辑结构:流式 / 记录式;顺序、索引、索引顺序、散列文件 | 去看 | — | |
| 打开文件表:系统表一个文件一项(打开计数)、进程表记读写指针,fd 是进程表下标 | 去看 | — | |
| 单级、两级、树形(绝对 / 相对路径)、无环图目录;目录的实现(线性表 / 哈希) | 去看 | 2010-31 2013-23 2020-31 2022-45 | |
| 硬链接(共享 inode、链接计数,不能跨文件系统)vs 软链接(存路径,原文件删除后失效) | 去看 | — | |
| 连续分配(随机访问、外部碎片)、隐式链接(只能顺序访问)、显式链接 FAT(在内存)、索引分配 | 去看 | 2015-31 2017-26 | |
| 混合索引:N = 块大小 ÷ 地址项大小,最大文件 = (直接 + N + N² + …) × 块大小;访问某偏移的读盘次数 | 去看 | 2009-28 2010-30 2011-46 2012-46 2013-24 2013-26 2014-46 2015-29 2016-47 2018-46 2020-24 | |
| 空闲表、空闲链表、位示图(块号 = 字号 × 字长 + 位号)、成组链接(超级块中的空闲块号栈) | 去看 | 2014-27 2019-26 2023-25 2024-26 2025-31 | |
| 文件系统布局:引导块、超级块、空闲空间管理、inode 区、根目录;内存中的挂载表、目录缓存、打开文件表 | 去看 | 2026-46 | |
| 文件保护:口令、加密、访问控制矩阵(按列 ACL、按行权限表) | 去看 | — | |
| 文件系统层次;虚拟文件系统 VFS(统一接口);挂载 | 去看 | 2025-28 | |
| 一次 open + read 的全过程:路径解析 → inode → 打开文件表 → 读盘块 | 去看 | — |
操作系统 · 输入 / 输出管理与磁盘 ()
| ✓ | 知识点(结论) | 讲解 | 真题 |
|---|---|---|---|
| I/O 软件层次:用户层 → 设备独立性软件(逻辑名映射、缓冲、分配)→ 驱动程序(与硬件相关)→ 中断处理程序 | 去看 | 2011-26 | |
| 块设备、字符设备、网络设备接口;阻塞、非阻塞、异步 I/O | 去看 | 2022-32 2026-31 | |
| I/O 核心子系统:I/O 调度、缓冲、设备分配、假脱机、错误处理 | 去看 | 2012-26 | |
| 一次 scanf / read 的全过程:系统调用 → 阻塞 → 中断 → 唤醒 → 复制数据 → 返回 | 去看 | 2010-32 2011-31 2023-46 2024-31 2026-32 | |
| 单缓冲 max(C, T) + M、双缓冲 max(C + M, T);循环缓冲与缓冲池 | 去看 | 2013-27 2015-28 2018-31 | |
| 设备分配:SDT → DCT → COCT → CHCT;静态 / 动态、安全 / 不安全分配 | 去看 | 2009-32 2020-30 2023-32 | |
| SPOOLing:输入井 / 输出井(磁盘)、输入 / 输出进程,把独占设备变共享设备 | 去看 | 2016-31 | |
| FCFS、SSTF(可能饥饿)、SCAN(到端点回头)、LOOK、C-SCAN(返回不服务,返回距离计入)、C-LOOK | 去看 | 2009-29 2010-45 2013-25 2015-32 2018-30 2019-44 2021-26 2024-32 | |
| 低级格式化 → 分区 → 高级格式化;引导块;扇区、块、簇;坏块(标记 / 扇区备用);交替编号 | 去看 | 2017-29 2021-46 2025-32 |