# 📖 操作系统复习与教学笔记 > **目标**:不仅能应付考试,更能真正理解操作系统的工作原理 > **定位**:这份笔记不是简单的知识点罗列,而是教你"怎么学、怎么记、怎么做题" --- ## 目录 1. [复习策略与时间规划](#一复习策略与时间规划) 2. [四大必考计算题 · 手把手教学](#二四大必考计算题--手把手教学) 3. [核心概念 · 快速记忆法](#三核心概念--快速记忆法) 4. [易混淆概念辨析](#四易混淆概念辨析) 5. [代码题考点精讲](#五代码题考点精讲) 6. [问答题高频考点](#六问答题高频考点) 7. [考前速查表](#七考前速查表) --- ## 一、复习策略与时间规划 ### 1.1 按优先级分三层 | 层级 | 内容 | 建议投入时间 | 策略 | |------|------|-------------|------| | 🔴 **保命层** | 磁盘调度、页面置换、fork进程树、inode计算 | 60% | 必须能**独立默写**解题步骤 | | 🟡 **进阶层** | CPU调度、I/O控制方式、段页式地址转换、死锁 | 30% | 理解原理 + 做2-3道题 | | 🟢 **基础层** | 概念题、代码优化、工作集模型 | 10% | 多看几遍,有印象即可 | ### 1.2 复习三步法 ``` 第一步:看本笔记 → 建立知识框架和记忆锚点 第二步:做预测卷 → 实战演练,暴露薄弱点 第三步:复盘错题 → 针对性地回顾对应章节 ``` ### 1.3 考试时间分配建议(120分钟) | 题型 | 建议用时 | 策略 | |------|---------|------| | 计算题(磁盘/页面置换/CPU调度/inode) | 50-60min | 先做!这是拿分大头 | | 代码分析题(fork进程树) | 15-20min | 画图要清晰规范 | | 简答/概念题 | 25-30min | 分点作答,关键词给分 | | 地址转换题 | 10-15min | 按步骤写,不要跳步 | | 检查 | 5-10min | 检查计算题的数字 | --- ## 二、四大必考计算题 · 手把手教学 ### 2.1 磁盘调度算法计算(★★★★★ 每年必考) #### 本质理解 磁盘调度就是在解决"磁头怎么走最省时间"的问题。可以把磁头想象成电梯,磁道就是楼层号。 #### 各算法的"人设" | 算法 | 一句话人设 | 行为特征 | |------|-----------|----------| | **FCFS** | "先来后到"的耿直 boy | 来一个处理一个,不做任何优化 | | **SSTF** | "贪心"的近视眼 | 每次都选最近的,但可能让远的永远等不到 | | **SCAN** | "电梯"本梯 | 一直走到头再回头 | | **C-SCAN** | 单向电梯 | 只往上走,到顶后直接回到底部重来 | | **LOOK** | 智能电梯 | 走到最后一个请求就回头,不到边界 | | **C-LOOK** | 智能单向电梯 | 只往上到最高请求,然后直接回最低请求 | #### 📐 解题模板(以最难的 C-SCAN 为例) ``` 题目:磁道0-199,磁头在53,向增大方向移动 请求序列:98, 183, 37, 122, 14, 124, 65, 67 Step 1: 将请求排序 排序:14, 37, 65, 67, 98, 122, 124, 183 Step 2: 确定方向并依次服务 C-SCAN规则:向增大方向走 → 到199(边界)→ 回到0 → 继续向增大方向 路径:53 → 65 → 67 → 98 → 122 → 124 → 183 → 199(到边界)→ 0 → 14 → 37 移动距离:12 + 2 + 31 + 24 + 2 + 59 + 16 + 199 + 14 + 23 Step 3: 求和 = 12+2+31+24+2+59+16+199+14+23 = 382道 ``` #### ⚠️ 常见错误 | 错误 | 正确做法 | |------|---------| | SCAN/C-SCAN 忘记走到边界(199或0) | **必须**走到边界,除非题目说用 LOOK | | SSTF 选择了相等距离的两个磁道 | 通常选编号小的(题目可能会有说明) | | 方向搞反 | 题目说"向增大方向"就从53往上走 | | C-SCAN 往回时逐个服务 | C-SCAN 回头时**不服务**,到底后才重新开始 | #### 💡 速算技巧 SSTF 的路径一定是最短的(贪心嘛),FCFS 通常是最长的。检查时如果 SSTF 比别的算法还长,肯定算错了。 --- ### 2.2 页面置换算法(★★★★★ 每年必考) #### 本质理解 内存满了,要换掉哪个页面?OPT 是"预言家",LRU 是"记仇本",FIFO 是"排队机"。 #### 各算法速记 | 算法 | 口诀 | 关键特征 | |------|------|---------| | **OPT** | "看未来,谁最晚用就换谁" | 理论最优,无法实现(但考试能算) | | **FIFO** | "先来先走,排队淘汰" | 可能 Belady 异常(页框变多反而缺页更多) | | **LRU** | "看过去,谁最久没用就换谁" | 无 Belady 异常,硬件开销大 | | **Clock** | "转圈查,引用位=0就换" | LRU 的简化版 | #### 📐 解题模板(以 LRU,页框数=3 为例) ``` 访问序列:7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2, 1, 2, 0, 1, 7, 0, 1 画表格法: ------------------------------------------------------------- 访问 页框1 页框2 页框3 是否缺页 说明 ------------------------------------------------------------- 7 7 - - ✓缺页 空,直接放入 0 7 0 - ✓缺页 空,直接放入 1 7 0 1 ✓缺页 空,直接放入 2 7 0 2 ✓缺页 换掉最久未用的1(1离上次最远) 0 7 0 2 ✗命中 0已存在,更新它的使用时间 3 3 0 2 ✓缺页 换掉最久未用的7(7离上次最远) 0 3 0 2 ✗命中 0已存在 4 3 0 4 ✓缺页 换掉最久未用的2(2离上次最远) ...以此类推 ``` #### ⚠️ OPT 和 LRU 的区别(最容易混淆) ``` 序列:1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5(页框数=4) OPT 在访问5时: 当前页框:1,2,3,4 看未来:1还要用(第5位),2还要用(第6位),3还要用(第10位),4还要用(第11位) 最晚使用的是4(第11位才用),所以换4 LRU 在访问5时: 当前页框:1,2,3,4 看过去:1最近在位置4用过,2在位置5用过,3在位置2用过,4在位置3用过 最久未用的是3(位置2之后就没用过),所以换3 ``` > **关键口诀**:OPT 看**未来**,LRU 看**过去**。OPT 是"预言",LRU 是"历史"。 #### 💡 Belady 异常 **只在 FIFO 中出现**。增加页框数反而缺页更多。LRU 和 OPT 都不会有这个问题。 --- ### 2.3 fork 进程树分析(★★★★★ 每年必考) #### 本质理解 `fork()` 就像细胞分裂 — 调用一次,变成两个进程,从同一行代码继续执行。 #### 黄金法则 ``` 法则1:fork() 一次,返回两次 - 父进程返回值 = 子进程的 PID - 子进程返回值 = 0 法则2:子进程从 fork() 的下一行开始执行(不是从 main 开头) 法则3:fork() 嵌套时,每个进程都要独立分析它的执行路径 法则4:父进程总数 = 2^n(n为fork调用次数,无嵌套时) ``` #### 📐 解题模板 ``` 程序: fork() // fork1 if (pid == 0) { fork() // fork2 } fork() // fork3 Step 1: 画出进程树 P(初始) ╱ ╲ ← fork1 P(子1) P(父) ╱ ╲ ╲ ← fork2(只有子1执行) P(孙1) P(子1续) P(父) | | | ← fork3(所有进程都执行) P(曾孙1) P(孙2) P(子2) Step 2: 统计进程总数 2³ = 8个进程(3次fork,最外层没有if限制) Step 3: 分析每个进程的输出 按执行路径逐条跟踪... ``` #### ⚠️ 常见陷阱 | 陷阱 | 说明 | |------|------| | `if (pid == 0)` 内的 fork | 只有子进程执行,父进程跳过 | | `wait(NULL)` | 父进程等待**任意**子进程结束 | | 嵌套的 `wait` | 每个 `wait` 只能回收一个子进程 | | 没有 wait | 子进程变成僵尸进程(Zombie) | #### 💡 wait 的个数 父进程有几个子进程,就应该有几个 `wait()`,否则会产生僵尸进程。 --- ### 2.4 Ext2 inode 最大文件计算(★★★★★ 每年必考) #### 本质理解 inode 就像文件系统的"目录索引卡",有15个格子放地址。前12个直接指向数据块,后3个是间接指向(指向一个"指针块",指针块再指向数据块)。 #### 计算公式速记 ``` 块大小 = 4KB = 4096 字节 指针大小 = 4 字节 每块指针数 = 4096 / 4 = 1024 个 最大文件: 直接:12 × 4KB = 48KB 一次间接:1024 × 4KB = 4MB 二次间接:1024² × 4KB = 4GB 三次间接:1024³ × 4KB = 4TB ────────────────────────────────── 总计:48KB + 4MB + 4GB + 4TB ≈ 4.004 TB ``` > **注意**:如果块大小是 1KB、指针 4B,则每块 256 个指针,结果约 16GB+(你的知识点总结里那个)。 #### 📐 解题模板:给定文件大小,问需要哪些地址项 ``` 题目:块大小=4KB,指针=4B,文件=260KB Step 1: 计算文件占多少数据块 260KB / 4KB = 65 块 Step 2: 检查直接索引能装多少 直接索引 = 12 块 → 只能装 48KB,不够 Step 3: 一次间接能装多少 一次间接 = 1024 块 → 1024 × 4KB = 4MB 12 + 1024 = 1036 块,65 ≤ 1036,所以只需要一次间接 结论:使用直接索引12个 + 一次间接1个(其中用到 65-12=53 个指针) ``` #### ⚠️ 易错点 - 问"需要几次磁盘访问":直接索引=1次,一次间接=2次(先读指针块,再读数据块),二次间接=3次... - 在计算最大文件时,**不要忘了加直接索引的12块** --- ## 三、核心概念 · 快速记忆法 ### 3.1 操作系统四大特征 ``` 口诀:共、享、虚、异 → "恭喜虚拟" - 共(并发):宏观并行,微观串行 - 享(共享):资源共享 - 虚(虚拟):物理实体变为逻辑多个 - 异(异步):进程走走停停 ``` ### 3.2 CPU 双模式 ``` 用户态 →→→ 中断/异常/系统调用 →→→ 内核态 内核态 →→→ PSW切换 →→→ 用户态 记忆:用户态进内核态叫"陷入"(trap),内核态回用户态叫"恢复" ``` ### 3.3 中断分类速记 ``` 中断(Interrupt) ├── 硬中断(外部,异步) │ ├── I/O中断(键盘敲一下) │ └── 时钟中断(OS拿回控制权的关键!) │ └── 软中断/异常(内部,同步) ├── 故障(Fault):缺页异常(可恢复) ├── 陷阱(Trap):系统调用 int 0x80 └── 终止(Abort):除零错误(不可恢复) ``` > **必考提问**:OS 如何获得 CPU 控制权?→ **时钟中断!** 时间片到了,时钟中断触发,OS 重新调度。 ### 3.4 ELF 内存布局 ``` 从低地址到高地址: ┌─────────────────┐ │ .text 代码段 │ ← 只读,存放指令 │ .rodata 只读数据 │ ← 字符串常量等 │ .data 初始化数据 │ ← 已初始化全局变量 │ .bss 未初始化数据 │ ← 不占磁盘!只占内存 ├─────────────────┤ │ heap(堆)↑ │ ← malloc 分配 ├─────────────────┤ │ ↓ │ │ stack(栈) │ ← 局部变量、函数调用 └─────────────────┘ ``` **记忆 trick**:堆向上长(像树往上),栈向下长(像水往下流)。 ### 3.5 死锁四个必要条件 ``` 口诀:互、请、不、循 → "互请不循"(互相请求,不能循环) 1. 互斥(Mutual exclusion) 2. 请求和保持(Hold and wait) 3. 不可抢占(No preemption) 4. 循环等待(Circular wait) 破坏任意一个 → 死锁预防 ``` ### 3.6 页面置换算法特征速记 ``` OPT → 看未来,最晚用的 → 不可能实现(除非能预知未来) LRU → 看过去,最久没用 → 硬件开销大 FIFO → 看时间,最早进入 → 会有 Belady 异常 Clock→ 看引用位,转圈查 → 近似 LRU LFU → 看次数,最少使用 → 可能被"热数据"刷掉 ``` --- ## 四、易混淆概念辨析 ### 4.1 并发 vs 并行 | | 并发 | 并行 | |--|------|------| | 含义 | 同时**处理**多个任务 | 同时**执行**多个任务 | | 核心 | 宏观上同时,微观上交替 | 真正的同一时刻都在运行 | | 硬件 | 单核 CPU | 多核 CPU | | 类比 | 一个人交替做几件事 | 几个人同时做各自的事 | ### 4.2 进程 vs 线程 | | 进程 | 线程 | |--|------|------| | 资源拥有 | 独立地址空间 | 共享进程资源 | | 切换开销 | 大(需切换页表等) | 小(只需切换栈) | | 通信方式 | IPC(管道、共享内存等) | 直接读写共享数据 | | 独立性 | 一个崩溃不影响其他 | 一个线程崩溃→整个进程崩溃 | ### 4.3 死锁 vs 饥饿 | | 死锁 | 饥饿 | |--|------|------| | 状态 | 多个进程**互相等待**,都无法前进 | 一个进程**一直等不到**资源 | | 是否可以解除 | 必须外部干预(终止进程/抢占资源) | 其他进程释放资源后可自动解除 | | 典型场景 | 银行家算法中不安全状态 | SJF 中长作业一直得不到 CPU | ### 4.4 静态链接 vs 动态链接 | | 静态链接 | 动态链接 | |--|---------|---------| | 时机 | 编译时 | 运行时 | | 文件大小 | 大(包含所有库代码) | 小(运行时加载) | | 更新库 | 需重新编译 | 直接替换库文件 | | 依赖 | 无外部依赖 | 需要 .so/.dll 文件存在 | ### 4.5 全局置换 vs 局部置换 | | 全局置换 | 局部置换 | |--|---------|---------| | 范围 | 从所有进程中选页面换出 | 只从当前进程的页面中选 | | 公平性 | 进程可能互相影响 | 每个进程独立管理 | | 抖动控制 | 难控制 | 容易控制 | ### 4.6 固定分区 vs 动态分区 vs 分页 | | 固定分区 | 动态分区 | 分页 | |--|---------|---------|------| | 内部碎片 | ✓ | ✗ | ✓(最后一页) | | 外部碎片 | ✗ | ✓ | ✗ | | 管理复杂度 | 低 | 中 | 高 | --- ## 五、代码题考点精讲 ### 5.1 fork 代码分析 #### 经典模式 1:单次 fork ```c pid_t pid = fork(); if (pid == 0) { // 子进程代码 } else { // 父进程代码,pid 是子进程的 PID } ``` **输出特点**: - 子进程和父进程的代码都会执行 - 子进程从 fork() 返回处开始执行(不是从 main 开始) - "A\n" 只有一个进程输出,"B\n" 和 "C\n" 各输出一次(因为 fork 后两个进程都继续) #### 经典模式 2:嵌套 fork ```c pid_t pid1 = fork(); // fork1 if (pid1 == 0) { pid_t pid2 = fork(); // fork2 — 只在子进程中执行 if (pid2 == 0) { // 孙进程 } } ``` **进程树分析**: ``` P(初始) / \ ← fork1 P(子1) P(父) / \ ← fork2(仅在子1中执行) P(孙) P(父) ``` #### 经典模式 3:fork + wait ```c for (int i = 0; i < 3; i++) { pid_t pid = fork(); if (pid == 0) { // 子进程 exit(0); } } // 父进程需要回收所有子进程 while (wait(NULL) > 0); ``` **重点**:循环中的 fork,子进程 exit 后,父进程用 `wait` 回收。总计 `2³ = 8` 个进程。 ### 5.2 生产者消费者(多线程同步) ```c sem_t mutex, empty, full; void *producer(void *arg) { // 生产数据... sem_wait(&empty); // 等空位 sem_wait(&mutex); // 加锁 // 放入缓冲区 sem_post(&mutex); // 解锁 sem_post(&full); // 通知消费者 } void *consumer(void *arg) { sem_wait(&full); // 等数据 sem_wait(&mutex); // 加锁 // 取出数据 sem_post(&mutex); // 解锁 sem_post(&empty); // 通知生产者 } ``` **⚠️ 注意顺序**:P(empty) 必须在 P(mutex) 之前!否则可能死锁。 ——如果先锁 mutex 再等 empty,而缓冲区满了,生产者拿不到 empty,消费者进不来临界区拿数据,死锁。 ### 5.3 Shell 基本框架 ```c while (1) { printf("$ "); fgets(cmdline, MAXLINE, stdin); pid_t pid = fork(); if (pid == 0) { // 子进程执行命令 execvp(argv[0], argv); exit(0); } // 父进程等待 wait(NULL); } ``` ### 5.4 守护进程创建(5步法) ```c // 第1步:fork + 父进程退出 pid_t pid = fork(); if (pid > 0) exit(0); // 第2步:创建新会话 setsid(); // 第3步:再 fork 一次,防止重新获得控制终端 pid = fork(); if (pid > 0) exit(0); // 第4步:改变工作目录 chdir("/"); // 第5步:重设文件权限掩码 umask(0); ``` **问**:为什么要 fork 两次? **答**:第一次 fork 后 setsid() 创建新会话,但会话首进程可能重新获得控制终端。第二次 fork 的子进程不是会话首进程,**永远无法**获得控制终端。 --- ## 六、问答题高频考点 ### 6.1 什么是操作系统?它的四大特征是什么? **答题模板**: 1. 操作系统是管理计算机硬件和软件资源的系统软件 2. 四大特征:并发、共享、虚拟、异步(各用一句话解释) ### 6.2 中断在操作系统中的作用? **核心要点**: - 中断是实现**并发执行**的基础 - **时钟中断**是操作系统**获得 CPU 控制权**的关键机制 - 中断使 CPU 能够**响应外部事件**(I/O 完成、硬件故障等) - 系统调用通过**软中断(trap)**实现用户态→内核态切换 ### 6.3 比较几种 I/O 控制方式 | 方式 | CPU介入 | 传输单位 | 并行性 | 硬件 | |------|---------|---------|--------|------| | 程序直接控制 | 全程忙等 | 字 | 无 | 最简单 | | 中断驱动 | 每个字中断一次 | 字 | 有 | 较简单 | | DMA | 每块中断一次 | 块 | 高 | 较复杂 | | 通道 | 程序级 | 块/组 | 极高 | 最复杂 | **记忆线索**:从上到下,CPU 越来越省事,硬件越来越复杂,传输效率越来越高。 ### 6.4 什么是抖动(Thrashing)?如何解决? **定义**:分配的页框数太少,导致频繁缺页,CPU 大部分时间花在页面置换上,利用率急剧下降。 **原因**:工作集 > 分配的页框数 **解决方案**: 1. 增加分配页框数(工作集模型) 2. 采用局部置换(不让进程互相影响) 3. 降低多道程序度 ### 6.5 SPOOLing 如何将独占设备改造为共享设备? **原理**: ``` 输入井 用户进程1 ╲ ╱ 预输入程序 用户进程2 ╲── 磁盘存储 ─╱ (把磁盘当"大缓冲区") 用户进程3 ╱ (输入/输出井) ╲ 缓输出程序 输出井 ``` **核心思想**:用磁盘(共享设备)做缓冲,把独占设备(打印机)的逻辑使用变成对磁盘文件的读写。用户觉得打印机是自己的,实际上是 SPOOLing 系统在排队管理。 ### 6.6 段式 vs 分页 vs 段页式 | | 分段 | 分页 | 段页式 | |--|------|------|--------| | 出发点 | 用户视角,逻辑模块 | 系统视角,物理管理 | 兼具两者优点 | | 大小 | 可变(由逻辑决定) | 固定(4KB等) | 段可变,段内页固定 | | 碎片 | 外部碎片 | 内部碎片(最后一页) | 无外部碎片 | | 共享/保护 | 方便 | 不方便 | 方便 | | 地址结构 | 段号+偏移 | 页号+偏移 | 段号+页号+偏移 | | 访存次数 | 2次 | 2次(有TLB可减) | 3次 | ### 6.7 优先级反转 **场景**:高优先级进程(H)等待低优先级进程(L)持有的资源,而中等优先级进程(M)抢占了 L 的 CPU,导致 H 被 M 间接阻塞。 **解决方案**:优先级继承 — L 持有的资源被 H 需要时,L 临时继承 H 的优先级,运行完释放资源后再降回去。 --- ## 七、考前速查表 ### 7.1 公式速查 | 公式 | 适用场景 | |------|---------| | 周转时间 = 完成时间 - 到达时间 | CPU 调度 | | 带权周转时间 = 周转时间 / 运行时间 | CPU 调度 | | 响应比 = 1 + 等待时间 / 运行时间 | HRRF 调度 | | EAT = λ + t + (1-a)·t | TLB 有效访问时间 | | 每块指针数 = 块大小 / 指针大小 | inode 计算 | | 加速比 S = T串行 / T并行 | 并行计算 | | Amdahl: S = 1 / ((1-f) + f/P) | 并行计算加速比上限 | | 磁盘访问时间 = 寻道 + 旋转延迟 + 传输 | I/O 性能 | | 平均旋转延迟 = 1/2 × 转一圈时间 | 磁盘性能 | ### 7.2 数值速查 | 场景 | 典型值 | |------|--------| | 页大小 | 4KB | | 块大小(Ext2) | 1KB 或 4KB | | 指针大小(地址项) | 4B | | 时间片(RR) | 几十 ms 级别 | | 7200RPM 旋转延迟 | 4.17ms(半圈) | ### 7.3 算法时间复杂度速查 | 算法/机制 | 数据结构 | 复杂度 | |-----------|---------|--------| | FCFS 调度 | 队列 | O(1) | | SJF 调度 | 最小堆 | O(log n) | | CFS(Linux) | 红黑树 | O(log n) | | 页表查找(无 TLB) | 数组 | O(1) | | 多级页表 | 树 | O(级数) | | 倒排页表 | 哈希表 | O(1)平均 | ### 7.4 Linux 常用命令速查 ```bash chmod 755 file # rwxr-xr-x (7=4+2+1, 5=4+1) chmod u+x file # 给所有者加执行权限 ps -ef # 查看所有进程 top # 动态查看进程 kill -9 PID # 强制终止进程(SIGKILL) grep pattern file # 搜索文件内容 strace ./prog # 跟踪系统调用 gdb ./prog # 调试程序 ``` --- ## 附录:考前最后一天做什么 ### 8小时冲刺计划 ``` 第1小时:复习四大计算题模板(本笔记 2.1-2.4) 第2小时:做预测卷的计算题部分 第3小时:复习进程树分析和代码题(第五部分) 第4小时:做预测卷的代码分析题 第5小时:复习概念辨析和问答题(第三、四、六部分) 第6小时:做预测卷的问答题 第7小时:错题回顾 + 速查表记忆 第8小时:放松!早点休息 ``` ### 考场检查清单 - ✅ 计算题先做(分值高、确定性大) - ✅ 画进程树时标注字母/输出 - ✅ 缺页次数计算时,初始空页框**算**缺页 - ✅ SCAN/C-SCAN 注意方向 - ✅ CPU 调度画甘特图辅助 - ✅ 地址转换要写出每一步的数值 - ✅ 简答题分点作答,关键词突出 --- > **最后的话**:操作系统不是死记硬背的科目,理解"为什么"比记住"是什么"重要得多。 > 遇到问题时,想想"如果是 Linux,它会怎么做?"——这是最好的学习方式。 > > 祝考试顺利!🎯