Files
obsidian/操作系统/复习与教学笔记(加强版).md

689 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 📖 操作系统复习与教学笔记
> **目标**:不仅能应付考试,更能真正理解操作系统的工作原理
> **定位**:这份笔记不是简单的知识点罗列,而是教你"怎么学、怎么记、怎么做题"
---
## 目录
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 ✓缺页 换掉最久未用的11离上次最远
0 7 0 2 ✗命中 0已存在更新它的使用时间
3 3 0 2 ✓缺页 换掉最久未用的77离上次最远
0 3 0 2 ✗命中 0已存在
4 3 0 4 ✓缺页 换掉最久未用的22离上次最远
...以此类推
```
#### ⚠️ 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()` 就像细胞分裂 — 调用一次,变成两个进程,从同一行代码继续执行。
#### 黄金法则
```
法则1fork() 一次,返回两次
- 父进程返回值 = 子进程的 PID
- 子进程返回值 = 0
法则2子进程从 fork() 的下一行开始执行(不是从 main 开头)
法则3fork() 嵌套时,每个进程都要独立分析它的执行路径
法则4父进程总数 = 2^nn为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(父)
```
#### 经典模式 3fork + 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) |
| CFSLinux | 红黑树 | 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它会怎么做"——这是最好的学习方式。
>
> 祝考试顺利!🎯