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

22 KiB
Raw Blame History

📖 操作系统复习与教学笔记

目标:不仅能应付考试,更能真正理解操作系统的工作原理 定位:这份笔记不是简单的知识点罗列,而是教你"怎么学、怎么记、怎么做题"


目录

  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

pid_t pid = fork();
if (pid == 0) {
    // 子进程代码
} else {
    // 父进程代码pid 是子进程的 PID
}

输出特点

  • 子进程和父进程的代码都会执行
  • 子进程从 fork() 返回处开始执行(不是从 main 开始)
  • "A\n" 只有一个进程输出,"B\n" 和 "C\n" 各输出一次(因为 fork 后两个进程都继续)

经典模式 2嵌套 fork

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

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 生产者消费者(多线程同步)

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 基本框架

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步法

// 第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 常用命令速查

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它会怎么做"——这是最好的学习方式。

祝考试顺利!🎯