Y
+ end
+```
+
+### 6.3 实验任务:测量加速比
+
+实验中的 `task64.c` 要求:
+1. 修改 `psum64.c`,添加计时功能
+2. 分别用 1、2、4、8 个线程运行
+3. 计算每个配置的加速比和效率
+4. 分析为什么加速比不是线性的
+
+**预期结果**:
+
+| 线程数 | 执行时间 | 加速比 | 效率 |
+|:------:|:--------:|:------:|:----:|
+| 1 | T1 | 1.0 | 100% |
+| 2 | T2 | ~1.8 | ~90% |
+| 4 | T4 | ~3.2 | ~80% |
+| 8 | T8 | ~5.5 | ~69% |
+
+---
+
+## 七、fork vs pthread_create 性能对比
+
+### 7.1 为什么线程更快
+
+`fork()` 创建进程时需要复制父进程的整个地址空间(虽然有写时复制优化),而 `pthread_create()` 只需要分配一个新的栈和少量元数据。
+
+```mermaid
+graph LR
+ subgraph "fork() 创建进程"
+ F1["复制页表"] --> F2["复制地址空间
(写时复制)"]
+ F2 --> F3["创建新的 PCB"]
+ F3 --> F4["开销大: ~ms 级"]
+ end
+
+ subgraph "pthread_create() 创建线程"
+ T1["分配栈空间"] --> T2["设置线程属性"]
+ T2 --> T3["创建线程元数据"]
+ T3 --> T4["开销小: ~us 级"]
+ end
+
+ style F4 fill:#ffcdd2
+ style T4 fill:#e8f5e9
+```
+
+### 7.2 实验任务:测量创建时间
+
+实验中的 `task66.c` 要求分别测量 `fork()` 和 `pthread_create()` 的创建时间:
+
+```c
+// 测量 fork 创建时间
+clock_gettime(CLOCK_MONOTONIC, &start);
+for (i = 0; i < N; i++) {
+ if (fork() == 0) exit(0); // 子进程立即退出
+ wait(NULL);
+}
+clock_gettime(CLOCK_MONOTONIC, &end);
+
+// 测量 pthread_create 创建时间
+clock_gettime(CLOCK_MONOTONIC, &start);
+for (i = 0; i < N; i++) {
+ pthread_create(&tid, NULL, dummy, NULL);
+ pthread_join(tid, NULL);
+}
+clock_gettime(CLOCK_MONOTONIC, &end);
+```
+
+**预期结果**:`pthread_create` 通常比 `fork` 快 10-100 倍。
+
+---
+
+## 八、动态线程池
+
+### 8.1 为什么需要线程池
+
+频繁创建和销毁线程会带来较大开销。线程池预先创建一组线程,当有任务到来时从池中取出一个线程执行,执行完毕后线程归还池中,避免反复创建销毁。
+
+### 8.2 sbuf_t 带缓冲区的线程池
+
+实验中的 `task67.c` 实现了一个动态线程池,核心数据结构 `sbuf_t`:
+
+```c
+typedef struct {
+ int *buf; // 缓冲区数组
+ int n; // 缓冲区容量
+ int front; // 队首索引
+ int rear; // 队尾索引
+ sem_t mutex; // 互斥访问
+ sem_t slots; // 空槽位信号量
+ sem_t items; // 满槽位信号量
+} sbuf_t;
+```
+
+**动态调整策略**:
+- **缓冲区满时翻倍**:`buf = realloc(buf, 2 * n)`
+- **缓冲区空时减半**:`buf = realloc(buf, n / 2)`
+
+```mermaid
+graph TD
+ subgraph "线程池架构"
+ MAIN["主线程
接收任务"] -->|"放入缓冲区"| BUF["sbuf_t 缓冲区"]
+ BUF -->|"取出任务"| W1["工作线程1"]
+ BUF -->|"取出任务"| W2["工作线程2"]
+ BUF -->|"取出任务"| W3["工作线程3"]
+ end
+
+ subgraph "动态调整"
+ FULL["缓冲区满"] -->|"realloc"| DOUBLE["容量翻倍"]
+ EMPTY["缓冲区空"] -->|"realloc"| HALF["容量减半"]
+ end
+
+ style MAIN fill:#e1f5fe
+ style BUF fill:#fff3e0
+ style W1 fill:#e8f5e9
+ style W2 fill:#e8f5e9
+ style W3 fill:#e8f5e9
+```
+
+---
+
+## 九、线程安全的数据结构
+
+### 9.1 tickets 售票问题
+
+参考 `实例源代码/chap6/tickets1.c`(有竞态条件)和 `tickets2.c`(使用互斥锁修复):
+
+```c
+// tickets2.c - 使用互斥锁保护售票
+int tickets = 10;
+pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+
+void *counter(void *no) {
+ while (tickets > 0) {
+ pthread_mutex_lock(&mutex); // 加锁
+ if (tickets > 0) { // 双重检查
+ printf("柜台%d 卖出一张票,票号为%d\n", (int)no, tickets);
+ usleep(1);
+ tickets--;
+ }
+ pthread_mutex_unlock(&mutex); // 解锁
+ usleep(1);
+ }
+}
+```
+
+**关键点**:`while` 条件检查和 `if` 条件检查都需要在锁内进行(双重检查),否则可能出现超卖。
+
+---
+
+## 十、实验任务概览
+
+本讲对应 [[实验03_多线程编程]],包含以下任务:
+
+| 任务 | 文件 | 内容 | 核心知识点 |
+|------|------|------|------------|
+| 任务一 | task61.c | 3 个对等线程打印姓名/学号/时间 | pthread_create/join |
+| 任务二 | task62.c | 用信号量修复 badcount.c 的竞态条件 | sem_wait/sem_post |
+| 任务三 | task63.c | k 个生产者 + m 个消费者,信号量同步 | 生产者-消费者模型 |
+| 任务四 | task64.c | psum64.c 并行求和,测量不同线程数的加速比 | 并行计算 + 性能分析 |
+| 任务五 | task66.c | 测量 fork vs pthread_create 时间 | 进程/线程创建开销对比 |
+| 任务六 | task67.c | 动态线程池(sbuf_t 缓冲区满翻倍/空减半) | 线程池 + 动态数据结构 |
+
+---
+
+## 知识关联
+
+- 线程是轻量级进程,理解 [[06_进程控制]] 中的 fork 有助于理解线程的优势
+- 线程间的同步问题在死锁章节中会进一步讨论
+- 生产者-消费者模型在 [[08_进程间通信]] 中也有管道和共享内存的实现方式
+- 线程池在并发网络服务器中是核心组件
+
+---
+
+## 思考题
+
+1. **互斥锁 vs 信号量**:互斥锁和二值信号量(初值为 1 的信号量)看起来功能相同,它们有什么本质区别?什么时候必须用信号量而不能用互斥锁?
+2. **死锁的产生**:如果一个线程对已经加锁的互斥锁再次调用 `pthread_mutex_lock()`,会发生什么?如果两个线程分别持有对方需要的锁呢?
+3. **线程安全的函数**:为什么 `printf()` 是线程安全的,而 `count++` 不是?如何判断一个函数是否线程安全?
+4. **并行计算的瓶颈**:为什么 p 个线程的加速比通常达不到理想的 p 倍?哪些因素限制了并行加速?
+5. **fork vs pthread**:在什么场景下应该用多进程而不是多线程?多进程模型有什么优势?
+
+---
+
+## 扩展阅读
+
+- 《UNIX环境高级编程》第11章:线程
+- 《深入理解计算机系统》第12章:并发编程
+- 《POSIX 多线程编程指南》
+- [Pthreads Tutorial (LLNL)](https://computing.llnl.gov/tutorials/pthreads/)
diff --git a/操作系统/08_进程间通信/08_进程间通信.md b/操作系统/08_进程间通信/08_进程间通信.md
new file mode 100644
index 0000000..0d515a7
--- /dev/null
+++ b/操作系统/08_进程间通信/08_进程间通信.md
@@ -0,0 +1,406 @@
+# 第08讲:进程间通信
+
+> 🎯 **本节目标**:掌握管道、消息队列、共享内存等进程间通信方式
+
+## 📋 前置知识
+- [[06_进程控制]] — 进程的基本概念
+- [[06_进程控制_深入]] — fork 和 exec 的工作原理
+
+---
+
+## 🤔 为什么需要这个?
+
+进程之间是相互隔离的,每个进程有自己的地址空间。但有时候进程需要协作:
+- Shell 需要将 `ls` 的输出传给 `grep`
+- 浏览器需要与下载管理器通信
+- 数据库需要与应用程序交互
+
+**进程间通信(IPC)** 就是解决这个问题的。
+
+**生活比喻**:
+- **管道** = 对讲机(单向通信)
+- **消息队列** = 邮箱(异步通信)
+- **共享内存** = 共享白板(最快的通信方式)
+
+---
+
+## 📖 核心概念
+
+### 1. IPC 方式概览
+
+```mermaid
+graph TD
+ A[进程间通信 IPC] --> B[管道 Pipe]
+ A --> C[消息队列 Message Queue]
+ A --> D[共享内存 Shared Memory]
+ A --> E[信号 Signal]
+ A --> F[信号量 Semaphore]
+ A --> G[套接字 Socket]
+
+ B --> B1[单向通信]
+ C --> C1[异步通信]
+ D --> D1[最快]
+ E --> E1[异步通知]
+ F --> F1[同步控制]
+ G --> G1[网络通信]
+
+ style A fill:#e1f5fe
+ style D fill:#e8f5e9
+```
+
+**对比**:
+| 方式 | 优点 | 缺点 | 适用场景 |
+|------|------|------|----------|
+| 管道 | 简单、易用 | 单向、只能父子进程 | Shell 命令组合 |
+| 消息队列 | 异步、可按类型读取 | 有大小限制 | 任务分发 |
+| 共享内存 | 最快 | 需要同步机制 | 大量数据交换 |
+| 信号 | 异步通知 | 只能传递信号编号 | 事件通知 |
+| 信号量 | 同步控制 | 不能传递数据 | 互斥、同步 |
+| 套接字 | 跨网络 | 开销大 | 网络通信 |
+
+### 2. 管道(Pipe)
+
+管道是最古老的 IPC 方式,用于有亲缘关系的进程之间:
+
+```mermaid
+graph LR
+ A[写端 fd[1]] -->|数据流| B[读端 fd[0]]
+
+ style A fill:#e8f5e9
+ style B fill:#e1f5fe
+```
+
+**特点**:
+- **单向**:只能从一端写,另一端读
+- **有亲缘关系**:通常用于父子进程
+- **自带同步**:读端空时阻塞,写端满时阻塞
+
+### 3. 命名管道(FIFO)
+
+命名管道让没有亲缘关系的进程也能通信:
+
+```mermaid
+graph LR
+ A[进程1] -->|写入| B[/tmp/my_fifo]
+ B -->|读取| C[进程2]
+
+ style B fill:#fff3e0
+```
+
+**特点**:
+- 有文件名,存在于文件系统中
+- 任意进程都可以打开
+- 使用方法与普通文件相同
+
+### 4. 消息队列
+
+消息队列是一种异步通信方式:
+
+```mermaid
+graph LR
+ A[发送方 msgsnd] -->|消息| B[消息队列]
+ B -->|消息| C[接收方 msgrcv]
+
+ style B fill:#fff3e0
+```
+
+**消息结构**:
+```c
+struct msgbuf {
+ long mtype; // 消息类型
+ char mtext[512]; // 消息内容
+};
+```
+
+**优势**:
+- 可以按类型读取消息
+- 异步通信,不需要同步
+- 可以设置优先级
+
+### 5. 共享内存
+
+共享内存是最快的 IPC 方式:
+
+```mermaid
+graph TD
+ A[进程1] -->|读写| B[共享内存区域]
+ C[进程2] -->|读写| B
+
+ style B fill:#e8f5e9
+```
+
+**工作流程**:
+1. 创建共享内存段
+2. 将共享内存映射到进程地址空间
+3. 直接读写共享内存
+4. 使用完毕后分离
+
+**注意**:共享内存本身不提供同步机制,需要配合信号量使用。
+
+---
+
+## 💻 动手实践
+
+### 示例1:使用管道通信
+
+```c
+// pipe1.c - 管道通信示例
+#include "wrapper.h"
+
+int main() {
+ int count;
+ int fds[2]; // fds[0]=读端, fds[1]=写端
+ const char some_data[] = "1234567890";
+ char buffer[BUFSIZ + 1];
+
+ memset(buffer, '\0', sizeof(buffer));
+
+ // 创建管道
+ pipe(fds);
+
+ // 向管道写入数据
+ count = Write(fds[1], (void *)some_data, strlen(some_data));
+ printf("Wrote %d bytes\n", count);
+
+ // 从管道读取数据
+ count = Read(fds[0], (void *)buffer, BUFSIZ);
+ printf("Read %d bytes: %s\n", count, buffer);
+
+ exit(EXIT_SUCCESS);
+}
+```
+
+**编译运行**:
+```bash
+gcc -o pipe1 pipe1.c -L. -lwrapper
+./pipe1
+```
+
+**预期输出**:
+```
+Wrote 10 bytes
+Read 10 bytes: 1234567890
+```
+
+### 示例2:创建命名管道
+
+```c
+// fifo1.c - 创建命名管道
+#include
作业池]
+ end
+
+ subgraph 内存
+ B[就绪队列]
+ C[阻塞队列]
+ D[CPU 执行]
+ end
+
+ A -->|高级调度
长程调度
作业调度| B
+ B -->|低级调度
短程调度
进程调度| D
+ D -->|时间片用完/等待事件| B
+ D -->|等待I/O| C
+ C -->|I/O完成| B
+
+ B -->|中级调度
交换调度
内存调度| A
+
+ style A fill:#f9f,stroke:#333
+ style D fill:#ff9,stroke:#333
+```
+
+| 调度级别 | 别名 | 频率 | 功能 |
+|----------|------|------|------|
+| **高级调度** | 作业调度 / 长程调度 | 最低 | 从后备队列选择作业调入内存 |
+| **中级调度** | 交换调度 / 内存调度 | 中等 | 将暂时不用的进程换出到外存(挂起) |
+| **低级调度** | 进程调度 / 短程调度 | 最高 | 从就绪队列选择进程分配 CPU |
+
+---
+
+## 四、调度时机
+
+### 4.1 何时触发调度
+
+```mermaid
+flowchart TD
+ A{发生什么事件?} --> B[进程结束]
+ A --> C[进程等待事件
如I/O请求]
+ A --> D[时间片用完]
+ A --> E[新进程产生]
+ A --> F[等待的事件已发生
如I/O完成]
+
+ B --> G[执行调度]
+ C --> G
+ D --> G
+ E --> G
+ F --> G
+
+ G --> H[选择下一个
运行的进程]
+
+ style G fill:#ff9,stroke:#333
+```
+
+| 调度时机 | 说明 | 触发场景 |
+|----------|------|----------|
+| 进程结束 | 运行进程执行完毕 | 正常退出 |
+| 等待事件 | 进程因 I/O 等阻塞 | 读磁盘、等待键盘输入 |
+| 时间片用完 | 当前进程的时间配额耗尽 | 时钟中断 |
+| 新进程产生 | 新进程进入就绪队列 | fork() 创建子进程 |
+| 等待事件已发生 | 阻塞进程变就绪 | I/O 中断 |
+
+---
+
+## 五、调度方式
+
+### 5.1 非抢占式调度
+
+进程**自愿放弃** CPU,系统不会强制剥夺。只有当进程结束、阻塞或主动让出时才调度。
+
+- **优点**:实现简单,系统开销小
+- **缺点**:响应时间不可预测,不适合交互式系统
+
+### 5.2 抢占式调度
+
+系统**强制剥夺**当前运行进程的 CPU,分配给其他进程。
+
+- **优点**:响应快,适合分时/实时系统
+- **缺点**:实现复杂,需要保存/恢复现场,开销较大
+
+```mermaid
+graph LR
+ subgraph 非抢占式
+ A[进程A运行] -->|自愿放弃| B[调度]
+ B --> C[进程B运行]
+ end
+
+ subgraph 抢占式
+ D[进程A运行] -->|强制剥夺| E[调度]
+ E --> F[进程B运行]
+ end
+
+ style E fill:#f96,stroke:#333
+```
+
+---
+
+## 六、调度算法的设计目标
+
+| 系统类型 | 主要目标 | 关键指标 |
+|----------|----------|----------|
+| **批处理系统** | 周转时间短、吞吐量高 | 平均周转时间、吞吐量 |
+| **分时系统** | 响应时间快、均衡性 | 响应时间、公平性 |
+| **实时系统** | 满足截止时间、可预测性 | 截止时间满足率 |
+
+### 关键性能指标
+
+$$\text{周转时间} = \text{完成时间} - \text{提交时间}$$
+
+$$\text{等待时间} = \text{周转时间} - \text{运行时间}$$
+
+$$\text{带权周转时间} = \frac{\text{周转时间}}{\text{运行时间}}$$
+
+---
+
+## 七、批处理调度算法
+
+### 7.1 FCFS — 先来先服务
+
+**First Come First Served**:按进程到达就绪队列的**先后顺序**依次调度。
+
+#### 示例
+
+假设三个进程 P1、P2、P3 几乎同时到达:
+
+| 进程 | 运行时间 |
+|------|----------|
+| P1 | 24 |
+| P2 | 3 |
+| P3 | 3 |
+
+**按 P1→P2→P3 顺序执行:**
+
+```
+时间轴(Gantt图):
+|--------P1(24)--------|--P2(3)--|--P3(3)--|
+0 24 27 30
+```
+
+| 进程 | 完成时间 | 周转时间 | 等待时间 |
+|------|----------|----------|----------|
+| P1 | 24 | 24 | 0 |
+| P2 | 27 | 27 | 24 |
+| P3 | 30 | 30 | 27 |
+| **平均** | | **27** | **17** |
+
+**按 P2→P3→P1 顺序执行(短作业在前):**
+
+```
+时间轴(Gantt图):
+|--P2(3)--|--P3(3)--|--------P1(24)--------|
+0 3 6 30
+```
+
+| 进程 | 完成时间 | 周转时间 | 等待时间 |
+|------|----------|----------|----------|
+| P2 | 3 | 3 | 0 |
+| P3 | 6 | 6 | 3 |
+| P1 | 30 | 30 | 6 |
+| **平均** | | **13** | **3** |
+
+> **结论**:FCFS 对短作业非常不利,平均等待时间从 17 降到仅 3(仅调整顺序)。
+
+### 7.2 SJF — 短作业优先
+
+**Shortest Job First**:选择**估计运行时间最短**的进程优先执行。
+
+#### 两种变体
+
+```mermaid
+graph TD
+ A[SJF 短作业优先] --> B[非抢占式 SJF]
+ A --> C[抢占式 SJF
即 SRTF]
+
+ B --> D[进程开始执行后
不会被中断]
+ C --> E[新进程到达时
比较剩余时间
更短则抢占]
+
+ style C fill:#f96,stroke:#333
+```
+
+#### 示例(非抢占式 SJF)
+
+| 进程 | 到达时间 | 运行时间 |
+|------|----------|----------|
+| P1 | 0 | 7 |
+| P2 | 2 | 4 |
+| P3 | 4 | 1 |
+| P4 | 5 | 4 |
+
+```
+Gantt图(非抢占SJF):
+|--P1(7)--|-P3(1)-|---P2(4)---|---P4(4)---|
+0 7 8 12 16
+```
+
+- t=0 时只有 P1,P1 先执行
+- t=7 时 P2、P3、P4 都已到达,选最短的 P3(运行时间 1)
+- t=8 时选 P2(运行时间 4)
+- t=12 时选 P4
+
+#### 示例(抢占式 SRTF)
+
+| 进程 | 到达时间 | 运行时间 |
+|------|----------|----------|
+| P1 | 0 | 7 |
+| P2 | 2 | 4 |
+| P3 | 4 | 1 |
+| P4 | 5 | 4 |
+
+```
+Gantt图(抢占式SRTF):
+|--P1--|P1|P3|---P2---|---P4---|--P1--|
+0 2 4 5 9 13 16
+
+时间线:
+0-2: P1运行(剩5)
+2: P2到达(需4) < P1剩余(5), P2抢占
+2-4: P2运行(剩2)
+4: P3到达(需1) < P2剩余(2), P3抢占
+4-5: P3完成
+5: P4到达(需4) = P2剩余(2), P2先到
+5-7: P2完成(剩2)
+7: P4(需4) vs P1(剩5), P4更短
+7-11: P4完成
+11-16: P1完成(剩5)
+```
+
+| 进程 | 完成时间 | 周转时间 | 等待时间 |
+|------|----------|----------|----------|
+| P1 | 16 | 16 | 9 |
+| P2 | 9 | 7 | 3 |
+| P3 | 5 | 1 | 0 |
+| P4 | 13 | 8 | 4 |
+| **平均** | | **8** | **4** |
+
+#### SJF 的优缺点
+
+| 优点 | 缺点 |
+|------|------|
+| 平均等待时间最小(理论上最优) | 对长作业不利,可能产生**饥饿** |
+| 吞吐量高 | 需要预知运行时间(难以精确估计) |
+
+### 7.3 HRRF — 响应比高优先
+
+**Highest Response Ratio First**:FCFS 和 SJF 的折衷方案。
+
+$$\text{响应比} = 1 + \frac{\text{等待时间}}{\text{估计运行时间}}$$
+
+- 短作业:运行时间小,响应比容易变高 → 兼顾 SJF
+- 长作业:随着等待时间增长,响应比逐渐增大 → 避免饥饿
+
+```mermaid
+flowchart LR
+ A[选择响应比最高的进程] --> B{响应比 = 1 + 等待时间/运行时间}
+ B --> C[短作业:等待时间相同时
运行时间短 → 响应比高]
+ B --> D[长作业:等待时间越长
响应比越高 → 不会饥饿]
+```
+
+#### 示例
+
+| 进程 | 到达时间 | 运行时间 |
+|------|----------|----------|
+| P1 | 0 | 20 |
+| P2 | 0 | 5 |
+| P3 | 0 | 10 |
+
+t=0: 三个进程同时到达,计算响应比:
+- P1: 1 + 0/20 = 1
+- P2: 1 + 0/5 = 1
+- P3: 1 + 0/10 = 1
+
+全部相同,按 FCFS 选 P1(运行时间 20)。
+
+t=20: P1 完成,计算剩余进程响应比:
+- P2: 1 + 20/5 = 5
+- P3: 1 + 20/10 = 3
+
+选 P2(响应比 5),P2 执行到 t=25。
+
+t=25: 选 P3(响应比 1+25/10=3.5),P3 执行到 t=35。
+
+### 7.4 优先级调度
+
+**Priority Scheduling**:为每个进程分配优先级,选择优先级最高的进程执行。
+
+| 分类方式 | 说明 |
+|----------|------|
+| **静态优先级** | 创建时确定,运行期间不变 |
+| **动态优先级** | 运行期间根据情况调整 |
+| **抢占式** | 高优先级进程到达时可抢占当前进程 |
+| **非抢占式** | 等当前进程完成后才重新调度 |
+
+---
+
+## 八、分时调度算法
+
+### 8.1 RR — 时间片轮转
+
+**Round Robin**:就绪队列中的进程按**先来先服务**依次执行,每个进程每次最多运行一个**时间片**(quantum)的时间。
+
+#### 示例
+
+4 个进程,时间片 q = 4:
+
+| 进程 | 到达时间 | 运行时间 |
+|------|----------|----------|
+| P1 | 0 | 24 |
+| P2 | 0 | 3 |
+| P3 | 0 | 3 |
+| P4 | 0 | 6 |
+
+```
+Gantt图(时间片q=4):
+|P1(4)|P2(3)|P3(3)|P4(4)|P1(4)|P4(2)|P1(4)|P1(4)|P1(4)|P1(4)|
+0 4 7 10 14 18 20 24 28 32 36
+
+执行顺序:
+0-4: P1执行(剩20)
+4-7: P2执行(完成) ← P2只需3 < 时间片4
+7-10: P3执行(完成) ← P3只需3 < 时间片4
+10-14: P4执行(剩2)
+14-18: P1执行(剩16)
+18-20: P4执行(完成) ← P4剩2 < 时间片4
+20-24: P1执行(剩12)
+24-28: P1执行(剩8)
+28-32: P1执行(剩4)
+32-36: P1执行(完成)
+```
+
+| 进程 | 完成时间 | 周转时间 | 等待时间 |
+|------|----------|----------|----------|
+| P1 | 36 | 36 | 12 |
+| P2 | 7 | 7 | 4 |
+| P3 | 10 | 10 | 7 |
+| P4 | 20 | 20 | 14 |
+| **平均** | | **18.25** | **9.25** |
+
+#### 时间片大小的影响
+
+```mermaid
+graph TD
+ A[时间片 q 的选择] --> B[q 太大]
+ A --> C[q 太小]
+ A --> D[q 适中]
+
+ B --> E[退化为 FCFS
响应时间变长]
+ C --> F[进程切换频繁
系统开销过大]
+ D --> G[兼顾响应性和效率
通常 10~100ms]
+
+ style B fill:#f99,stroke:#333
+ style C fill:#f99,stroke:#333
+ style D fill:#9f9,stroke:#333
+```
+
+#### 时间片选择示例
+
+> 10 个进程,进程切换开销为 10ms。
+>
+> - 时间片 q = 200ms:每个进程切换一次,总切换时间 = 10×10ms = 100ms
+> - 开销比 = 100 / (10×200) = **5%** → 可接受
+> - 时间片 q = 100ms:每个进程切换约2次,总切换时间 = 20×10ms = 200ms
+> - 开销比 = 200 / (10×200) = **10%** → 开销偏高
+
+### 8.2 多级反馈队列(MFQ)
+
+**Multi-level Feedback Queue**:综合多种调度算法优点的经典调度算法。
+
+```mermaid
+graph TB
+ subgraph MFQ多级反馈队列
+ A[新进程到达] --> B[队列1
优先级最高
时间片最小 如4ms]
+ B -->|时间片用完
未完成| C[队列2
优先级中等
时间片中等 如8ms]
+ C -->|时间片用完
未完成| D[队列3
优先级最低
时间片最大 如16ms]
+ end
+
+ B -->|完成| E[进程结束]
+ C -->|完成| E
+ D -->|完成| E
+
+ B -->|等待I/O| F[阻塞]
+ F -->|I/O完成| B
+
+ style B fill:#9f9,stroke:#333
+ style C fill:#ff9,stroke:#333
+ style D fill:#f99,stroke:#333
+```
+
+#### MFQ 的核心规则
+
+1. **新进程**进入**最高优先级**队列(队列1)
+2. 进程在当前队列用完时间片 → **降级**到下一优先级队列
+3. 高优先级队列**空**时,才调度低优先级队列
+4. 可设置**队列间调度策略**:固定优先级 或 按时间比例分配
+
+| 特点 | 说明 |
+|------|------|
+| 自适应 | I/O 密集型进程常在高优先级队列(因时间片未用完就阻塞) |
+| 周转时间短 | CPU 密集型进程最终会降到低优先级队列,用大时间片执行 |
+| 公平性 | 各类用户(交互型、批处理型)都能得到合理服务 |
+
+---
+
+## 九、实时调度算法
+
+### 9.1 EDF — 最早截止时间优先
+
+**Earliest Deadline First**:截止时间越早的进程,优先级越高。
+
+```mermaid
+gantt
+ title EDF 调度示例
+ dateFormat X
+ axisFormat %s
+
+ section 进程
+ P1(截止t=4) :a1, 0, 2
+ P2(截止t=6) :a2, 2, 5
+ P3(截止t=8) :a3, 5, 7
+```
+
+| 进程 | 到达时间 | 运行时间 | 截止时间 |
+|------|----------|----------|----------|
+| P1 | 0 | 2 | 4 |
+| P2 | 0 | 3 | 6 |
+| P3 | 0 | 2 | 8 |
+
+调度过程:
+1. t=0:P1(截止4) < P2(截止6) < P3(截止8) → 执行 P1
+2. t=2:P1 完成,P2(截止6) < P3(截止8) → 执行 P2
+3. t=5:P2 完成,执行 P3
+4. t=7:全部完成,均满足截止时间
+
+### 9.2 LLF — 最低松弛度优先
+
+**Least Laxity First**:选择**松弛度最小**的进程优先执行。
+
+$$\text{松弛度} = \text{必须完成时间} - \text{本身运行时间} - \text{当前时间}$$
+
+> 松弛度越小,说明该进程越"紧迫"。
+
+#### 示例
+
+| 进程 | 运行时间 | 截止时间 |
+|------|----------|----------|
+| P1 | 2 | 8 |
+| P2 | 4 | 12 |
+
+t=0 时刻:
+- P1 松弛度 = 8 - 2 - 0 = **6**
+- P2 松弛度 = 12 - 4 - 0 = **8**
+
+P1 松弛度更小,先执行 P1。
+
+---
+
+## 十、优先级倒置问题
+
+### 10.1 问题描述
+
+**优先级倒置(Priority Inversion)**:高优先级进程被低优先级进程间接阻塞的现象。
+
+```mermaid
+sequenceDiagram
+ participant H as 高优先级进程H
+ participant M as 中优先级进程M
+ participant L as 低优先级进程L
+ participant R as 共享资源R
+
+ L->>R: 获得资源R(加锁)
+ H->>R: 请求资源R → 阻塞!
+ Note over H: H必须等待L释放R
+ M->>M: M到达,优先级高于L
+ Note over M: M抢占L执行
+ Note over H: H被M间接阻塞!
优先级倒置发生!
+ M->>M: M执行完毕
+ L->>R: 释放资源R
+ H->>R: 获得资源R,继续执行
+```
+
+### 10.2 解决方案——优先级继承
+
+**Priority Inheritance Protocol**:当高优先级进程因共享资源阻塞时,**临时提升**持有该资源的低优先级进程的优先级,使其尽快完成并释放资源。
+
+```mermaid
+sequenceDiagram
+ participant H as 高优先级进程H
+ participant L as 低优先级进程L(提升优先级)
+ participant M as 中优先级进程M
+ participant R as 共享资源R
+
+ L->>R: 获得资源R
+ H->>R: 请求资源R → 阻塞
+ Note over L: L的优先级临时提升为H的优先级
+ Note over M: M到达,但L优先级已提升
+ Note over M: M无法抢占L!
+ L->>R: 释放资源R
+ Note over L: L优先级恢复原值
+ H->>R: 获得资源R,继续执行
+```
+
+---
+
+## 十一、Linux 调度器
+
+### 11.1 CFS — 完全公平调度器
+
+**Completely Fair Scheduler**:Linux 默认调度器,基于**虚拟运行时间(vruntime)**实现公平调度。
+
+核心思想:**vruntime 最小的进程优先执行**。
+
+$$\text{vruntime} += \text{实际运行时间} \times \frac{\text{nice 值基准权重}}{\text{该进程权重}}$$
+
+```mermaid
+graph TD
+ A[CFS调度器] --> B{选择 vruntime
最小的进程}
+ B --> C[红黑树组织
就绪进程]
+ C --> D[最左叶子节点
= vruntime 最小]
+ D --> E[执行该进程]
+ E --> F[更新 vruntime]
+ F --> C
+
+ style D fill:#9f9,stroke:#333
+```
+
+#### CFS 特点
+
+| 特性 | 说明 |
+|------|------|
+| 无固定时间片 | 根据进程权重和系统负载动态计算 |
+| 红黑树结构 | O(log n) 插入/查找,高效调度 |
+| 公平性保证 | nice 值越低(优先级越高),vruntime 增长越慢 |
+| 自动适配 | I/O 密集型进程 vruntime 自然较小,获得更多 CPU |
+
+### 11.2 调度器类层次
+
+```mermaid
+graph TB
+ A[调度器类优先级] --> B[Stop Task
最高优先级
停止特定CPU]
+ A --> C[Real-Time
实时调度类]
+ A --> D[Fair
CFS 调度类
普通进程]
+ A --> E[Idle Task
最低优先级
空闲任务]
+
+ B --> C --> D --> E
+
+ style B fill:#f66,stroke:#333
+ style C fill:#f96,stroke:#333
+ style D fill:#9f9,stroke:#333
+ style E fill:#99f,stroke:#333
+```
+
+**调度顺序**:Stop Task > Real-Time > Fair > Idle Task
+
+每次调度时,优先从高优先级调度类中选择进程。
+
+### 11.3 实时进程调度策略
+
+| 策略 | 说明 |
+|------|------|
+| **SCHED_FIFO** | 先来先服务。实时进程一旦获得 CPU,将一直执行直到主动放弃或被更高优先级进程抢占 |
+| **SCHED_RR** | 时间片轮转。同优先级的实时进程轮流使用 CPU,时间片用完后回到队尾 |
+
+---
+
+## 十二、调度算法对比总结
+
+```mermaid
+graph LR
+ subgraph 批处理算法
+ FCFS[FCFS
简单公平]
+ SJF[SJF/SRTF
最优平均等待]
+ HRRF[HRRF
FCFS+SJF折衷]
+ PRIO[优先级调度
灵活控制]
+ end
+
+ subgraph 分时算法
+ RR[RR 轮转
公平响应]
+ MFQ[多级反馈队列
综合最优]
+ end
+
+ subgraph 实时算法
+ EDF[EDF
截止时间优先]
+ LLF[LLF
松弛度优先]
+ end
+
+ FCFS -.->|缺点: 短作业等待久| SJF
+ SJF -.->|缺点: 长作业饥饿| HRRF
+ RR -.->|时间片选择关键| MFQ
+```
+
+| 算法 | 类型 | 抢占 | 优点 | 缺点 |
+|------|------|------|------|------|
+| FCFS | 批处理 | 否 | 简单公平 | 短作业等待时间长 |
+| SJF | 批处理 | 否 | 最小平均等待 | 长作业饥饿 |
+| SRTF | 批处理 | 是 | 更优的平均等待 | 长作业饥饿 |
+| HRRF | 批处理 | 否 | 兼顾公平 | 需预估运行时间 |
+| RR | 分时 | 是 | 响应快 | 时间片选择影响大 |
+| MFQ | 分时 | 是 | 自适应各类进程 | 参数调整复杂 |
+| EDF | 实时 | 是 | 利用率可达100% | 过载时不确定 |
+| LLF | 实时 | 是 | 松弛度精确 | 频繁切换开销 |
+
+---
+
+## 十三、本讲关键公式
+
+$$\text{周转时间} = \text{完成时间} - \text{提交时间}$$
+
+$$\text{等待时间} = \text{周转时间} - \text{运行时间}$$
+
+$$\text{带权周转时间} = \frac{\text{周转时间}}{\text{运行时间}} \geq 1$$
+
+$$\text{平均周转时间} = \frac{1}{n}\sum_{i=1}^{n}T_i$$
+
+$$\text{响应比} = 1 + \frac{\text{等待时间}}{\text{估计运行时间}}$$
+
+$$\text{松弛度} = \text{必须完成时间} - \text{本身运行时间} - \text{当前时间}$$
+
+$$\text{时间片开销比} = \frac{\text{进程数} \times \text{切换时间}}{\text{进程数} \times \text{时间片}} = \frac{\text{切换时间}}{\text{时间片}}$$
+
+---
+
+## 十四、常见考点
+
+1. **FCFS/SJF/RR 手工计算**:给定进程到达时间和运行时间,画 Gantt 图,计算平均等待时间和周转时间
+2. **时间片大小对 RR 的影响**:太大退化为 FCFS,太小切换开销大
+3. **响应比计算**:HRRF 的每一步调度决策
+4. **优先级倒置场景分析**及优先级继承原理
+5. **CFS 核心思想**:vruntime 最小优先
+
+---
+
+**上一章**:[[06_进程控制]]
+**下一章**:[[12_死锁]]
diff --git a/操作系统/12_死锁/12_死锁.md b/操作系统/12_死锁/12_死锁.md
new file mode 100644
index 0000000..a1c74b7
--- /dev/null
+++ b/操作系统/12_死锁/12_死锁.md
@@ -0,0 +1,376 @@
+# 第12讲:死锁
+
+> 🎯 **本节目标**:理解死锁的概念,掌握死锁的预防、避免和检测方法
+
+## 📋 前置知识
+- [[07_多线程编程]] — 互斥锁和信号量
+- [[11_处理机调度]] — 调度的基本概念
+
+---
+
+## 🤔 为什么需要这个?
+
+想象这样一个场景:
+- 线程 A 持有锁 1,等待锁 2
+- 线程 B 持有锁 2,等待锁 1
+- 两个线程互相等待,永远无法继续
+
+这就是**死锁**——多个进程互相等待对方释放资源,导致所有进程都无法继续执行。
+
+**生活比喻**:
+- **死锁** = 两个人在狭窄的走廊相遇,谁都不肯让路,结果谁也过不去
+
+---
+
+## 📖 核心概念
+
+### 1. 死锁的四个必要条件
+
+```mermaid
+graph TD
+ A[死锁四个条件] --> B[互斥条件]
+ A --> C[持有并等待]
+ A --> D[不可抢占]
+ A --> E[循环等待]
+
+ B --> B1[资源一次只能被一个进程使用]
+ C --> C1[进程持有资源的同时请求新资源]
+ D --> D1[资源不能被强制剥夺]
+ E --> E1[存在进程的循环等待链]
+
+ style A fill:#ffcdd2
+```
+
+**必须同时满足这四个条件才会发生死锁**。
+
+### 2. 资源分配图
+
+```mermaid
+graph LR
+ subgraph 进程
+ P1[P1]
+ P2[P2]
+ end
+ subgraph 资源
+ R1[R1]
+ R2[R2]
+ end
+
+ R1 -->|分配| P1
+ R2 -->|分配| P2
+ P1 -->|请求| R2
+ P2 -->|请求| R1
+
+ style P1 fill:#e1f5fe
+ style P2 fill:#e1f5fe
+ style R1 fill:#e8f5e9
+ style R2 fill:#e8f5e9
+```
+
+**图例**:
+- 方框表示资源,圆圈表示进程
+- 资源→进程:已分配
+- 进程→资源:请求中
+
+### 3. 死锁的处理策略
+
+```mermaid
+graph TD
+ A[死锁处理策略] --> B[死锁预防]
+ A --> C[死锁避免]
+ A --> D[死锁检测]
+ A --> E[死锁恢复]
+
+ B --> B1[破坏四个条件之一]
+ C --> C1[银行家算法]
+ D --> D1[资源分配图]
+ E --> E1[终止进程]
+
+ style B fill:#e8f5e9
+ style C fill:#e1f5fe
+ style D fill:#fff3e0
+ style E fill:#ffcdd2
+```
+
+### 4. 死锁预防
+
+破坏死锁的四个必要条件之一:
+
+| 条件 | 破坏方法 | 代价 |
+|------|----------|------|
+| 互斥 | 使用可共享资源 | 不总是可行 |
+| 持有并等待 | 一次性申请所有资源 | 资源浪费 |
+| 不可抢占 | 允许抢占资源 | 实现复杂 |
+| 循环等待 | 按顺序申请资源 | 限制灵活性 |
+
+**资源有序分配法**:
+```c
+// 规定所有进程必须按编号顺序申请资源
+// 例如:先申请锁1,再申请锁2
+
+pthread_mutex_lock(&mutex1); // 正确
+pthread_mutex_lock(&mutex2);
+
+// 而不是
+pthread_mutex_lock(&mutex2); // 可能导致死锁
+pthread_mutex_lock(&mutex1);
+```
+
+### 5. 银行家算法
+
+```mermaid
+graph TD
+ A[银行家算法] --> B[检查请求是否安全]
+ B --> C{安全?}
+ C -->|是| D[分配资源]
+ C -->|否| E[等待]
+ D --> F[更新数据结构]
+ E --> G[阻塞进程]
+
+ style C fill:#fff3e0
+ style D fill:#e8f5e9
+ style E fill:#ffcdd2
+```
+
+**安全状态**:存在一个安全序列,使得所有进程都能顺利完成
+
+**数据结构**:
+- `Available[]`:可用资源向量
+- `Max[][]`:最大需求矩阵
+- `Allocation[][]`:已分配矩阵
+- `Need[][]`:还需要的资源矩阵
+
+**算法步骤**:
+1. 检查请求是否超过需要
+2. 检查请求是否超过可用资源
+3. 尝试分配,检查是否安全
+4. 如果安全,正式分配;否则等待
+
+### 6. 死锁检测
+
+```mermaid
+graph TD
+ A[构建资源分配图] --> B[寻找环路]
+ B --> C{有环路?}
+ C -->|是| D[可能存在死锁]
+ C -->|否| E[无死锁]
+ D --> F[进一步分析]
+
+ style C fill:#fff3e0
+ style D fill:#ffcdd2
+ style E fill:#e8f5e9
+```
+
+**检测算法**:
+1. 构建资源分配图
+2. 使用深度优先搜索寻找环路
+3. 如果存在环路,可能存在死锁
+
+### 7. 死锁恢复
+
+```mermaid
+graph TD
+ A[检测到死锁] --> B[选择终止进程]
+ B --> C[回滚操作]
+ C --> D[释放资源]
+ D --> E[唤醒等待进程]
+
+ style A fill:#ffcdd2
+ style E fill:#e8f5e9
+```
+
+**恢复方法**:
+- **终止所有死锁进程**:简单但代价大
+- **逐个终止进程**:直到死锁解除
+- **资源抢占**:强制剥夺资源
+
+---
+
+## 💻 动手实践
+
+### 示例1:死锁演示
+
+```c
+// deadlock_demo.c - 死锁演示
+#include
源文件"] -->|预处理| B["hello.i
预处理后"]
+ B -->|编译| C["hello.s
汇编文件"]
+ C -->|汇编| D["hello.o
目标文件(可重定位)"]
+ D -->|链接| E["hello / a.out
可执行文件"]
+ E -->|装入| F["内存中运行的进程"]
+
+ style A fill:#e1f5fe
+ style C fill:#fff3e0
+ style D fill:#fce4ec
+ style E fill:#e8f5e9
+ style F fill:#f3e5f5
+```
+
+### 各阶段说明
+
+| 阶段 | 输入 | 输出 | 工具 | 说明 |
+|------|------|------|------|------|
+| **预处理** | `.c` | `.i` | cpp | 展开宏、头文件、条件编译 |
+| **编译** | `.i` | `.s` | cc1 | 翻译为汇编语言 |
+| **汇编** | `.s` | `.o` | as | 翻译为机器指令(可重定位目标文件) |
+| **链接** | `.o` | 可执行文件 | ld | 合并节段、解析符号引用、重定位 |
+| **装入** | 可执行文件 | 进程 | 加载器 | 将程序载入内存并创建进程 |
+
+### 查看目标文件的节段
+
+使用 `objdump -h` 可以查看可执行文件或目标文件中的节段(Section)信息:
+
+```bash
+gcc -c hello.c # 编译为可重定位目标文件
+objdump -h hello.o # 查看节段头信息
+
+gcc hello.c -o hello # 链接为可执行文件
+objdump -h hello # 查看可执行文件的节段
+```
+
+---
+
+## 四、可执行文件结构
+
+可执行文件在内存中从低地址到高地址的典型布局如下:
+
+```
+高地址 ┌──────────────────┐
+ │ 命令行参数 │
+ │ 和环境变量 │
+ ├──────────────────┤
+ │ 栈(Stack) │ ← 局部变量、函数调用帧,向下增长 ↓
+ │ ↓ │
+ │ │
+ │ ↑ │
+ │ 堆(Heap) │ ← malloc/new动态分配,向上增长 ↑
+ ├──────────────────┤
+ │ .bss 段 │ ← 未初始化的全局/静态变量(不占文件空间)
+ ├──────────────────┤
+ │ .data 段 │ ← 已初始化的全局变量和静态变量
+ ├──────────────────┤
+ │ .rodata 段 │ ← 只读数据(如字符串常量)
+ ├──────────────────┤
+ │ .text 段 │ ← 可执行代码(机器指令)
+低地址 └──────────────────┘
+```
+
+| 段名 | 内容 | 是否可写 | 说明 |
+|------|------|---------|------|
+| `.text` | 机器指令代码 | 只读/只执行 | 程序的可执行代码 |
+| `.rodata` | 只读数据 | 只读 | 字符串常量、`const`变量 |
+| `.data` | 已初始化全局变量 | 可读写 | 有初始值的全局和静态变量 |
+| `.bss` | 未初始化全局变量 | 可读写 | 不占文件空间,装入时清零 |
+| 堆(Heap) | 动态分配内存 | 可读写 | `malloc`/`new` 分配 |
+| 栈(Stack) | 函数调用帧 | 可读写 | 局部变量、返回地址等 |
+
+---
+
+## 五、逻辑地址与物理地址
+
+### 核心概念
+
+| 术语 | 别名 | 说明 |
+|------|------|------|
+| **逻辑地址** | 虚拟地址(VA)、相对地址 | 程序中使用的地址,从0开始编址 |
+| **物理地址** | PA、绝对地址 | 内存硬件中的实际地址 |
+
+> **关键**: 程序中的地址(逻辑地址)**不等于**内存中的实际地址(物理地址)。地址转换由硬件([[01_系统运行机制#四、存储器管理硬件——MMU|MMU]])和操作系统配合完成。
+
+### 逻辑地址空间与物理地址空间
+
+```mermaid
+flowchart TB
+ subgraph VA["逻辑地址空间 (虚拟)"]
+ direction TB
+ V0["地址 0"]
+ V1["..."]
+ V2["地址 2^v - 1"]
+ end
+
+ subgraph PA["物理地址空间 (实际)"]
+ direction TB
+ P0["地址 0"]
+ P1["..."]
+ P2["地址 2^p - 1"]
+ end
+
+ VA -->|"地址映射
(页表/段表)"| PA
+
+ style VA fill:#e3f2fd,stroke:#1976d2
+ style PA fill:#fff8e1,stroke:#f9a825
+```
+
+- **虚拟地址空间**: 大小为 $2^v$ 字节,其中 $v$ 是虚拟地址的位数
+- **物理地址空间**: 大小为 $2^p$ 字节,其中 $p$ 是物理地址的位数
+- 通常 $v \geq p$(虚拟地址空间可以大于物理内存),这就是[[16_虚拟存储器|虚拟存储器]]的基础
+
+### 引入逻辑地址的好处
+
+1. **进程隔离**: 每个进程拥有独立的虚拟地址空间,互不干扰
+2. **提高内存利用率**: 可以使用[[16_虚拟存储器|虚拟存储器]]技术
+3. **内存保护**: 通过地址转换实现访问权限控制
+
+---
+
+## 六、内核空间与用户空间
+
+操作系统将每个进程的虚拟地址空间划分为**用户空间**和**内核空间**两部分:
+
+```mermaid
+block-beta
+ columns 1
+ block:linux["Linux 进程地址空间 (4GB)"]
+ columns 1
+ block:kernel_linux["内核空间 (高 1GB)"]
+ k1["内核代码、数据、内核栈等"]
+ end
+ block:user_linux["用户空间 (低 3GB)"]
+ u1["栈 ↓"]
+ u2["..."]
+ u3["堆 ↑"]
+ u4[".bss / .data / .rodata / .text"]
+ end
+ end
+ block:win["Windows 进程地址空间 (4GB)"]
+ columns 1
+ block:kernel_win["内核空间 (高 2GB)"]
+ k2["内核代码、驱动等"]
+ end
+ block:user_win["用户空间 (低 2GB)"]
+ u5["用户程序空间"]
+ end
+ end
+```
+
+| 操作系统 | 用户空间 | 内核空间 | 说明 |
+|---------|---------|---------|------|
+| **Linux (32位)** | 0 ~ 3GB (低3GB) | 3GB ~ 4GB (高1GB) | 通过`PAGE_OFFSET`划分 |
+| **Windows (32位)** | 0 ~ 2GB (低2GB) | 2GB ~ 4GB (高2GB) | 可通过`/3GB`启动参数调整为3:1 |
+
+**重要规则**: 用户态程序**不能**直接访问内核空间的地址,否则触发保护异常。内核态代码可以访问整个地址空间。
+
+---
+
+## 七、MMU 与内存保护
+
+**MMU (Memory Management Unit)** 是CPU中负责地址转换和内存保护的硬件单元。
+
+### 地址转换流程
+
+```mermaid
+flowchart LR
+ CPU["CPU 发出
虚拟地址(VA)"] --> MMU["MMU
地址转换"]
+ MMU --> PA["物理地址(PA)"]
+ PA --> MEM["访问内存"]
+
+ style MMU fill:#ffcdd2,stroke:#c62828
+```
+
+### 页表保护机制
+
+MMU通过页表中的**U/S位**和CPU的**模式位**配合实现内存保护:
+
+| 页表U/S位 | 含义 |
+|-----------|------|
+| **U=0 (Supervisor)** | 内核态页面,只有内核可以访问 |
+| **U=1 (User)** | 用户态页面,用户态和内核态均可访问 |
+
+| CPU模式 | 可访问的页面 |
+|---------|-------------|
+| **用户模式 (User mode)** | 只能访问 U=1 的页面 |
+| **内核模式 (Kernel mode)** | 可以访问 U=0 和 U=1 的页面 |
+
+当用户态程序试图访问 U=0 的内核页面时,MMU会产生**保护异常(段错误/Segmentation Fault)**,终止该进程。
+
+---
+
+## 八、地址转换的主要方式
+
+操作系统实现地址转换有多种方式,各有特点:
+
+| 方式 | 原理 | 优点 | 缺点 |
+|------|------|------|------|
+| **重定位寄存器(基址寄存器)** | PA = VA + 基址值 | 简单 | 程序必须连续存放 |
+| **静态重定位** | 装入时一次性修改所有地址 | 无需硬件支持 | 装入后不能移动 |
+| **动态重定位** | 执行时通过MMU实时转换 | 灵活,支持移动 | 需要硬件支持 |
+| **[[14_分页存储管理\|分页]]** | 按页划分,通过页表映射 | 消除外部碎片 | 有内部碎片、页表开销 |
+| **[[15_段式存储管理\|分段]]** | 按逻辑段划分,通过段表映射 | 符合程序逻辑 | 外部碎片问题 |
+
+---
+
+## 九、小结
+
+```mermaid
+mindmap
+ root((存储管理基础))
+ 存储层次
+ 寄存器
+ Cache
+ 主存
+ Flash
+ 磁盘
+ 管理功能
+ 分配回收
+ 地址转换
+ 内存共享
+ 内存保护
+ 内存扩充
+ 程序装入
+ 编译链接
+ 可执行文件结构
+ 逻辑地址vs物理地址
+ 地址空间
+ 虚拟地址空间
+ 物理地址空间
+ 内核空间/用户空间
+ MMU保护
+ 页表U/S位
+ CPU模式位
+```
+
+---
+
+## 关联笔记
+
+- [[01_系统运行机制]] — CPU工作模式(用户态/内核态)与MMU硬件基础
+- [[14_分页存储管理]] — 分页式地址转换的详细实现
+- [[15_段式存储管理]] — 分段式地址转换
+- [[16_虚拟存储器]] — 虚拟存储器的实现原理
+
+---
+
+**上一讲**: [[01_系统运行机制]]
+**下一讲**: [[14_分页存储管理]]
diff --git a/操作系统/14_分页存储管理/14_分页存储管理.md b/操作系统/14_分页存储管理/14_分页存储管理.md
new file mode 100644
index 0000000..35cbd4e
--- /dev/null
+++ b/操作系统/14_分页存储管理/14_分页存储管理.md
@@ -0,0 +1,597 @@
+# 14. 分页存储管理
+
+> **课程**: 操作系统 - 存储器管理
+> **核心内容**: 碎片问题、分页思想、地址结构、页表、地址变换、TLB快表、多级页表、倒转页表
+
+---
+
+## 前置知识
+
+- [[13_存储管理基础]] — 存储器层次结构、逻辑地址与物理地址、MMU
+- [[01_系统运行机制]] — CPU工作模式与硬件基础
+
+---
+
+## 一、碎片问题
+
+在连续分配方式中,内存中会出现无法被利用的空闲区域,称为**碎片**。
+
+| 碎片类型 | 出现位置 | 产生原因 | 对应分配方式 |
+|---------|---------|---------|------------|
+| **内碎片** (Internal Fragmentation) | 分配区域**内部** | 固定分区大小 > 进程实际需要 | 固定分区分配 |
+| **外碎片** (External Fragmentation) | 分配区域**外部** | 空闲分区太小,无法满足任何请求 | 动态分区分配 |
+
+```mermaid
+flowchart LR
+ subgraph 内碎片示意
+ direction TB
+ A1["┌──────────────┐"]
+ A2["│ 进程实际数据 │"]
+ A3["│──────────────│"]
+ A4["│ 未使用空间 │ ← 内碎片"]
+ A5["└──────────────┘"]
+ end
+
+ subgraph 外碎片示意
+ direction TB
+ B1["┌────┐ ┌────┐ ┌────┐ ┌────┐"]
+ B2["│进程A│ │空闲│ │进程B│ │空闲│"]
+ B3["└────┘ └────┘ └────┘ └────┘"]
+ B4[" ↑外碎片 ↑外碎片"]
+ end
+```
+
+> **根本原因**: 无论是固定分区还是动态分区,都要求进程在内存中**连续存放**。要彻底解决碎片问题,必须放弃"连续"要求——这就是**分页**思想的核心出发点。
+
+---
+
+## 二、分页的基本思想
+
+分页存储管理将**虚拟地址空间**和**物理内存**都划分为大小相等的小块:
+
+- 虚拟地址空间的每一块称为**虚拟页 (Virtual Page, VP)**
+- 物理内存的每一块称为**物理页框 (Page Frame, PF)**
+
+常见的页面大小为 $2^k$ 字节:512B、1KB、2KB、**4KB**(最常用)。
+
+```mermaid
+flowchart TB
+ subgraph VA["虚拟地址空间 (进程视角)"]
+ direction TB
+ VP0["虚拟页 VP0"]
+ VP1["虚拟页 VP1"]
+ VP2["虚拟页 VP2"]
+ VP3["虚拟页 VP3"]
+ VP4["..."]
+ end
+
+ subgraph PM["物理内存"]
+ direction TB
+ PF0["物理页框 PF0"]
+ PF1["物理页框 PF1"]
+ PF2["物理页框 PF2"]
+ PF3["物理页框 PF3"]
+ PF4["..."]
+ end
+
+ VP0 -->|"页表映射"| PF2
+ VP1 -->|"页表映射"| PF0
+ VP2 -->|"不在内存"| DISK["外存(磁盘)"]
+ VP3 -->|"页表映射"| PF3
+
+ style VA fill:#e3f2fd,stroke:#1976d2
+ style PM fill:#fff8e1,stroke:#f9a825
+ style DISK fill:#fce4ec,stroke:#c62828
+```
+
+**关键特性**:
+- 虚拟页在物理内存中**不需要连续**存放
+- 每个虚拟页可以映射到任意一个空闲物理页框
+- 消除了外碎片(但每个页内可能有少量内碎片)
+
+---
+
+## 三、地址结构
+
+在分页系统中,虚拟地址被划分为两部分:
+
+```
+虚拟地址 VA (v+k 位)
+┌─────────────────┬─────────────┐
+│ VPN (v位) │ VPO (k位) │
+│ 虚拟页号 │ 页内偏移 │
+└─────────────────┴─────────────┘
+```
+
+**计算公式**:
+- 页大小 = $2^k$ 字节
+- 虚拟页号 VPN = $\lfloor VA / 2^k \rfloor$(即高位部分)
+- 页内偏移 VPO = $VA \mod 2^k$(即低 $k$ 位)
+
+物理地址同理:
+
+```
+物理地址 PA (p+k 位)
+┌─────────────────┬─────────────┐
+│ PPN (p位) │ PPO (k位) │
+│ 物理页号 │ 页内偏移 │
+└─────────────────┴─────────────┘
+```
+
+- 物理页号 PPN 由页表查得
+- **PPO = VPO**(页内偏移不变,直接复制)
+
+### 地址计算示例
+
+> **例题**: 某系统虚拟地址 16 位,页面大小 4KB ($2^{12}$),求虚拟地址 VA = 0x3A6F 对应的 VPN 和 VPO。
+
+- 页大小 $2^k = 2^{12}$,所以 $k = 12$,$v = 16 - 12 = 4$
+- VPN = $0x3A6F / 0x1000 = 0x3$(高4位:0011)
+- VPO = $0x3A6F \mod 0x1000 = 0xA6F$(低12位)
+
+```
+VA = 0x3A6F = 0011 1010 0110 1111
+ ──── ────────────────
+ VPN VPO
+ (0x3) (0xA6F)
+```
+
+---
+
+## 四、页表
+
+页表是实现虚拟页到物理页框映射的核心数据结构。
+
+### 页表结构
+
+每个进程拥有**独立的页表**。页表以 VPN 为索引,每个页表项 (PTE) 包含:
+
+```
+页表 (以VPN为索引)
+┌──────┬───────┬──────────────────────────────┐
+│ VPN │ PPN │ 控制位 │
+├──────┼───────┼──────────────────────────────┤
+│ 0 │ 5 │ V=1 D=0 R=1 W=1 U/S=1 │
+│ 1 │ 2 │ V=1 D=1 R=1 W=1 U/S=1 │
+│ 2 │ --- │ V=0 (不在内存) │
+│ 3 │ 8 │ V=1 D=0 R=1 W=0 U/S=1 │
+│ ... │ ... │ ... │
+└──────┴───────┴──────────────────────────────┘
+```
+
+### 页表项 (PTE) 各字段
+
+| 字段 | 含义 | 说明 |
+|------|------|------|
+| **有效位 (Valid/Present)** | 页面是否在内存中 | V=1:在内存;V=0:不在内存(缺页) |
+| **修改位 (Dirty)** | 页面是否被写过 | D=1:被修改过,换出时需写回外存 |
+| **引用位 (Reference)** | 页面是否被访问过 | 用于页面置换算法(如Clock、LRU近似) |
+| **读/写权限 (R/W)** | 读写保护 | 只读页被写入时触发保护异常 |
+| **用户/内核 (U/S)** | 访问权限 | U=0:仅内核可访问;U=1:用户可访问 |
+| **物理页号 (PPN)** | 对应的物理页框号 | 与VPO拼接得到物理地址 |
+
+---
+
+## 五、地址变换过程
+
+### 基本地址变换流程
+
+```mermaid
+flowchart TD
+ A["CPU发出虚拟地址 VA"] --> B["从VA中提取VPN和VPO"]
+ B --> C{"TLB查找VPN"}
+ C -->|"TLB命中"| D["直接取出PPN"]
+ C -->|"TLB未命中"| E["访问页表
(页表基址寄存器PTBR + VPN × PTE大小)"]
+ E --> F{"页表项有效位?"}
+ F -->|"V=1"| G["取出PPN"]
+ F -->|"V=0"| H["**缺页中断**
操作系统处理"]
+ H --> I["从外存调入页面"]
+ I --> J["更新页表"]
+ J --> G
+ D --> K["物理地址 PA = PPN × 2^k + PPO"]
+ G --> K
+ K --> L["访问物理内存"]
+
+ style H fill:#ffcdd2,stroke:#c62828
+ style C fill:#e8f5e9,stroke:#2e7d32
+```
+
+### 详细步骤
+
+1. **提取地址字段**: 从虚拟地址 VA 中分离出 VPN 和 VPO
+2. **查TLB**: 用 VPN 在 TLB 中查找(见第六节)
+3. **查页表**: 若 TLB 未命中,用 **PTBR(页表基址寄存器)+ VPN** 定位页表项
+4. **检查有效位**:
+ - V=1:取出 PPN,与 PPO 拼接得到物理地址
+ - V=0:触发**缺页中断**,OS 从外存调入页面
+5. **拼接物理地址**: PA = PPN | PPO(将 PPN 放高位,PPO 放低位)
+
+### 缺页中断处理流程
+
+```mermaid
+flowchart TD
+ A["发生缺页中断"] --> B["保存CPU现场"]
+ B --> C{"外存中找到该页?"}
+ C -->|"找到"| D{"内存有空闲页框?"}
+ C -->|"未找到"| E["终止进程
(非法访问)"]
+ D -->|"有空闲"| F["从外存读入该页"]
+ D -->|"无空闲"| G["执行页面置换算法
选择牺牲页"]
+ G --> H{"牺牲页Dirty=1?"}
+ H -->|"是"| I["将牺牲页写回外存"]
+ H -->|"否"| J["直接覆盖"]
+ I --> F
+ J --> F
+ F --> K["修改页表
设置V=1, PPN"]
+ K --> L["重新执行被中断的指令"]
+```
+
+### 地址变换计算示例
+
+> **例题**: 某系统页面大小 1KB ($2^{10}$),虚拟地址 14 位。页表如下,求虚拟地址 VA=0x1A8F 对应的物理地址。
+
+| VPN | PPN | Valid |
+|-----|-----|-------|
+| 0 | 3 | 1 |
+| 1 | 7 | 1 |
+| 2 | --- | 0 |
+| 3 | 5 | 1 |
+| 4 | 2 | 1 |
+| 5 | 8 | 1 |
+| 6 | 1 | 1 |
+
+**解题过程**:
+
+1. 页面大小 $2^{10}$,所以 $k=10$,$v=14-10=4$
+2. VA = 0x1A8F = **01 1010 1000 1111** (二进制)
+ - VPN = 高4位 = 0110 = **6**
+ - VPO = 低10位 = 10 1000 1111 = 0x28F
+3. 查页表:VPN=6 对应 PPN=**1**,Valid=1
+4. PA = PPN | PPO = 1 × 2^{10} + 0x28F = 0x400 + 0x28F = **0x68F**
+
+```
+VA = 0x1A8F: 0110 1010001111
+ VPN=6 VPO=0x28F
+
+查页表: VPN=6 → PPN=1
+
+PA = 0001 1010001111 = 0x068F
+ PPN=1 PPO=0x28F
+```
+
+---
+
+## 六、TLB 快表
+
+### TLB 概述
+
+**TLB (Translation Lookaside Buffer)** 是集成在 MMU 中的高速缓存,存储最近使用的页表项。
+
+```mermaid
+flowchart LR
+ CPU["CPU"] -->|"虚拟地址"| TLB{"TLB
(快表)"}
+ TLB -->|"命中
取出PPN"| PA["物理地址"]
+ TLB -->|"未命中"| PT["查页表
(内存)"]
+ PT -->|"PPN"| PA
+ PT -->|"更新TLB"| TLB
+
+ style TLB fill:#e8f5e9,stroke:#2e7d32
+ style PT fill:#fff3e0,stroke:#e65100
+```
+
+### TLB 组织方式
+
+| 方式 | 说明 | 特点 |
+|------|------|------|
+| **全相联** | VPN可以放在TLB的任意位置 | 灵活但查找慢,适合小容量TLB |
+| **组相联** | VPN映射到固定的组(set),组内任意放置 | 折中方案,最常用 |
+| **直接映射** | VPN映射到固定的TLB位置 | 最快但冲突多 |
+
+### TLB 性能分析
+
+设 TLB 查找时间为 $\lambda$,内存访问时间为 $t$,TLB 命中率为 $a$:
+
+**无 TLB 时**:每次地址变换需要访问一次页表(内存)+ 一次数据访问(内存)
+
+$$EAT_{无TLB} = t + t = 2t$$
+
+**有 TLB 时**:
+
+$$EAT = a(\lambda + t) + (1-a)(\lambda + t + t) = \lambda + t + (1-a) \cdot t$$
+
+> **计算示例**: 设 $\lambda = 10$ns, $t = 100$ns, $a = 0.98$(98%命中率)
+>
+> $EAT = 10 + 100 + (1 - 0.98) \times 100 = 10 + 100 + 2 = 112$ ns
+>
+> 相比无TLB的 $2t = 200$ ns,性能提升了约 **44%**。
+
+---
+
+## 七、多级页表
+
+### 为什么需要多级页表
+
+对于 32 位系统,页面大小 4KB:
+- 虚拟地址空间 = $2^{32}$ = 4GB
+- 页数 = $2^{32} / 2^{12} = 2^{20}$ = 1M 个页
+- 每个页表项 4 字节 → 页表大小 = 1M × 4B = **4MB**
+
+4MB 的页表对于每个进程都太大了!而且页表必须连续存放。
+
+### 二级页表结构
+
+**核心思想**: 将页表本身也分页,用"页目录"来索引这些页表页。
+
+```
+32位虚拟地址 (二级页表)
+┌──────────────┬──────────────┬──────────────┐
+│ 页目录索引(10位)│ 页表索引(10位) │ 页内偏移(12位)│
+└──────────────┴──────────────┴──────────────┘
+```
+
+```mermaid
+flowchart LR
+ CR3["CR3
(页目录基址)"] --> PD["页目录
(1024项)"]
+ PD -->|"页目录项"| PT1["页表1
(1024项)"]
+ PD -->|"页目录项"| PT2["页表2
(1024项)"]
+ PD -->|"页目录项"| PT3["页表3
(1024项)"]
+ PT1 -->|"页表项+偏移"| PF1["物理页框"]
+ PT2 -->|"页表项+偏移"| PF2["物理页框"]
+ PT3 -->|"页表项+偏移"| PF3["物理页框"]
+
+ style PD fill:#e3f2fd,stroke:#1976d2
+ style PT1 fill:#fff3e0,stroke:#e65100
+ style PT2 fill:#fff3e0,stroke:#e65100
+ style PT3 fill:#fff3e0,stroke:#e65100
+```
+
+### 多级页表的优势
+
+- 页目录只需常驻内存(4KB),页表页按需创建
+- 未使用的虚拟地址区域**不需要分配页表页**,节省大量内存
+- 64 位系统通常使用 **3~5 级页表**(如 Linux 的 4 级页表:PGD→PUD→PMD→PTE→偏移)
+
+### 二级页表地址变换
+
+1. 用 CR3 找到页目录基址
+2. 用**页目录索引**在页目录中找到页表页的物理地址
+3. 用**页表索引**在页表页中找到 PPN
+4. PPN 与**页内偏移**拼接得到物理地址
+
+> **注意**: 二级页表需要 **3 次内存访问**(页目录 + 页表 + 数据),比一级页表多一次。因此 TLB 的作用更加重要。
+
+---
+
+## 八、倒转页表
+
+### 基本思想
+
+传统页表以**虚拟页号**为索引,每个进程一张。倒转页表以**物理页框号**为索引,整个系统一张。
+
+| 对比项 | 传统页表 | 倒转页表 |
+|--------|---------|---------|
+| 索引 | 虚拟页号 (VPN) | 物理页框号 (PFN) |
+| 表项数 | 虚拟页数(可能很大) | 物理页框数(固定) |
+| 进程数 | 每进程一张 | 全系统一张 |
+| 查找方式 | 直接索引 | 需要搜索(或用Hash) |
+
+```mermaid
+flowchart LR
+ subgraph 传统页表
+ direction TB
+ T1["VPN 0 → PPN x"]
+ T2["VPN 1 → PPN y"]
+ T3["VPN 2 → PPN z"]
+ T4["..."]
+ end
+
+ subgraph 倒转页表
+ direction TB
+ I1["PFN 0 ← VPN a, 进程P1"]
+ I2["PFN 1 ← VPN b, 进程P2"]
+ I3["PFN 2 ← VPN c, 进程P1"]
+ I4["..."]
+ end
+```
+
+**优点**: 表大小与物理内存成正比,节省空间(尤其在 64 位系统)
+
+**缺点**: 查找需要搜索整个表(通常用 Hash 加速)
+
+---
+
+## 九、内存保护
+
+分页系统中通过以下机制实现内存保护:
+
+### 1. 越界保护
+
+- 页表项中的有效位 V=0 表示该页不在内存,访问时触发缺页中断
+- 非法虚拟地址(超出进程地址空间范围)触发保护异常
+
+### 2. 标志位保护
+
+| 标志位 | 保护功能 |
+|--------|---------|
+| R/W | 读/写权限控制。只读页被写入时触发异常 |
+| U/S | 用户/内核权限。用户态访问内核页时触发异常 |
+| NX (No Execute) | 禁止执行位。数据页被当作代码执行时触发异常 |
+
+### 3. 键保护
+
+- 每个物理页框有一个保护键 (Protection Key)
+- 每个进程有一个键寄存器
+- 只有键匹配时才允许访问
+- Intel MPX/MPK 技术支持此机制
+
+---
+
+## 十、空闲页面管理
+
+操作系统需要跟踪物理内存中哪些页框是空闲的:
+
+### 1. 位示图法 (Bitmap)
+
+用一个 bit 表示一个物理页框的状态:0=空闲,1=已分配。
+
+```
+位示图示例 (假设16个物理页框):
+位号: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
+状态: 1 1 0 1 0 0 1 1 0 1 0 0 1 1 0 1
+ ↑ ↑ ↑
+ 空闲 空闲 空闲
+```
+
+- **优点**: 简单,查找连续空闲块方便
+- **缺点**: 位示图本身占用内存(物理内存 4GB、页大小 4KB → 位示图 128KB)
+
+### 2. 链表法
+
+将所有空闲页框用链表串起来:
+
+```
+空闲链表: [PF3] → [PF5] → [PF8] → [PF12] → NULL
+```
+
+- **优点**: 实现简单,不额外占用大量空间
+- **缺点**: 查找连续空闲块需要遍历链表
+
+---
+
+## 十一、Nachos 页式存储管理代码
+
+Nachos 教学操作系统中实现了基本的分页存储管理。以下是关键代码片段:
+
+### 核心数据结构
+
+```cpp
+// 页表项结构 (Machine/translate.h)
+typedef struct {
+ int virtualPage; // 虚拟页号 (VPN)
+ int physicalPage; // 物理页号 (PPN)
+ bool valid; // 有效位
+ bool readOnly; // 只读标志
+ bool use; // 引用位 (用于LRU等算法)
+ bool dirty; // 修改位
+} TranslationEntry;
+```
+
+### 地址转换核心代码
+
+```cpp
+// Machine/translate.cc - translate()
+// 将虚拟地址转换为物理地址
+int Machine::Translate(int virtAddr, int *physAddr, int size, bool writing) {
+ // 1. 计算VPN和偏移
+ int vpn = virtAddr / PageSize;
+ int offset = virtAddr % PageSize;
+
+ // 2. 查找页表
+ TranslationEntry *entry = &pageTable[vpn];
+
+ // 3. 检查有效位
+ if (!entry->valid) {
+ // 缺页处理
+ return PageFaultException;
+ }
+
+ // 4. 检查写权限
+ if (writing && entry->readOnly) {
+ return ReadOnlyException;
+ }
+
+ // 5. 计算物理地址
+ *physAddr = entry->physicalPage * PageSize + offset;
+
+ // 6. 更新引用位和修改位
+ entry->use = true;
+ if (writing) entry->dirty = true;
+
+ return NoException;
+}
+```
+
+### 内存分配示例
+
+```cpp
+// AddrSpace/addrspace.cc - 进程地址空间初始化
+// 为进程分配物理页框
+void AddrSpace::InitRegisters() {
+ // 初始化页表
+ pageTable = new TranslationEntry[numPages];
+ for (int i = 0; i < numPages; i++) {
+ pageTable[i].virtualPage = i;
+ pageTable[i].physicalPage = bitMap->Find(); // 位示图分配
+ pageTable[i].valid = true;
+ pageTable[i].readOnly = false;
+ pageTable[i].use = false;
+ pageTable[i].dirty = false;
+ }
+}
+```
+
+---
+
+## 十二、小结
+
+```mermaid
+mindmap
+ root((分页存储管理))
+ 碎片问题
+ 内碎片(固定分区)
+ 外碎片(动态分区)
+ 根因:连续存放
+ 分页思想
+ 虚拟页VP
+ 物理页框PF
+ 等大划分
+ 地址结构
+ VPN+VPO
+ PPN+PPO
+ 页内偏移不变
+ 页表
+ VPN→PPN映射
+ 有效位/Dirty/Ref
+ 每进程独立
+ 地址变换
+ PTBR+VPN查页表
+ 取PPN拼PPO
+ 缺页中断处理
+ TLB快表
+ 高速缓存页表项
+ 命中率影响EAT
+ 多级页表
+ 页目录+页表
+ 节省内存
+ 倒转页表
+ 按物理块号索引
+ 全系统一张
+```
+
+---
+
+## 思考题
+
+1. **概念理解**: 为什么分页能消除外碎片但不能消除内碎片?内碎片平均浪费多少?
+
+2. **计算题**: 某系统页面大小 4KB,虚拟地址 20 位,物理地址 18 位。
+ - 虚拟地址空间有多少页?
+ - 物理内存最大多少?
+ - 页表至少需要多少项?
+
+3. **TLB计算**: 设 TLB 查找时间 5ns,内存访问时间 80ns,要求有效访问时间不超过 100ns,TLB 命中率至少为多少?
+
+4. **多级页表**: 对于 64 位系统,为什么至少需要 3 级页表?
+
+---
+
+## 关联笔记
+
+- [[13_存储管理基础]] — 存储器层次结构与地址空间基础
+- [[15_段式存储管理]] — 分段式地址转换,段页式结合
+- [[16_虚拟存储器]] — 基于分页的虚拟存储器实现
+- [[11_处理机调度]] — 进程调度与缺页处理的关系
+
+---
+
+**上一讲**: [[13_存储管理基础]]
+**下一讲**: [[15_段式存储管理]]
diff --git a/操作系统/15_段式存储管理/15_段式存储管理.md b/操作系统/15_段式存储管理/15_段式存储管理.md
new file mode 100644
index 0000000..aed451a
--- /dev/null
+++ b/操作系统/15_段式存储管理/15_段式存储管理.md
@@ -0,0 +1,398 @@
+# 15. 段式存储管理
+
+> **课程**: 操作系统 - 存储器管理
+> **核心内容**: 分段引入原因、分段思想、地址结构、段表、地址变换、段页式存储管理
+
+---
+
+## 前置知识
+
+- [[13_存储管理基础]] — 存储器层次结构、逻辑地址与物理地址
+- [[14_分页存储管理]] — 分页思想、页表、地址变换
+
+---
+
+## 一、为什么需要分段
+
+[[14_分页存储管理|分页]]虽然解决了碎片问题,但在以下场景中存在不足:
+
+### 1. 信息共享不方便
+
+分页按固定大小划分,不考虑程序的逻辑结构。如果要共享一段代码(如共享库函数),该代码可能跨越多个页面,其中某些页面还包含不需要共享的数据,导致共享粒度过粗。
+
+```
+分页视角(按固定大小切分,不考虑逻辑含义):
+┌────────┐ ┌────────┐ ┌────────┐
+│ 代码1 │ │ 代码2+ │ │ 数据 │ ← 一个逻辑模块跨了3个页
+│ │ │ 常量 │ │ │ 共享时会把不相关的内容也共享了
+└────────┘ └────────┘ └────────┘
+```
+
+### 2. 动态链接问题
+
+动态链接需要在运行时将目标模块装入内存并链接。分页系统中,模块的装入和链接以页为单位,不够灵活。
+
+### 3. 程序员视角需求
+
+程序员编写程序时,自然地将代码划分为**代码段、数据段、堆栈段**等逻辑模块。分页完全打乱了这种逻辑结构。
+
+> **核心需求**: 地址空间的划分应该按照**逻辑意义**进行,而非固定大小。这就是分段的思想。
+
+---
+
+## 二、分段的基本思想
+
+分段按照程序的**逻辑结构**将地址空间划分为若干个段:
+
+- 每个段有独立的**段名**和**段长**
+- 每个段在内存中**连续存放**
+- 不同段之间**不需要连续**
+
+```mermaid
+flowchart TB
+ subgraph VA["进程虚拟地址空间"]
+ direction TB
+ S0["主程序段 (段0)"]
+ S1["子程序段 (段1)"]
+ S2["数据段 (段2)"]
+ S3["栈段 (段3)"]
+ end
+
+ subgraph PM["物理内存"]
+ direction TB
+ M0["区域A"]
+ M1["区域B"]
+ M2["区域C"]
+ M3["区域D"]
+ M4["空闲"]
+ M5["区域E"]
+ end
+
+ S0 -->|"段表映射"| M5
+ S1 -->|"段表映射"| M0
+ S2 -->|"段表映射"| M2
+ S3 -->|"段表映射"| M3
+
+ style VA fill:#e3f2fd,stroke:#1976d2
+ style PM fill:#fff8e1,stroke:#f9a825
+```
+
+### 分段 vs 分页
+
+| 对比项 | 分页 | 分段 |
+|--------|------|------|
+| **划分依据** | 固定大小(物理需要) | 逻辑意义(程序员视角) |
+| **段/页大小** | 所有页等大 | 各段长度不同 |
+| **地址空间** | 一维(VA直接计算VPN和偏移) | **二维**(段号 + 段内偏移) |
+| **碎片类型** | 内碎片(页内浪费) | 外碎片(段间空闲区) |
+| **共享** | 以页为粒度,较粗 | 以段为粒度,符合逻辑 |
+| **动态增长** | 不方便 | 方便(如堆、栈段可动态扩展) |
+
+---
+
+## 三、地址结构
+
+分段系统的逻辑地址是**二维**的,由两部分组成:
+
+```
+逻辑地址 = (段号 S, 段内地址 d)
+```
+
+```
+逻辑地址表示:
+┌──────────────┬────────────────────┐
+│ 段号 S │ 段内地址 d │
+│ (高位部分) │ (低位部分) │
+└──────────────┴────────────────────┘
+
+注意: 不同于分页的VPN|VPO可以统一计算,
+分段中 d 的取值范围取决于段长,各段不同。
+```
+
+**地址表示示例**:
+
+- 分页地址 `(0x1A8F)` → 可直接算出 VPN=6, VPO=0x28F
+- 分段地址 `(2, 0x100)` → 段号=2,偏移=0x100(需要查段表才知道该段的基址和长度)
+
+---
+
+## 四、段表
+
+### 段表结构
+
+每个进程拥有一张**段表**,段表以**段号**为索引,每项包含:
+
+| 字段 | 含义 |
+|------|------|
+| **段号** | 段的编号(隐含在索引中) |
+| **段长 (Limit)** | 该段的长度(字节数),用于越界检查 |
+| **段基址 (Base)** | 该段在物理内存中的起始地址 |
+
+```
+段表示例:
+┌──────┬────────────┬────────────────┐
+│ 段号 │ 段长(Limit) │ 基址(Base) │
+├──────┼────────────┼────────────────┤
+│ 0 │ 0x2000 │ 0x4000 │ ← 主程序: 在内存0x4000处, 长8KB
+│ 1 │ 0x1000 │ 0x8000 │ ← 子程序: 在内存0x8000处, 长4KB
+│ 2 │ 0x3000 │ 0xB000 │ ← 数据段: 在内存0xB000处, 长12KB
+│ 3 │ 0x1800 │ 0x2000 │ ← 栈段: 在内存0x2000处, 长6KB
+└──────┴────────────┴────────────────┘
+```
+
+段表基址寄存器 **STBR** 指向段表在内存中的起始地址。
+
+---
+
+## 五、地址变换过程
+
+### 变换流程
+
+```mermaid
+flowchart TD
+ A["逻辑地址 (S, d)"] --> B["用STBR找到段表基址"]
+ B --> C["定位段表项: 段表基址 + S × 段表项大小"]
+ C --> D{"d < Limit ?"}
+ D -->|"是"| E["物理地址 PA = Base + d"]
+ D -->|"否"| F["**越界中断**
(Segmentation Fault)"]
+ E --> G["访问物理内存"]
+
+ style F fill:#ffcdd2,stroke:#c62828
+ style D fill:#fff3e0,stroke:#e65100
+```
+
+### 详细步骤
+
+1. **提取段号和偏移**: 从逻辑地址中分离出段号 S 和段内地址 d
+2. **查段表**: 用 STBR + S 定位到第 S 个段表项
+3. **越界检查**: 比较 d 与 Limit
+ - 若 d >= Limit,触发**越界中断**(Segmentation Fault)
+ - 若 d < Limit,继续
+4. **计算物理地址**: PA = Base + d
+5. **访问内存**: 用物理地址访问实际内存
+
+### 地址变换计算示例
+
+> **例题**: 某分段系统,段表如下。求以下逻辑地址对应的物理地址:
+> - (0, 0x1500)
+> - (1, 0x2000)
+> - (2, 0x0800)
+
+**解题过程**:
+
+| 逻辑地址 | S | d | Limit | 比较 | Base | PA = Base + d | 结果 |
+|---------|---|---|-------|------|------|--------------|------|
+| (0, 0x1500) | 0 | 0x1500 | 0x2000 | 0x1500 < 0x2000 ✓ | 0x4000 | 0x4000 + 0x1500 | **0x5500** |
+| (1, 0x2000) | 1 | 0x2000 | 0x1000 | 0x2000 >= 0x1000 ✗ | — | — | **越界中断** |
+| (2, 0x0800) | 2 | 0x0800 | 0x3000 | 0x0800 < 0x3000 ✓ | 0xB000 | 0xB000 + 0x0800 | **0xB800** |
+
+---
+
+## 六、分段的优缺点
+
+### 优点
+
+| 优点 | 说明 |
+|------|------|
+| **符合程序逻辑** | 按代码、数据、栈等逻辑单元组织,便于理解和管理 |
+| **便于共享** | 以段为单位共享,粒度合理(如共享整个代码段) |
+| **便于动态链接** | 可以按段为单位进行动态链接和装入 |
+| **支持动态增长** | 堆、栈等段可以独立扩展,不影响其他段 |
+| **保护自然** | 每段可设置独立的访问权限(代码段只读、数据段可读写等) |
+
+### 缺点
+
+| 缺点 | 说明 |
+|------|------|
+| **外碎片** | 段长不等,内存分配/回收后产生不连续的空闲区 |
+| **段长限制** | 每段最大长度受地址结构限制 |
+| **内存紧缩开销** | 消除外碎片需要移动段(类似动态分区的紧凑操作) |
+
+---
+
+## 七、段页式存储管理
+
+### 基本思想
+
+**段页式** = 分段 + 分页,兼具两者的优点:
+
+- 先按**逻辑结构分段**(保留分段的优点:共享、保护、逻辑清晰)
+- 再将每段**按固定大小分页**(保留分页的优点:消除外碎片)
+
+```mermaid
+flowchart TB
+ subgraph VA["虚拟地址空间"]
+ direction TB
+ S0["段0 (主程序)"]
+ S1["段1 (子程序)"]
+ S2["段2 (数据)"]
+ end
+
+ subgraph S0P["段0 内部分页"]
+ direction TB
+ P0["页0"]
+ P1["页1"]
+ P2["页2"]
+ end
+
+ subgraph PM["物理内存"]
+ direction TB
+ F0["页框0"]
+ F1["页框1"]
+ F2["页框2"]
+ F3["页框3"]
+ end
+
+ VA --> S0P
+ P0 --> F2
+ P1 --> F0
+ P2 --> F3
+
+ style VA fill:#e3f2fd,stroke:#1976d2
+ style S0P fill:#f3e5f5,stroke:#7b1fa2
+ style PM fill:#fff8e1,stroke:#f9a825
+```
+
+### 段页式地址结构
+
+逻辑地址由**三部分**组成:
+
+```
+段页式逻辑地址:
+┌──────────────┬──────────────┬──────────────┐
+│ 段号 S │ 页号 P │ 页内偏移 d │
+└──────────────┴──────────────┴──────────────┘
+```
+
+### 段页式地址变换
+
+```mermaid
+flowchart TD
+ A["逻辑地址 (S, P, d)"] --> B["1. 用STBR找到段表"]
+ B --> C["2. 用段号S查段表
得到该段的页表基址和段长"]
+ C --> D{"P < 该段页数?"}
+ D -->|"是"| E["3. 用页号P查页表
得到物理页框号 PPN"]
+ D -->|"否"| F["越界中断"]
+ E --> G["4. 物理地址 = PPN × 页大小 + d"]
+ G --> H["访问物理内存"]
+
+ style F fill:#ffcdd2,stroke:#c62828
+```
+
+### 段页式地址变换步骤
+
+1. **查段表**: 用 STBR + S 找到第 S 个段表项,得到该段的**页表基址**
+2. **查页表**: 用页表基址 + P 找到第 P 个页表项,得到 **PPN**
+3. **拼接地址**: PA = PPN × 页大小 + d
+
+> **注意**: 段页式需要 **3 次内存访问**(段表 + 页表 + 数据),比纯分页多一次。TLB 的作用更加关键。
+
+### 段页式地址变换计算示例
+
+> **例题**: 某段页式系统,页面大小 4KB。段表如下,求逻辑地址 (1, 2, 0x100) 的物理地址。
+
+**段表**:
+
+| 段号 | 页表基址 | 段长(页数) |
+|------|---------|-----------|
+| 0 | 0x8000 | 4 |
+| 1 | 0xA000 | 6 |
+| 2 | 0xC000 | 3 |
+
+**段1的页表** (基址 0xA000):
+
+| 页号 | PPN |
+|------|-----|
+| 0 | 5 |
+| 1 | 2 |
+| 2 | 8 |
+| 3 | 1 |
+| 4 | 7 |
+| 5 | 3 |
+
+**解题过程**:
+
+1. S=1, P=2, d=0x100
+2. 查段表:段1的页表基址 = 0xA000,段长 = 6 页
+3. P=2 < 6,合法
+4. 查段1的页表:P=2 对应 PPN=**8**
+5. PA = 8 × 4096 + 0x100 = 0x8000 + 0x100 = **0x8100**
+
+---
+
+## 八、三种存储管理方式对比
+
+| 对比项 | 纯分页 | 纯分段 | 段页式 |
+|--------|--------|--------|--------|
+| **划分依据** | 固定大小 | 逻辑结构 | 先逻辑后固定大小 |
+| **地址维度** | 一维 | 二维 | 三维 |
+| **碎片** | 内碎片 | 外碎片 | 内碎片 |
+| **共享** | 以页为粒度 | 以段为粒度 | 以段为粒度 |
+| **内存访问次数** | 2次(页表+数据) | 2次(段表+数据) | 3次(段表+页表+数据) |
+| **代表系统** | Linux | 早期Multics | Intel x86(32位保护模式) |
+
+---
+
+## 九、小结
+
+```mermaid
+mindmap
+ root((段式存储管理))
+ 分段引入
+ 信息共享不便
+ 动态链接需求
+ 逻辑结构需求
+ 分段思想
+ 按逻辑分段
+ 代码段/数据段/栈段
+ 各段独立地址空间
+ 地址结构
+ 二维: 段号+偏移
+ 不同于分页的一维
+ 段表
+ 段号/段长/基址
+ 越界检查
+ STBR寄存器
+ 地址变换
+ 查段表→越界检查→基址+偏移
+ PA = Base + d
+ 段页式
+ 先分段再分页
+ 三维地址: 段号+页号+偏移
+ 兼具两者优点
+```
+
+---
+
+## 思考题
+
+1. **概念理解**: 分段和分页的根本区别是什么?为什么说分段的地址空间是二维的?
+
+2. **地址变换**: 某分段系统有 4 个段,段表如下。求物理地址或判断是否越界:
+ - (0, 0x800) → ?
+ - (2, 0x5000) → ?
+ - (3, 0x2000) → ?
+
+ | 段号 | 段长 | 基址 |
+ |------|------|------|
+ | 0 | 0x1000 | 0x5000 |
+ | 1 | 0x2000 | 0x8000 |
+ | 2 | 0x4000 | 0xA000 |
+ | 3 | 0x3000 | 0x2000 |
+
+3. **段页式**: 为什么段页式需要 3 次内存访问?TLB 如何缓解这个问题?
+
+4. **对比分析**: 在什么场景下分段优于分页?在什么场景下分页优于分段?
+
+---
+
+## 关联笔记
+
+- [[13_存储管理基础]] — 存储器层次结构与地址空间基础
+- [[14_分页存储管理]] — 分页思想、页表与地址变换
+- [[16_虚拟存储器]] — 基于分段的虚拟存储器实现
+
+---
+
+**上一讲**: [[14_分页存储管理]]
+**下一讲**: [[16_虚拟存储器]]
diff --git a/操作系统/16_虚拟存储器/16_虚拟存储器.md b/操作系统/16_虚拟存储器/16_虚拟存储器.md
new file mode 100644
index 0000000..f920f45
--- /dev/null
+++ b/操作系统/16_虚拟存储器/16_虚拟存储器.md
@@ -0,0 +1,606 @@
+# 16. 虚拟存储器
+
+> **课程**: 操作系统 - 存储器管理
+> **核心内容**: 虚拟存储器思想、局部性原理、请求分页、页面置换算法、内存分配策略、抖动与工作集
+
+---
+
+## 前置知识
+
+- [[14_分页存储管理]] — 分页思想、页表、地址变换、缺页中断
+- [[15_段式存储管理]] — 分段思想、段页式
+- [[11_处理机调度]] — 进程调度基本概念
+
+---
+
+## 一、虚拟存储器的基本思想
+
+### 问题的提出
+
+在传统的存储管理中,作业必须**全部装入内存**才能运行。这带来两个问题:
+1. 作业大小超过物理内存时,无法运行
+2. 内存中同时驻留多个大程序时,内存不够用
+
+### 虚拟存储器思想
+
+**核心**: 程序员在比实际物理内存**大得多**的虚拟地址空间中编写程序,运行时只需将**部分页面**调入内存,其余留在外存。当访问到不在内存的页面时,再从外存调入。
+
+```mermaid
+flowchart LR
+ subgraph VA["虚拟地址空间 (很大)"]
+ direction TB
+ V0["页面0 ✓ 在内存"]
+ V1["页面1 ✓ 在内存"]
+ V2["页面2 ✗ 在外存"]
+ V3["页面3 ✓ 在内存"]
+ V4["页面4 ✗ 在外存"]
+ V5["..."]
+ end
+
+ subgraph MEM["物理内存 (较小)"]
+ direction TB
+ M0["页框0"]
+ M1["页框1"]
+ M2["页框2"]
+ M3["页框3"]
+ end
+
+ subgraph DISK["外存 (磁盘)"]
+ direction TB
+ D0["页面2"]
+ D1["页面4"]
+ D2["..."]
+ end
+
+ V0 --> M0
+ V1 --> M1
+ V3 --> M3
+ V2 -.->|"缺页时调入"| DISK
+ V4 -.->|"缺页时调入"| DISK
+
+ style VA fill:#e3f2fd,stroke:#1976d2
+ style MEM fill:#fff8e1,stroke:#f9a825
+ style DISK fill:#fce4ec,stroke:#c62828
+```
+
+### 实现基础
+
+虚拟存储器的实现依赖两个关键技术:
+1. **[[14_分页存储管理|分页]]或[[15_段式存储管理|分段]]**: 将程序划分为小块,可以部分装入
+2. **[[14_分页存储管理#五、地址变换过程|缺页中断]]**: 访问不在内存的页面时,由OS负责调入
+
+---
+
+## 二、局部性原理
+
+虚拟存储器之所以可行,是因为程序运行时具有**局部性**——程序不会均匀地访问所有地址空间。
+
+### 时间局部性 (Temporal Locality)
+
+> **含义**: 如果一条指令/数据被访问,那么在不久的将来它很可能**再次被访问**。
+
+**典型场景**:
+- 循环中的指令反复执行
+- 频繁访问的变量
+- 栈顶数据的反复操作
+
+### 空间局部性 (Spatial Locality)
+
+> **含义**: 如果一个存储单元被访问,那么它**附近的单元**也很可能很快被访问。
+
+**典型场景**:
+- 顺序执行的指令
+- 数组的顺序遍历
+- 结构体成员的连续访问
+
+### 局部性的实际影响:行优先 vs 列优先
+
+以二维数组遍历为例,行优先和列优先的性能差异可达 **21.5 倍**:
+
+```c
+// 二维数组: int a[1024][1024];
+
+// 行优先遍历 (空间局部性好)
+for (int i = 0; i < 1024; i++)
+ for (int j = 0; j < 1024; j++)
+ sum += a[i][j]; // 访问顺序: a[0][0], a[0][1], a[0][2], ...
+ // 连续内存访问,充分利用缓存行
+
+// 列优先遍历 (空间局部性差)
+for (int j = 0; j < 1024; j++)
+ for (int i = 0; i < 1024; i++)
+ sum += a[i][j]; // 访问顺序: a[0][0], a[1][0], a[2][0], ...
+ // 每次跳过一整行,频繁缺页/缓存失效
+```
+
+```mermaid
+flowchart LR
+ subgraph RowMajor["行优先遍历"]
+ direction LR
+ R1["a[0][0]"] --> R2["a[0][1]"] --> R3["a[0][2]"] --> R4["..."]
+ style R1 fill:#c8e6c9
+ style R2 fill:#c8e6c9
+ style R3 fill:#c8e6c9
+ end
+
+ subgraph ColMajor["列优先遍历"]
+ direction LR
+ C1["a[0][0]"] --> C2["a[1][0]"] --> C3["a[2][0]"] --> C4["..."]
+ style C1 fill:#ffcdd2
+ style C2 fill:#ffcdd2
+ style C3 fill:#ffcdd2
+ end
+
+ RowMajor ---|"性能差异约 21.5 倍"| ColMajor
+```
+
+---
+
+## 三、请求分页
+
+### 基本思想
+
+**请求分页 (Demand Paging)** 是最常用的虚拟存储器实现方式:
+- 页面只在**被访问时**才调入内存(而非预装入)
+- 内存不足时,将某些页面**换出**到外存,腾出空间
+
+### 页表项扩展
+
+在[[14_分页存储管理#四、页表|基本分页]]的基础上,请求分页的页表项增加了**状态位**:
+
+| 字段 | 含义 | 说明 |
+|------|------|------|
+| **状态位 (Present/Valid)** | 页面是否在内存 | 1=在内存,0=不在内存 |
+| **访问位 (Reference/Access)** | 页面是否被访问过 | 用于置换算法判断 |
+| **修改位 (Dirty)** | 页面是否被写过 | Dirty=1 时换出需写回外存 |
+| **外存地址** | 页面在外存中的位置 | 用于缺页时调入 |
+
+### 缺页中断处理
+
+```mermaid
+flowchart TD
+ A["CPU访问虚拟地址"] --> B{"页面在内存中?
(检查状态位)"}
+ B -->|"在内存"| C["正常地址变换,访问数据"]
+ B -->|"不在内存"| D["触发**缺页中断**"]
+ D --> E["保存CPU现场"]
+ E --> F{"外存中存在该页?"}
+ F -->|"不存在"| G["终止进程
Segmentation Fault"]
+ F -->|"存在"| H{"内存有空闲页框?"}
+ H -->|"有空闲"| I["从外存读入该页"]
+ H -->|"无空闲"| J["执行**页面置换算法**
选择牺牲页"]
+ J --> K{"牺牲页Dirty=1?"}
+ K -->|"是"| L["将牺牲页写回外存"]
+ K -->|"否"| M["直接覆盖"]
+ L --> I
+ M --> I
+ I --> N["更新页表:
状态位=1, PPN"]
+ N --> O["刷新TLB"]
+ O --> P["重新执行被中断的指令"]
+
+ style D fill:#ffcdd2,stroke:#c62828
+ style J fill:#fff3e0,stroke:#e65100
+ style G fill:#ffcdd2,stroke:#c62828
+```
+
+---
+
+## 四、页面置换算法
+
+当内存中没有空闲页框时,需要选择一个页面换出到外存,为新页面腾出空间。不同的选择策略就是**页面置换算法**。
+
+> **示例参数**: 物理内存有 **3 个页框**,页面引用串为:
+> **7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2, 1, 2, 0, 1, 7, 0, 1**
+
+### 1. OPT (Optimal) 最佳置换算法
+
+**策略**: 换出**将来最长时间不会被使用**的页面。
+
+> OPT 是理论算法,因为无法预知未来的页面访问序列。它用于衡量其他算法的上限。
+
+**执行过程**:
+
+| 访问序列 | 页框0 | 页框1 | 页框2 | 缺页? | 说明 |
+|---------|-------|-------|-------|-------|------|
+| **7** | 7 | - | - | 缺页 | 空闲页框,直接装入 |
+| **0** | 7 | 0 | - | 缺页 | 空闲页框,直接装入 |
+| **1** | 7 | 0 | 1 | 缺页 | 空闲页框,直接装入 |
+| **2** | 7 | 0 | **2** | 缺页 | 换出1(最久后才用) |
+| **0** | 7 | 0 | 2 | 命中 | 0已在内存 |
+| **3** | **3** | 0 | 2 | 缺页 | 换出7(最久后才用) |
+| **0** | 3 | 0 | 2 | 命中 | |
+| **4** | 3 | 0 | **4** | 缺页 | 换出2(最久后才用) |
+| **2** | 3 | **2** | 4 | 缺页 | 换出0(最久后才用) |
+| **3** | 3 | 2 | 4 | 命中 | |
+| **0** | 3 | 2 | **0** | 缺页 | 换出4(最久后才用) |
+| **3** | 3 | 2 | 0 | 命中 | |
+| **2** | 3 | 2 | 0 | 命中 | |
+| **1** | **1** | 2 | 0 | 缺页 | 换出3(最久后才用) |
+| **2** | 1 | 2 | 0 | 命中 | |
+| **0** | 1 | 2 | 0 | 命中 | |
+| **1** | 1 | 2 | 0 | 命中 | |
+| **7** | 1 | **7** | 0 | 缺页 | 换出2(最久后才用) |
+| **0** | 1 | 7 | 0 | 命中 | |
+| **1** | 1 | 7 | 0 | 命中 | |
+
+**缺页次数: 9**,缺页率 = 9/20 = **45%**
+
+### 2. FIFO (First-In First-Out) 先进先出
+
+**策略**: 换出**最早进入内存**的页面。
+
+**执行过程**:
+
+| 访问序列 | 页框0 | 页框1 | 页框2 | 缺页? | 淘汰队列 |
+|---------|-------|-------|-------|-------|---------|
+| **7** | 7 | - | - | 缺页 | [7] |
+| **0** | 7 | 0 | - | 缺页 | [7,0] |
+| **1** | 7 | 0 | 1 | 缺页 | [7,0,1] |
+| **2** | **2** | 0 | 1 | 缺页 | [0,1,2] 淘汰7 |
+| **0** | 2 | 0 | 1 | 命中 | [0,1,2] |
+| **3** | 2 | **3** | 1 | 缺页 | [1,2,3] 淘汰0 |
+| **0** | 2 | 3 | **0** | 缺页 | [2,3,0] 淘汰1 |
+| **4** | **4** | 3 | 0 | 缺页 | [3,0,4] 淘汰2 |
+| **2** | 4 | **2** | 0 | 缺页 | [0,4,2] 淘汰3 |
+| **3** | 4 | 2 | **3** | 缺页 | [4,2,3] 淘汰0 |
+| **0** | **0** | 2 | 3 | 缺页 | [2,3,0] 淘汰4 |
+| **3** | 0 | 2 | 3 | 命中 | [2,3,0] |
+| **2** | 0 | 2 | 3 | 命中 | [2,3,0] |
+| **1** | 0 | **1** | 3 | 缺页 | [3,0,1] 淘汰2 |
+| **2** | 0 | 1 | **2** | 缺页 | [0,1,2] 淘汰3 |
+| **0** | 0 | 1 | 2 | 命中 | [0,1,2] |
+| **1** | 0 | 1 | 2 | 命中 | [0,1,2] |
+| **7** | **7** | 1 | 2 | 缺页 | [1,2,7] 淘汰0 |
+| **0** | 7 | **0** | 2 | 缺页 | [2,7,0] 淘汰1 |
+| **1** | 7 | 0 | **1** | 缺页 | [7,0,1] 淘汰2 |
+
+**缺页次数: 15**,缺页率 = 15/20 = **75%**
+
+> **Belady 异常**: FIFO 算法在某些情况下,增加物理页框数反而会导致缺页率**上升**。例如本例中 4 个页框时缺页次数可能比 3 个页框时更多。这是 FIFO 算法的独特缺陷。
+
+### 3. LRU (Least Recently Used) 最近最久未使用
+
+**策略**: 换出**最近最长时间没有被访问**的页面。
+
+**实现方式**:
+- **栈法**: 用一个栈保存页面号,访问某页时将其移到栈顶。栈底就是最久未使用的页面
+- **计数器法**: 每个页面记录最后一次访问的时间,淘汰时间值最小的页面
+
+**执行过程**:
+
+| 访问序列 | 页框0 | 页框1 | 页框2 | 缺页? | 说明 |
+|---------|-------|-------|-------|-------|------|
+| **7** | 7 | - | - | 缺页 | |
+| **0** | 7 | 0 | - | 缺页 | |
+| **1** | 7 | 0 | 1 | 缺页 | |
+| **2** | 2 | 0 | 1 | 缺页 | 淘汰7(最久未用) |
+| **0** | 2 | 0 | 1 | 命中 | 0被访问,更新时间戳 |
+| **3** | 2 | 0 | 3 | 缺页 | 淘汰1(最久未用) |
+| **0** | 2 | 0 | 3 | 命中 | |
+| **4** | 4 | 0 | 3 | 缺页 | 淘汰2(最久未用) |
+| **2** | 4 | 0 | 2 | 缺页 | 淘汰3(最久未用) |
+| **3** | 4 | 3 | 2 | 缺页 | 淘汰0(最久未用) |
+| **0** | 0 | 3 | 2 | 缺页 | 淘汰4(最久未用) |
+| **3** | 0 | 3 | 2 | 命中 | |
+| **2** | 0 | 3 | 2 | 命中 | |
+| **1** | 0 | 3 | 1 | 缺页 | 淘汰2(最久未用) |
+| **2** | 0 | 2 | 1 | 缺页 | 淘汰3(最久未用) |
+| **0** | 0 | 2 | 1 | 命中 | |
+| **1** | 0 | 2 | 1 | 命中 | |
+| **7** | 0 | 2 | 7 | 缺页 | 淘汰1(最久未用) |
+| **0** | 0 | 2 | 7 | 命中 | |
+| **1** | 1 | 2 | 7 | 缺页 | 淘汰0(最久未用) |
+
+**缺页次数: 12**,缺页率 = 12/20 = **60%**
+
+### 4. Clock (时钟 / 近似LRU)
+
+**策略**: 将所有页面组成一个**循环链表**,每页有一个**访问位 R**。淘汰指针从当前位置开始扫描:
+- R=1:将 R 置 0,指针前移(给第二次机会)
+- R=0:淘汰该页
+
+```mermaid
+flowchart LR
+ subgraph Clock["时钟置换算法 (循环链表)"]
+ direction LR
+ P0["页0
R=1"]
+ P1["页1
R=0"]
+ P2["页2
R=1"]
+ P3["页3
R=0"]
+ end
+
+ PTR["淘汰指针"] --> P1
+
+ P0 --> P1
+ P1 --> P2
+ P2 --> P3
+ P3 --> P0
+
+ style P1 fill:#ffcdd2,stroke:#c62828
+ style PTR fill:#fff3e0,stroke:#e65100
+```
+
+**执行示例**(简化):
+- 指针指向页1 (R=0) → 淘汰页1,装入新页,指针前移
+- 指针指向页2 (R=1) → R置0,指针前移
+- 指针指向页3 (R=0) → 淘汰页3
+
+**改进型Clock**: 同时考虑 R 和 D (修改位),优先淘汰 R=0 且 D=0 的页面(不需要写回外存)。
+
+| 优先级 | R | D | 淘汰代价 |
+|--------|---|---|---------|
+| 最高 | 0 | 0 | 未访问未修改,直接覆盖 |
+| | 0 | 1 | 未访问但已修改,需写回 |
+| | 1 | 0 | 已访问未修改,再给机会 |
+| 最低 | 1 | 1 | 已访问已修改,最不想淘汰 |
+
+### 5. LFU (Least Frequently Used) 最少使用
+
+**策略**: 换出**访问次数最少**的页面。
+
+- 用计数器记录每个页面的访问次数
+- 淘汰计数器值最小的页面
+- **缺点**: 某页面过去被频繁访问但现在不再使用,仍难以被淘汰
+- **实现开销**: 需要为每个页面维护计数器
+
+### 算法对比
+
+| 算法 | 策略 | 优点 | 缺点 | 是否有Belady异常 |
+|------|------|------|------|----------------|
+| **OPT** | 淘汰将来最久不用的 | 缺页率最低(理论最优) | 无法实现 | 无 |
+| **FIFO** | 淘汰最早进入的 | 实现简单 | 缺页率高,有Belady异常 | **有** |
+| **LRU** | 淘汰最近最久未用的 | 接近OPT,效果好 | 实现开销大(需时间戳或栈) | 无 |
+| **Clock** | 给访问位R=0的淘汰 | LRU的近似,开销小 | 效果略逊于LRU | 无 |
+| **LFU** | 淘汰访问次数最少的 | 考虑历史频率 | 对访问模式变化响应慢 | 无 |
+
+### 算法效果对比(本例)
+
+```
+页面引用串: 7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1 (3个物理块)
+
+┌──────────┬──────────┬──────────┐
+│ 算法 │ 缺页次数 │ 缺页率 │
+├──────────┼──────────┼──────────┤
+│ OPT │ 9 │ 45% │ ← 理论最优
+│ LRU │ 12 │ 60% │
+│ FIFO │ 15 │ 75% │ ← 效果最差
+└──────────┴──────────┴──────────┘
+```
+
+---
+
+## 五、内存分配策略
+
+### 物理页框分配方式
+
+| 方式 | 说明 |
+|------|------|
+| **平均分配** | 将可用页框平均分配给所有进程 |
+| **按比例分配** | 按进程大小占总需求的比例分配 |
+| **考虑优先权** | 高优先级进程分配更多页框 |
+
+**按比例分配公式**:
+
+$$a_i = \frac{s_i}{\sum s_j} \times m$$
+
+其中 $s_i$ 是进程 $i$ 的页面数,$m$ 是可用页框总数。
+
+### 置换策略
+
+| 策略 | 说明 | 特点 |
+|------|------|------|
+| **固定分配局部置换** | 每个进程固定页框数,只能在自己的页面中置换 | 公平,但可能浪费 |
+| **可变分配全局置换** | 从全局空闲页框池中分配,可换出任何进程的页面 | 灵活,但可能影响其他进程 |
+| **可变分配局部置换** | 进程页框数可调,但只能在自己的页面中置换 | 折中方案 |
+
+---
+
+## 六、抖动与工作集
+
+### 抖动 (Thrashing)
+
+当进程分配到的页框数**不足以**容纳其工作集时,会频繁发生缺页,CPU 大量时间花在页面置换上,利用率急剧下降——这种现象称为**抖动**。
+
+```mermaid
+flowchart TD
+ A["进程增多"] --> B["每个进程分到的页框减少"]
+ B --> C["缺页率上升"]
+ C --> D["CPU利用率下降"]
+ D --> E["调度器增加进程数
(以为CPU空闲)"]
+ E --> B
+
+ style C fill:#ffcdd2,stroke:#c62828
+ style D fill:#ffcdd2,stroke:#c62828
+```
+
+CPU 利用率随进程数的变化:
+
+```
+CPU利用率
+ ↑
+100%│ ╱‾‾‾‾╲
+ │ ╱ ╲
+ │ ╱ ╲ ← 抖动区域
+ │ ╱ ╲
+ │ ╱ ╲
+ │╱ ╲
+ └──────────────────────────→ 进程数
+ ↑
+ 最佳点
+```
+
+### 工作集 (Working Set)
+
+**工作集**的定义:在最近 $\Delta$ 时间内,进程实际访问的页面集合。
+
+$$W(t, \Delta) = \text{在时刻 } t-\Delta \text{ 到 } t \text{ 之间访问的页面集合}$$
+
+**示例**:
+
+页面访问序列: ... 2, 6, 1, 5, 7, 7, 7, 2, ...
+
+设 $\Delta = 5$(最近 5 次访问),当前访问页面 2:
+- 最近 5 次访问: {5, 7, 7, 7, 2}
+- 工作集 = {2, 5, 7},大小 = 3
+
+**工作集策略**: 为每个进程分配**不少于其工作集大小**的页框数,就可以避免抖动。
+
+### 工作集模型的应用
+
+```mermaid
+flowchart TD
+ A["监控每个进程的工作集"] --> B{"总工作集 ≤ 物理页框数?"}
+ B -->|"是"| C["正常运行,不会抖动"]
+ B -->|"否"| D["可能发生抖动"]
+ D --> E["挂起某些进程
释放页框"]
+ E --> B
+
+ style D fill:#ffcdd2,stroke:#c62828
+ style C fill:#c8e6c9,stroke:#2e7d32
+```
+
+---
+
+## 七、请求分段
+
+### 基本思想
+
+**请求分段**是虚拟存储器在[[15_段式存储管理|分段]]基础上的实现:
+- 段不需要全部装入内存
+- 访问到不在内存的段时,触发**缺段中断**,从外存调入
+
+### 与请求分页的对比
+
+| 对比项 | 请求分页 | 请求分段 |
+|--------|---------|---------|
+| 调入/调出单位 | 页面(固定大小) | 段(可变大小) |
+| 碎片 | 内碎片 | 外碎片 |
+| 共享 | 以页为粒度 | 以段为粒度 |
+| 中断类型 | 缺页中断 | 缺段中断 |
+| 实现复杂度 | 较简单 | 较复杂 |
+
+---
+
+## 八、完整计算示例
+
+### 综合例题
+
+> 某系统采用请求分页存储管理,页面大小为 4KB,物理内存有 4 个页框。某进程的页面访问序列为:**0, 1, 4, 2, 0, 2, 6, 5, 1, 2, 3, 2, 1, 2, 6, 5, 2, 1, 3, 6**。分别用 FIFO 和 LRU 算法计算缺页次数。
+
+**FIFO 算法**:
+
+| 访问 | 页框0 | 页框1 | 页框2 | 页框3 | 缺页? |
+|------|-------|-------|-------|-------|-------|
+| 0 | 0 | - | - | - | 缺页 |
+| 1 | 0 | 1 | - | - | 缺页 |
+| 4 | 0 | 1 | 4 | - | 缺页 |
+| 2 | 0 | 1 | 4 | 2 | 缺页 |
+| 0 | 0 | 1 | 4 | 2 | 命中 |
+| 2 | 0 | 1 | 4 | 2 | 命中 |
+| 6 | 6 | 1 | 4 | 2 | 缺页(淘汰0) |
+| 5 | 6 | 5 | 4 | 2 | 缺页(淘汰1) |
+| 1 | 6 | 5 | 1 | 2 | 缺页(淘汰4) |
+| 2 | 6 | 5 | 1 | 2 | 命中 |
+| 3 | 6 | 5 | 1 | 3 | 缺页(淘汰2) |
+| 2 | 2 | 5 | 1 | 3 | 缺页(淘汰6) |
+| 1 | 2 | 5 | 1 | 3 | 命中 |
+| 2 | 2 | 5 | 1 | 3 | 命中 |
+| 6 | 2 | 6 | 1 | 3 | 缺页(淘汰5) |
+| 5 | 2 | 6 | 5 | 3 | 缺页(淘汰1) |
+| 2 | 2 | 6 | 5 | 3 | 命中 |
+| 1 | 2 | 6 | 5 | 1 | 缺页(淘汰3) |
+| 3 | 3 | 6 | 5 | 1 | 缺页(淘汰2) |
+| 6 | 3 | 6 | 5 | 1 | 命中 |
+
+**FIFO 缺页次数: 14**
+
+**LRU 算法**:
+
+| 访问 | 页框0 | 页框1 | 页框2 | 页框3 | 缺页? |
+|------|-------|-------|-------|-------|-------|
+| 0 | 0 | - | - | - | 缺页 |
+| 1 | 0 | 1 | - | - | 缺页 |
+| 4 | 0 | 1 | 4 | - | 缺页 |
+| 2 | 0 | 1 | 4 | 2 | 缺页 |
+| 0 | 0 | 1 | 4 | 2 | 命中 |
+| 2 | 0 | 1 | 4 | 2 | 命中 |
+| 6 | 0 | 1 | 6 | 2 | 缺页(淘汰4) |
+| 5 | 0 | 5 | 6 | 2 | 缺页(淘汰1) |
+| 1 | 1 | 5 | 6 | 2 | 缺页(淘汰0) |
+| 2 | 1 | 5 | 6 | 2 | 命中 |
+| 3 | 1 | 5 | 3 | 2 | 缺页(淘汰6) |
+| 2 | 1 | 5 | 3 | 2 | 命中 |
+| 1 | 1 | 5 | 3 | 2 | 命中 |
+| 2 | 1 | 5 | 3 | 2 | 命中 |
+| 6 | 1 | 5 | 3 | 6 | 缺页(淘汰2) |
+| 5 | 1 | 5 | 3 | 6 | 命中 |
+| 2 | 2 | 5 | 3 | 6 | 缺页(淘汰1) |
+| 1 | 2 | 1 | 3 | 6 | 缺页(淘汰5) |
+| 3 | 2 | 1 | 3 | 6 | 命中 |
+| 6 | 2 | 1 | 3 | 6 | 命中 |
+
+**LRU 缺页次数: 12**
+
+---
+
+## 九、小结
+
+```mermaid
+mindmap
+ root((虚拟存储器))
+ 基本思想
+ 虚拟空间>物理内存
+ 部分装入
+ 按需调入调出
+ 局部性原理
+ 时间局部性
+ 空间局部性
+ 行优先vs列优先
+ 请求分页
+ 页表扩展状态位
+ 缺页中断处理
+ 页面置换
+ 置换算法
+ OPT理论最优
+ FIFO简单但有Belady异常
+ LRU效果好但开销大
+ Clock近似LRU
+ LFU最少使用
+ 内存分配
+ 平均/按比例/优先权
+ 固定/可变分配
+ 局部/全局置换
+ 抖动与工作集
+ 频繁缺页=抖动
+ 工作集=近期访问页面集
+ 帧数≥工作集大小
+```
+
+---
+
+## 思考题
+
+1. **概念理解**: 为什么说虚拟存储器的基础是局部性原理?如果没有局部性,虚拟存储器还能工作吗?
+
+2. **算法对比**: 对于页面引用串 1,2,3,4,1,2,5,1,2,3,4,5(3个物理块),分别计算 OPT、FIFO、LRU 的缺页次数。是否存在Belady异常?
+
+3. **工作集**: 某进程页面访问序列为 2,6,1,5,7,7,7,2,5,1,3,1,5,7,2,5,设 $\Delta=5$,求各时刻的工作集大小。
+
+4. **抖动分析**: 某系统物理内存可容纳 10 个工作集页面。目前有 5 个进程,工作集大小分别为 {3,2,2,1,3}。此时再增加一个工作集大小为 3 的进程,系统是否会发生抖动?
+
+5. **综合题**: 在请求分页系统中,页面大小 4KB,页表项 4 字节,虚拟地址 32 位,物理地址 32 位。
+ - 一级页表有多大?
+ - 如果采用二级页表,页目录和页表各多大?
+ - 如果 TLB 命中率 95%,TLB 访问 10ns,内存访问 100ns,有效访问时间是多少?
+
+---
+
+## 关联笔记
+
+- [[14_分页存储管理]] — 分页基础、页表结构、地址变换
+- [[15_段式存储管理]] — 分段思想、段页式
+- [[11_处理机调度]] — 进程调度与抖动的关系
+
+---
+
+**上一讲**: [[15_段式存储管理]]
+**目录**: [[00_课程导航]]
diff --git a/操作系统/17_IO系统/17_IO系统.md b/操作系统/17_IO系统/17_IO系统.md
new file mode 100644
index 0000000..16f3951
--- /dev/null
+++ b/操作系统/17_IO系统/17_IO系统.md
@@ -0,0 +1,937 @@
+# 17 I/O系统
+
+> **课程**:操作系统
+> **关联章节**:[[01_系统运行机制]] | [[04_文件IO编程]] | [[05_磁盘空间管理]]
+
+---
+
+## 一、I/O系统概述
+
+### 1.1 I/O系统的基本概念
+
+I/O系统(输入/输出系统)是操作系统中负责管理**外部设备**与**主机(CPU和内存)**之间数据传输的子系统。它是计算机系统中最复杂的组成部分之一。
+
+> **核心任务**:屏蔽各种I/O设备的硬件差异,为上层提供**统一、简洁**的I/O接口,使用户程序能够方便地使用各种外部设备。
+
+I/O系统需要解决的关键问题:
+- 设备的多样性与统一管理
+- CPU与I/O设备之间的速度差异
+- 设备的分配与回收
+- I/O操作的高效执行
+
+### 1.2 I/O设备分类
+
+#### 按数据传输单位分类
+
+| 类型 | 特征 | 典型设备 |
+|------|------|----------|
+| **字符设备** | 以字符为单位传输,速率低,不可寻址 | 键盘、鼠标、串口 |
+| **块设备** | 以数据块为单位传输,速率高,可寻址 | 磁盘、磁带、光盘 |
+
+#### 按传输速率分类
+
+| 类型 | 速率范围 | 典型设备 |
+|------|----------|----------|
+| **低速设备** | 几字节/s ~ 几百B/s | 鼠标、键盘、调制解调器 |
+| **中速设备** | 几KB/s ~ 几MB/s | 打印机、扫描仪 |
+| **高速设备** | 几MB/s ~ 几GB/s | 磁盘、磁带、网络接口 |
+
+#### 按使用特性分类
+
+| 类型 | 说明 | 典型设备 |
+|------|------|----------|
+| **存储设备** | 用于永久保存信息 | 磁盘、磁带、光盘 |
+| **输入设备** | 用于向计算机输入信息 | 键盘、鼠标、扫描仪 |
+| **输出设备** | 用于将计算机信息输出 | 显示器、打印机 |
+| **交互设备** | 既能输入又能输出 | 触摸屏、网络接口 |
+
+### 1.3 I/O设备的组成
+
+I/O设备一般由**机械部分**和**电子部分**组成:
+
+```
+I/O设备 = 设备(Device)+ 设备控制器(Device Controller)
+```
+
+- **设备(Device)**:执行实际的I/O操作(如磁盘的旋转和读写)
+- **设备控制器(Device Controller)**:电子部件,负责控制设备的物理操作,是CPU与设备之间的接口
+
+> **设备控制器的功能**:
+> 1. 接收和识别CPU发来的命令
+> 2. 实现CPU与控制器、控制器与设备之间的数据交换
+> 3. 记录设备的状态供CPU查询
+> 4. 识别设备地址(端口地址)
+
+---
+
+## 二、I/O控制方式
+
+I/O控制方式的发展目标:**减少CPU对I/O过程的干预**,提高CPU与I/O设备的**并行操作**程度。
+
+### 2.1 程序直接控制方式(Programmed I/O)
+
+#### 基本思想
+
+CPU通过不断**轮询(polling)**设备状态寄存器,直接控制I/O操作的全过程。
+
+#### 工作流程
+
+```mermaid
+sequenceDiagram
+ participant CPU
+ participant 控制器
+ participant 设备
+
+ CPU->>控制器: 1. 发送I/O命令
+ CPU->>控制器: 2. 读取状态寄存器
+ loop 轮询等待
+ CPU->>控制器: 检查忙/完成位
+ 控制器-->>CPU: 返回状态(忙)
+ end
+ 控制器-->>CPU: 返回状态(完成)
+ CPU->>控制器: 3. 读取数据寄存器
+ 控制器-->>CPU: 数据
+```
+
+#### 优缺点
+
+| 优点 | 缺点 |
+|------|------|
+| 实现简单 | CPU利用率极低(忙等待) |
+| 硬件要求低 | CPU与设备串行工作 |
+| 易于理解 | 不适用于多设备系统 |
+
+> [!note] 关键特征
+> CPU在整个I/O过程中**一直参与**,以**轮询方式**检查设备状态,CPU与设备完全**串行**工作。
+
+### 2.2 中断驱动方式(Interrupt-Driven I/O)
+
+#### 基本思想
+
+CPU发出I/O命令后,**不需要持续轮询**,可以去执行其他进程。当设备完成操作后,通过**中断**通知CPU。
+
+#### 工作流程
+
+```mermaid
+sequenceDiagram
+ participant CPU
+ participant 控制器
+ participant 设备
+
+ CPU->>控制器: 1. 发送I/O命令
+ Note over CPU: CPU转去执行其他进程
+ 控制器->>设备: 启动设备操作
+ 设备->>控制器: 操作完成
+ 控制器->>CPU: 2. 发出中断请求
+ Note over CPU: CPU响应中断
+ CPU->>控制器: 3. 读取数据
+ 控制器-->>CPU: 数据
+```
+
+#### 优缺点
+
+| 优点 | 缺点 |
+|------|------|
+| CPU利用率提高 | 每次传输以字/字节为单位 |
+| CPU与设备可并行工作 | 频繁中断开销大 |
+| 支持多设备 | 数据仍需经过CPU中转 |
+
+> [!note] 与程序直接控制方式的对比
+> 中断驱动方式中,CPU在**启动I/O后**可执行其他任务,设备完成时通过**中断**通知CPU,实现了CPU与设备的**部分并行**。但每次数据传输都需要CPU干预。
+
+### 2.3 DMA方式(Direct Memory Access)
+
+#### 基本思想
+
+引入**DMA控制器**,在设备与内存之间开辟**直接数据通路**,数据传输不需要经过CPU中转。CPU只需在传输**开始**和**结束**时介入。
+
+#### DMA控制器的组成
+
+```
+DMA控制器寄存器:
+┌─────────────────────────────────┐
+│ MAR(内存地址寄存器) │ → 数据在内存中的目标地址
+│ DC (数据计数器) │ → 剩余要传输的字节数
+│ DR (数据寄存器) │ → 暂存传输的数据
+│ CR (命令/状态寄存器) │ → 存放CPU发来的命令和状态
+└─────────────────────────────────┘
+```
+
+#### 工作流程
+
+```mermaid
+sequenceDiagram
+ participant CPU
+ participant DMA
+ participant 内存
+ participant 设备
+
+ CPU->>DMA: 1. 设置MAR、DC、CR(发送命令)
+ Note over CPU: CPU转去执行其他进程
+ DMA->>设备: 启动设备
+ loop 块传输(硬件自动)
+ 设备->>DMA: 读入一个字
+ DMA->>内存: 写入内存(MAR)
+ Note over DMA: MAR++, DC--
+ end
+ DMA->>CPU: 2. 传输完成,发出中断
+ CPU->>DMA: 3. 处理完成中断
+```
+
+#### DMA与中断驱动的对比
+
+| 比较项 | 中断驱动 | DMA |
+|--------|----------|-----|
+| 数据传输单位 | 字/字节 | **数据块** |
+| 数据是否经过CPU | **是** | **否**(直接内存访问) |
+| 中断频率 | 每个字/字节一次 | **每块一次** |
+| CPU干预程度 | 每次传输都干预 | 仅开始和结束干预 |
+
+> [!important] DMA的核心优势
+> 数据在**设备和内存之间**直接传输,不经过CPU,以**块**为单位传输,大大减少了CPU的干预次数。
+
+### 2.4 通道方式(Channel I/O)
+
+#### 基本思想
+
+通道是一种**专用处理器**(I/O处理器),有自己的**指令系统**(通道指令)和**程序**(通道程序)。CPU只需发出一条I/O指令,通道就能**自主完成**整个数据传输过程。
+
+#### 通道的类型
+
+| 类型 | 特征 | 传输单位 |
+|------|------|----------|
+| **字节多路通道** | 以字节为单位交叉传输多个设备 | 字节 |
+| **数组选择通道** | 一次只服务一个设备,传输速率高 | 数据块 |
+| **数组多路通道** | 上述两种的结合,兼顾效率和利用率 | 数据块 |
+
+#### 工作流程
+
+```mermaid
+sequenceDiagram
+ participant CPU
+ participant 通道
+ participant 控制器
+ participant 设备
+
+ CPU->>通道: 1. 发送通道程序地址
+ Note over CPU: CPU完全转去执行其他进程
+ 通道->>通道: 取通道指令执行
+ 通道->>控制器: 控制设备操作
+ loop 通道自主执行
+ 通道->>内存: 数据传输(无需CPU干预)
+ end
+ 通道->>CPU: 2. 传输完成,发出中断
+```
+
+#### 通道与DMA的对比
+
+| 比较项 | DMA | 通道 |
+|--------|-----|------|
+| 控制能力 | 受CPU控制 | **自主执行**通道程序 |
+| 并行度 | 一个DMA对应一类设备 | 一个通道可控制**多个**设备 |
+| CPU干预 | 需要CPU设置传输参数 | 仅需CPU启动通道 |
+| 灵活性 | 较低 | **高**(可编程) |
+
+> [!tip] I/O控制方式演进总结
+> | 方式 | CPU干预频率 | 数据单位 | 并行程度 |
+> |------|-------------|----------|----------|
+> | 程序直接控制 | 每个字 | 字 | 无并行 |
+> | 中断驱动 | 每个字 | 字 | 部分并行 |
+> | DMA | 每个块 | 块 | 较高并行 |
+> | 通道 | 仅启动/结束 | 一组块 | 高度并行 |
+
+---
+
+## 三、I/O软件层次结构
+
+### 3.1 层次结构概览
+
+I/O软件采用**分层设计**思想,每一层利用下层提供的服务,为上层提供更高级的服务。
+
+```mermaid
+graph TB
+ subgraph 用户空间
+ A["用户层I/O软件
(库函数: printf, scanf)"]
+ end
+ subgraph 内核空间
+ B["设备独立性软件
(统一接口、缓冲、分配)"]
+ C["设备驱动程序
(控制具体设备)"]
+ D["中断处理程序
(响应设备中断)"]
+ end
+ subgraph 硬件
+ E["设备控制器 / 设备"]
+ end
+
+ A -->|"系统调用"| B
+ B -->|"调用驱动"| C
+ C -->|"启动I/O"| E
+ E -->|"中断"| D
+ D -->|"唤醒驱动"| C
+ C -->|"唤醒上层"| B
+ B -->|"返回结果"| A
+
+ style A fill:#e1f5fe
+ style B fill:#fff3e0
+ style C fill:#fce4ec
+ style D fill:#f3e5f5
+ style E fill:#e8f5e9
+```
+
+### 3.2 各层功能详解
+
+#### 第一层:中断处理程序
+
+**位置**:最底层,直接与硬件交互
+**功能**:
+- 响应设备中断请求
+- 保存被中断进程的现场
+- 分析中断原因,转入相应的中断处理
+- 唤醒等待I/O完成的进程
+- 恢复现场,返回被中断的进程
+
+```c
+// 中断处理程序伪代码
+void disk_interrupt_handler() {
+ save_context(); // 保存现场
+ wake_up(device_waiter); // 唤醒等待该设备的进程
+ schedule(); // 可能触发进程调度
+ restore_context(); // 恢复现场
+}
+```
+
+#### 第二层:设备驱动程序
+
+**位置**:紧邻中断处理程序之上
+**功能**:
+- 将上层的抽象I/O请求转换为对**具体设备控制器**的操作
+- 设置设备控制器的寄存器
+- 启动设备进行I/O操作
+- 处理设备中断,检查操作结果
+
+> [!note] 设备驱动程序的特点
+> - 每类设备对应一个**设备驱动程序**
+> - 是操作系统中**了解设备控制器细节**的唯一模块
+> - 通常由**设备制造商**提供
+
+#### 第三层:设备独立性软件
+
+**位置**:设备驱动程序之上
+**功能**:
+- 提供**统一的I/O接口**,屏蔽设备差异
+- 实现**缓冲管理**(缓冲区的分配与回收)
+- 实现**设备分配与回收**
+- 实现**逻辑设备名到物理设备名**的映射
+- 提供**差错处理**机制
+- 提供**设备保护**(防止非法访问)
+
+> **设备独立性**:应用程序使用**逻辑设备名**请求I/O,由系统将其映射到**物理设备**。这样,更换物理设备不影响程序运行。
+
+```
+应用程序: read(fd, buf, size)
+ ↓ 逻辑设备名
+设备独立性软件: 查逻辑设备表 → 物理设备
+ ↓
+设备驱动程序: 操作具体设备控制器
+```
+
+#### 第四层:用户层I/O软件
+
+**位置**:最上层,用户空间
+**功能**:
+- 提供**库函数**接口(如C语言的 `printf`、`scanf`、`read`、`write`)
+- 对用户数据进行**格式化**处理
+- 通过**系统调用**进入内核
+
+### 3.3 一次完整的I/O操作流程
+
+```
+用户调用 printf("Hello")
+ → 用户层:格式化数据,调用 write() 系统调用
+ → 设备独立性软件:查设备表,分配缓冲区,确定物理设备
+ → 设备驱动程序:向设备控制器发送命令
+ → 设备控制器:启动设备进行输出
+ → 设备完成操作,发出中断
+ → 中断处理程序:唤醒等待进程
+ → 设备驱动程序:检查操作结果
+ → 设备独立性软件:释放缓冲区,返回结果
+ → 用户层:系统调用返回
+用户程序继续执行
+```
+
+---
+
+## 四、缓冲管理
+
+### 4.1 引入缓冲的原因
+
+1. **缓和CPU与I/O设备速度不匹配**的矛盾
+2. **减少对CPU的中断频率**,放宽对中断响应时间的限制
+3. **提高CPU与I/O设备之间的并行性**
+
+### 4.2 单缓冲(Single Buffer)
+
+#### 基本思想
+
+系统为设备分配**一个**缓冲区。
+
+#### 工作方式
+
+处理一块数据的时间 = **max(C, T) + M**
+- C:CPU处理一块数据的时间
+- T:设备将一块数据传送到缓冲区的时间
+- M:缓冲区数据传送到用户区的时间
+
+```
+设备 → [缓冲区] → 用户区 → CPU处理
+```
+
+> **分析**:当C > T时,CPU处理慢,设备需等待;当T > C时,设备传输慢,CPU需等待。两者不能完全并行。
+
+### 4.3 双缓冲(Double Buffer)
+
+#### 基本思想
+
+系统为设备分配**两个**缓冲区,实现**并行操作**。
+
+#### 工作方式
+
+```
+设备 → [缓冲区1] → 用户区 缓冲区2 ← 设备
+设备 → [缓冲区2] → 用户区 缓冲区1 ← 设备
+```
+
+处理一块数据的时间 = **max(C, T)**
+
+> [!tip] 双缓冲的优势
+> 当一个缓冲区的数据传送到用户区时,另一个缓冲区可同时接收设备的新数据,实现了**设备与CPU的并行**。
+
+### 4.4 循环缓冲(Circular Buffer)
+
+#### 基本思想
+
+系统分配**多个**大小相等的缓冲区,组成**循环队列**。
+
+```
+ ┌───┐ ┌───┐ ┌───┐ ┌───┐
+输入 → │ B1 │ → │ B2 │ → │ B3 │ → │ B4 │ → 输出
+ └───┘ └───┘ └───┘ └───┘
+ ↑ ↓
+ └────────────────────────┘
+ 循环使用
+```
+
+- **输入指针(in)**:指向下一个可放入数据的缓冲区
+- **输出指针(out)**:指向下一个可取出数据的缓冲区
+
+> **适用场景**:I/O速度与处理速度差异较大的系统。
+
+### 4.5 缓冲池(Buffer Pool)
+
+#### 基本思想
+
+系统从公用内存中分配**一组**缓冲区,组成缓冲池,供**多个进程共享**使用。
+
+#### 缓冲区队列
+
+缓冲池中的缓冲区按用途分为三个队列:
+
+| 队列 | 用途 |
+|------|------|
+| **空缓冲队列(emq)** | 尚未使用的空缓冲区 |
+| **输入队列(inq)** | 装满输入数据的缓冲区 |
+| **输出队列(outq)** | 装满输出数据的缓冲区 |
+
+#### 四种工作缓冲区
+
+| 缓冲区类型 | 功能 |
+|------------|------|
+| **收容输入(hin)** | 从空缓冲队列取缓冲区,接收输入数据,挂入输入队列 |
+| **提取输入(sin)** | 从输入队列取缓冲区,供CPU提取数据,释放回空缓冲队列 |
+| **收容输出(hout)** | 从空缓冲队列取缓冲区,接收CPU输出数据,挂入输出队列 |
+| **提取输出(sout)** | 从输出队列取缓冲区,供设备提取数据,释放回空缓冲队列 |
+
+```mermaid
+graph LR
+ subgraph 缓冲池
+ 空缓冲队列["空缓冲队列 (emq)"]
+ 输入队列["输入队列 (inq)"]
+ 输出队列["输出队列 (outq)"]
+ end
+
+ 输入设备 -->|"收容输入(hin)"| 空缓冲队列
+ 空缓冲队列 -->|"收容输入(hin)"| 输入队列
+ 输入队列 -->|"提取输入(sin)"| CPU
+ CPU -->|"提取输入(sin)"| 空缓冲队列
+
+ CPU -->|"收容输出(hout)"| 空缓冲队列
+ 空缓冲队列 -->|"收容输出(hout)"| 输出队列
+ 输出队列 -->|"提取输出(sout)"| 输出设备
+ 输出设备 -->|"提取输出(sout)"| 空缓冲队列
+
+ style 空缓冲队列 fill:#e8f5e9
+ style 输入队列 fill:#e1f5fe
+ style 输出队列 fill:#fff3e0
+```
+
+---
+
+## 五、设备分配与回收
+
+### 5.1 设备分配中的数据结构
+
+#### 设备控制表(DCT)
+
+每个设备一张,记录设备的状态和属性:
+
+```
+┌─────────────────────────────┐
+│ 设备控制表 (DCT) │
+├─────────────────────────────┤
+│ 设备标识符 (设备ID) │
+│ 设备类型 │
+│ 设备状态:忙/空闲/故障 │
+│ 指向控制器表的指针 (COCT) │
+│ 重复执行次数/定时器 │
+│ 设备队列的队首指针 │
+└─────────────────────────────┘
+```
+
+#### 控制器控制表(COCT)
+
+每个设备控制器一张:
+
+```
+┌─────────────────────────────┐
+│ 控制器控制表 (COCT) │
+├─────────────────────────────┤
+│ 控制器标识符 │
+│ 控制器状态 │
+│ 指向通道表的指针 (CHCT) │
+│ 控制器队列的队首指针 │
+└─────────────────────────────┘
+```
+
+#### 通道控制表(CHCT)
+
+每个通道一张:
+
+```
+┌─────────────────────────────┐
+│ 通道控制表 (CHCT) │
+├─────────────────────────────┤
+│ 通道标识符 │
+│ 通道状态 │
+│ 通道连接的控制器列表 │
+│ 通道队列的队首指针 │
+└─────────────────────────────┘
+```
+
+#### 系统设备表(SDT)
+
+整个系统一张,记录所有设备的信息:
+
+```
+┌─────────────────────────────┐
+│ 系统设备表 (SDT) │
+├─────────────────────────────┤
+│ 设备类 │
+│ 设备标识符 │
+│ DCT指针 │
+│ 驱动程序入口地址 │
+└─────────────────────────────┘
+```
+
+### 5.2 设备分配原则
+
+| 考虑因素 | 说明 |
+|----------|------|
+| **固有属性** | 独占设备、共享设备、虚拟设备 |
+| **安全性** | 避免死锁(参考 [[12_死锁]]) |
+| **效率** | 充分利用设备,提高系统吞吐量 |
+
+### 5.3 独占设备、共享设备与SPOOLing
+
+#### 独占设备
+
+同一时刻**只能被一个进程**使用(如打印机)。
+
+#### 共享设备
+
+可被**多个进程交替**使用(如磁盘)。
+
+#### SPOOLing技术(假脱机技术)
+
+将**独占设备**改造为**共享设备**的技术,是虚拟设备的典型实现。
+
+```
+输入设备 ──→ 输入井 ──→ 内存 ──→ 输出井 ──→ 输出设备
+ (输入进程) (磁盘) (用户进程) (磁盘) (输出进程)
+```
+
+> [!important] SPOOLing系统的核心思想
+> 在磁盘上开辟**输入井**和**输出井**,用**输入进程**模拟脱机输入,用**输出进程**模拟脱机输出。用户进程不直接操作独占设备,而是与磁盘缓冲区交互,从而实现**独占设备的共享**。
+
+**经典应用**:共享打印机——多个进程的打印请求先存入输出井,由输出进程依次输出到打印机。
+
+### 5.4 设备分配流程
+
+```mermaid
+flowchart TD
+ A[进程请求设备] --> B{分配设备}
+ B -->|独占设备| C{设备空闲?}
+ C -->|是| D[分配设备, 修改DCT]
+ C -->|否| E[进程阻塞, 排入设备等待队列]
+ B -->|共享设备| F[直接分配]
+ D --> G{分配控制器}
+ F --> G
+ G --> H{分配通道}
+ H --> I[分配成功, 启动I/O]
+ I --> J[I/O完成]
+ J --> K[回收通道]
+ K --> L[回收控制器]
+ L --> M[回收设备]
+```
+
+---
+
+## 六、磁盘调度算法
+
+### 6.1 磁盘结构与性能参数
+
+#### 磁盘的物理结构
+
+```
+ 柱面 (Cylinder)
+ ↓
+ ┌───────────────────────┐
+ │ 磁头0 磁头1 磁头2 │ ← 磁头 (Head)
+ │ ───── ───── ───── │
+ │ 磁道0 磁道0 磁道0 │ ← 磁道 (Track)
+ │ 磁道1 磁道1 磁道1 │
+ │ ... ... ... │
+ │ 磁道n 磁道n 磁道n │
+ └───────────────────────┘
+ 盘面 盘面 盘面
+```
+
+#### 磁盘访问时间组成
+
+```
+总访问时间 T = 寻道时间 Ts + 旋转延迟 Tr + 传输时间 Tt
+```
+
+| 参数 | 含义 | 典型值 |
+|------|------|--------|
+| **寻道时间 Ts** | 磁头移动到目标磁道的时间 | 几ms ~ 几十ms(**最主要**) |
+| **旋转延迟 Tr** | 等待目标扇区旋转到磁头下的时间 | 转速7200rpm → 平均4.17ms |
+| **传输时间 Tt** | 数据读写时间 | 较短 |
+
+> [!important] 优化重点
+> **寻道时间**在总访问时间中占比最大,因此磁盘调度算法主要优化**寻道时间**。
+
+### 6.2 常见磁盘调度算法
+
+#### 示例磁盘请求序列
+
+假设当前磁头位置在**磁道53**,磁道范围0~199,请求队列:
+
+```
+请求队列: 98, 183, 37, 122, 14, 124, 65, 67
+方向: 磁道号增加方向 (向磁道号大的方向移动)
+```
+
+---
+
+#### FCFS(先来先服务)
+
+**原则**:按请求到达的先后顺序依次服务。
+
+```
+磁头移动顺序: 53 → 98 → 183 → 37 → 122 → 14 → 124 → 65 → 67
+
+移动距离:
+|53-98| + |98-183| + |183-37| + |37-122| + |122-14| + |14-124| + |124-65| + |65-67|
+= 45 + 85 + 146 + 85 + 108 + 110 + 59 + 2
+= 640 磁道
+
+平均寻道长度 = 640 / 8 = 80 磁道
+```
+
+> [!note] FCFS特点
+> - **优点**:公平、简单、不会产生饥饿
+> - **缺点**:寻道距离大,性能差(磁头来回大幅移动)
+> - **适用**:请求量少或对公平性要求高的场景
+
+---
+
+#### SSTF(最短寻道时间优先)
+
+**原则**:选择距离当前磁头位置**最近**的请求优先服务。
+
+```
+当前位置: 53
+请求队列: 98, 183, 37, 122, 14, 124, 65, 67
+
+Step 1: 当前53, 最近是65(距离12)或37(距离16), 选65
+Step 2: 当前65, 最近是67(距离2), 选67
+Step 3: 当前67, 最近是37(距离30)或98(距离31), 选37
+Step 4: 当前37, 最近是14(距离23), 选14
+Step 5: 当前14, 最近是98(距离84), 选98
+Step 6: 当前98, 最近是122(距离24), 选122
+Step 7: 当前122, 最近是124(距离2), 选124
+Step 8: 当前124, 最近是183(距离59), 选183
+
+移动顺序: 53 → 65 → 67 → 37 → 14 → 98 → 122 → 124 → 183
+
+移动距离:
+|53-65| + |65-67| + |67-37| + |37-14| + |14-98| + |98-122| + |122-124| + |124-183|
+= 12 + 2 + 30 + 23 + 84 + 24 + 2 + 59
+= 236 磁道
+
+平均寻道长度 = 236 / 8 = 29.5 磁道
+```
+
+> [!warning] SSTF的"饥饿"问题
+> - **优点**:平均寻道时间短
+> - **缺点**:可能导致某些请求**长期得不到服务**(饥饿),尤其是距离较远的磁道请求
+> - **原因**:距离远的请求总被距离近的新请求"插队"
+
+---
+
+#### SCAN(电梯算法/扫描算法)
+
+**原则**:磁头沿**一个方向**移动,依次服务沿途的请求,到达磁道尽头后**反向**移动。
+
+```
+当前位置: 53, 方向: 磁道号增加
+
+磁头移动: 53 → 65 → 67 → 98 → 122 → 124 → 183 → (到达尽头) → 37 → 14
+
+移动距离:
+(53→65): 12
+(65→67): 2
+(67→98): 31
+(98→122): 24
+(122→124): 2
+(124→183): 59
+(183→37): 146 ← 到达尽头后反向
+(37→14): 23
+
+总距离 = 12 + 2 + 31 + 24 + 2 + 59 + 146 + 23 = 299 磁道
+
+平均寻道长度 = 299 / 8 = 37.4 磁道
+```
+
+> [!note] SCAN特点
+> - **优点**:寻道性能好,不会产生饥饿
+> - **缺点**:两侧磁道的访问频率不均匀(中间磁道被更频繁访问)
+> - **类比**:像电梯一样,先上后下(或先下后上)
+
+---
+
+#### C-SCAN(循环扫描算法)
+
+**原则**:磁头**单向移动**,到达尽头后**快速返回**起始端(返回途中不服务),继续单向扫描。
+
+```
+当前位置: 53, 方向: 磁道号增加
+
+磁头移动: 53 → 65 → 67 → 98 → 122 → 124 → 183 → (到达尽头)
+→ 快速返回0 → 14 → 37
+
+移动距离:
+(53→65): 12
+(65→67): 2
+(67→98): 31
+(98→122): 24
+(122→124): 2
+(124→183): 59
+(183→0): 183 ← 快速返回
+(0→14): 14
+(14→37): 23
+
+总距离 = 12 + 2 + 31 + 24 + 2 + 59 + 183 + 14 + 23 = 350 磁道
+
+平均寻道长度 = 350 / 8 = 43.75 磁道
+```
+
+> [!tip] C-SCAN vs SCAN
+> - **C-SCAN** 解决了 SCAN 中**磁道访问频率不均匀**的问题
+> - 返回过程不服务请求,使各磁道被访问的概率更**均匀**
+> - 总移动距离可能比SCAN略大
+
+---
+
+#### LOOK与C-LOOK
+
+**LOOK**:SCAN的改进,磁头移动到**最后一个请求**就反向(不必到磁道尽头)。
+**C-LOOK**:C-SCAN的改进,同理不必到磁道尽头。
+
+```
+当前位置: 53, 方向: 磁道号增加
+
+LOOK: 53 → 65 → 67 → 98 → 122 → 124 → 183 → 37 → 14
+(不必到达磁道199,到183就反向)
+
+移动距离 = 12 + 2 + 31 + 24 + 2 + 59 + 146 + 23 = 299
+注意: SCAN和LOOK在此例中结果相同,因为183已是最大请求
+
+C-LOOK: 53 → 65 → 67 → 98 → 122 → 124 → 183 → 14 → 37
+(不必返回磁道0,直接跳到最小请求14)
+
+移动距离 = 12 + 2 + 31 + 24 + 2 + 59 + 169 + 23 = 322
+```
+
+### 6.3 磁盘调度算法对比
+
+| 算法 | 寻道距离 | 公平性 | 饥饿问题 | 特点 |
+|------|----------|--------|----------|------|
+| **FCFS** | 640 | 公平 | 无 | 简单,性能差 |
+| **SSTF** | 236 | 不公平 | **有** | 性能好,可能饥饿 |
+| **SCAN** | 299 | 较公平 | 无 | 性能好,两侧不均匀 |
+| **C-SCAN** | 350 | 公平 | 无 | 各磁道均匀访问 |
+| **LOOK** | 299 | 较公平 | 无 | SCAN的优化版本 |
+| **C-LOOK** | 322 | 公平 | 无 | C-SCAN的优化版本 |
+
+```mermaid
+graph LR
+ subgraph 磁盘调度算法对比
+ A["FCFS
距离: 640"] -->|"改进"| B["SSTF
距离: 236"]
+ B -->|"解决饥饿"| C["SCAN
距离: 299"]
+ C -->|"优化尽头"| D["LOOK
距离: 299"]
+ C -->|"解决不均匀"| E["C-SCAN
距离: 350"]
+ E -->|"优化尽头"| F["C-LOOK
距离: 322"]
+ end
+
+ style A fill:#ffebee
+ style B fill:#fff3e0
+ style C fill:#e8f5e9
+ style D fill:#e1f5fe
+ style E fill:#f3e5f5
+ style F fill:#fce4ec
+```
+
+---
+
+## 七、磁盘高速缓存
+
+### 7.1 基本概念
+
+**磁盘高速缓存**是在**内存**中为磁盘盘块设置的缓冲区,保存了某些盘块的副本。
+
+> **工作原理**:当有访问磁盘的请求时,先检查高速缓存中是否有所需盘块的数据。如果有(命中),直接从缓存读取;如果未命中,才启动磁盘读取,并将数据存入缓存。
+
+```
+请求访问盘块X
+ → 查找高速缓存
+ → 命中(Hit): 直接从缓存读取(速度提高几个数量级)
+ → 未命中(Miss): 启动磁盘读取 → 数据送入缓存 → 返回给进程
+```
+
+### 7.2 数据交付方式
+
+| 方式 | 说明 | 特点 |
+|------|------|------|
+| **数据交付** | 将缓存中的数据**复制**到进程的内存工作区 | 数据量大,有复制开销 |
+| **指针交付** | 将指向缓存的**指针**交给进程 | 无复制,更高效 |
+
+### 7.3 置换策略
+
+| 算法 | 说明 |
+|------|------|
+| **LRU**(最近最久未使用) | 替换最长时间未被访问的盘块 |
+| **NRU**(最近未使用) | 替换最近一个周期内未被访问的盘块 |
+| **LFU**(最少使用) | 替换被访问次数最少的盘块 |
+
+设计置换算法还需考虑:
+- **访问频率**:经常被访问的盘块应保留在缓存中
+- **可预见性**:某些盘块可能很快又被访问(如顺序读取时的预读)
+- **数据一致性**:确保缓存中的数据与磁盘中的数据一致
+
+### 7.4 数据一致性保障
+
+#### 写回策略
+
+- **立即写回**:修改数据后立即写入磁盘(安全但性能低)
+- **延迟写回**:修改数据先保留在缓存中,稍后写回磁盘(性能高但有风险)
+
+#### 周期性写回
+
+UNIX系统使用 `sync` 系统调用,周期性地将所有已修改的缓存数据强制写回磁盘,防止数据丢失。
+
+```
+UNIX update进程: 周期性调用 sync()
+ → 将所有"脏"缓冲区的数据写回磁盘
+ → 保障数据一致性
+```
+
+---
+
+## 八、综合示例
+
+### 8.1 磁盘调度算法计算练习
+
+> [!example] 练习题
+> 某磁盘有200个磁道(0~199),当前磁头位于**磁道100**,向磁道号**减小**方向移动。请求队列为:`55, 58, 39, 18, 90, 160, 150, 38, 184`。请分别用 FCFS、SSTF、SCAN、C-SCAN 算法计算总寻道距离。
+
+**FCFS**:
+```
+100 → 55 → 58 → 39 → 18 → 90 → 160 → 150 → 38 → 184
+= 45+3+19+21+72+70+10+112+146 = 498
+```
+
+**SSTF**:
+```
+100 → 90(10) → 58(32) → 55(3) → 39(16) → 38(1) → 18(20)
+→ 150(132) → 160(10) → 184(24)
+= 10+32+3+16+1+20+132+10+24 = 248
+```
+
+**SCAN**(向减小方向):
+```
+100 → 90 → 58 → 55 → 39 → 38 → 18 → (到达0) → 150 → 160 → 184
+= 10+32+3+16+1+20+18+150+10+24 = 284
+```
+
+**C-SCAN**(向减小方向,单向):
+```
+100 → 90 → 58 → 55 → 39 → 38 → 18 → (到达0) → 快速到199
+→ 184 → 160 → 150
+= 10+32+3+16+1+20+18+199+15+24+10 = 348
+```
+
+---
+
+## 九、I/O系统与其他子系统的关系
+
+```mermaid
+graph TB
+ A["用户程序"] -->|"系统调用"| B["I/O系统"]
+ B --> C["文件系统
(参考 [[04_文件IO编程]])"]
+ B --> D["存储管理
(参考 [[12_存储管理]])"]
+ B --> E["进程管理
(参考 [[01_系统运行机制]])"]
+ C --> F["磁盘管理
(参考 [[05_磁盘空间管理]])"]
+ D -->|"缓冲区分配"| B
+ E -->|"中断处理、进程调度"| B
+ F -->|"磁盘调度"| B
+
+ style A fill:#e1f5fe
+ style B fill:#fff3e0
+ style C fill:#e8f5e9
+ style D fill:#f3e5f5
+ style E fill:#fce4ec
+ style F fill:#ffebee
+```
+
+> [!summary] 本章要点回顾
+> 1. **I/O控制方式**:程序直接控制 → 中断驱动 → DMA → 通道,CPU干预逐步减少
+> 2. **I/O软件层次**:中断处理程序 → 设备驱动 → 设备独立性软件 → 用户层软件
+> 3. **缓冲管理**:单缓冲、双缓冲、循环缓冲、缓冲池,解决速度匹配问题
+> 4. **设备分配**:考虑独占/共享/虚拟设备,SPOOLing将独占改造为共享
+> 5. **磁盘调度**:FCFS、SSTF(最短寻道)、SCAN(电梯)、C-SCAN(循环扫描)
+> 6. **磁盘高速缓存**:内存中缓存磁盘数据,配合LRU等置换算法
+
+---
+
+> **参考教材**:汤小丹《计算机操作系统》第4版 第5章
+> **下一篇**:[[05_磁盘空间管理]]
diff --git a/操作系统/18_程序代码优化/18_程序代码优化.md b/操作系统/18_程序代码优化/18_程序代码优化.md
new file mode 100644
index 0000000..d7a5c81
--- /dev/null
+++ b/操作系统/18_程序代码优化/18_程序代码优化.md
@@ -0,0 +1,731 @@
+# 18. 程序代码优化
+
+> **课程**: 操作系统 - 程序代码优化
+> **核心内容**: 机器无关优化、代码移动、消除不必要的内存引用、优化障碍(指针别名、函数副作用)、性能度量
+
+---
+
+## 前置知识
+- [[03_C语言编程基础]] -- C语言编译链接过程、指针与内存模型
+- [[13_存储管理基础]] -- 存储器层次结构、局部性原理
+
+---
+
+## 一、代码优化的概念与意义
+
+### 1.1 为什么需要代码优化
+
+> [!important] 核心观点
+> 常数因子也很重要!根据代码编写方式的不同,程序性能可能相差 **10倍** 以上。必须在多个层次上进行优化:算法、数据表示、过程调用和循环。
+
+代码优化的目标:
+- 理解程序如何被编译和执行
+- 学习如何度量程序性能并识别瓶颈
+- 在不破坏代码模块化和通用性的前提下提升性能
+
+### 1.2 优化的层次
+
+```mermaid
+graph TB
+ A["算法优化
选择更高效的算法"] --> B["数据结构优化
选择合适的内存布局"]
+ B --> C["编译器优化
利用编译器选项"]
+ C --> D["源代码级优化
代码移动、消除冗余"]
+ D --> E["指令级优化
利用底层硬件特性"]
+
+ style A fill:#e8f5e9
+ style B fill:#e1f5fe
+ style C fill:#fff3e0
+ style D fill:#fce4ec
+ style E fill:#f3e5f5
+```
+
+### 1.3 编译器优化级别
+
+| 优化级别 | 说明 | 特点 |
+|---------|------|------|
+| `-O0` | 不优化 | 编译最快,调试最方便,性能最差 |
+| `-O1` | 基本优化 | 消除冗余代码、简单内联,平衡编译速度和性能 |
+| `-O2` | 推荐优化 | 启用大多数优化,包括循环优化、指令调度等 |
+| `-O3` | 激进优化 | 包含 `-O2` 所有优化,加上循环展开、SIMD向量化等 |
+
+```bash
+# 不同优化级别的编译
+gcc -O0 prog.c -o prog_O0 # 无优化(调试用)
+gcc -O1 prog.c -o prog_O1 # 基本优化
+gcc -O2 prog.c -o prog_O2 # 推荐优化
+gcc -O3 prog.c -o prog_O3 # 激进优化
+
+# 比较不同优化级别的汇编输出
+gcc -O0 -S prog.c -o prog_O0.s
+gcc -O2 -S prog.c -o prog_O2.s
+diff prog_O0.s prog_O2.s
+```
+
+> [!tip] 编译器的局限性
+> 编译器通常**不会**改善渐近效率(大O复杂度),这取决于程序员选择最优算法。大O节省通常比常数因子更重要,但常数因子也确实重要。编译器在面对"优化障碍"时往往难以进行优化。
+
+---
+
+## 二、性能度量:CPE(每元素周期数)
+
+### 2.1 CPE 概念
+
+CPE(Cycles Per Element)是度量向量或列表操作程序性能的便捷方式:
+
+$$T = CPE \times n + Overhead$$
+
+其中 $n$ 是向量长度,$T$ 是总执行时间(时钟周期数)。
+
+### 2.2 时间尺度
+
+| 指标 | 说明 |
+|------|------|
+| 绝对时间 | 通常使用纳秒($10^{-9}$ 秒) |
+| 时钟周期 | 100 MHz → 10ns/周期;2 GHz → 0.5ns/周期 |
+
+### 2.3 CPE 度量示例
+
+```c
+// psum1: 朴素前缀和
+void psum1(float a[], float p[], long int n) {
+ long int i;
+ p[0] = a[0];
+ for (i = 0; i < n; i++)
+ p[i] = p[i-1] + a[i];
+}
+
+// psum2: 循环展开的前缀和
+void psum2(float a[], float p[], long int n) {
+ long int i;
+ p[0] = a[0];
+ for (i = 1; i < n-1; i += 2) {
+ float mid_val = p[i-1] + a[i];
+ p[i] = mid_val;
+ p[i+1] = mid_val + a[i+1];
+ }
+ if (i < n)
+ p[i] = p[i-1] + a[i];
+}
+```
+
+> [!note] 性能对比
+> `psum2` 通过循环展开减少了关键路径上的操作次数,从而降低了 CPE。
+
+---
+
+## 三、优化实例:向量求和的逐步优化
+
+### 3.1 向量 ADT 定义
+
+```c
+typedef int data_t;
+
+typedef struct {
+ int len;
+ data_t *data;
+} vec_rec, *vec_ptr;
+```
+
+相关操作:
+- `new_vec(len)` -- 创建指定长度的向量
+- `vec_length(v)` -- 返回向量长度
+- `get_vec_start(v)` -- 返回向量数据起始指针
+- `get_vec_element(v, index, &dest)` -- 获取指定下标的元素(带边界检查)
+
+### 3.2 combine1:抽象版本(基线)
+
+```c
+void combine1(vec_ptr v, data_t *dest) {
+ long int i;
+ *dest = IDENT;
+ for (i = 0; i < vec_length(v); i++) {
+ data_t val;
+ get_vec_element(v, i, &val);
+ *dest = *dest OP val;
+ }
+}
+```
+
+> [!warning] 问题分析
+> 每次循环迭代都调用 `vec_length(v)`,即使其返回值始终不变。这属于**循环不变量**问题。
+
+### 3.3 combine2:代码移动(Code Motion)
+
+```c
+void combine2(vec_ptr v, data_t *dest) {
+ long int i;
+ long int length = vec_length(v); // 移出循环
+ *dest = IDENT;
+ for (i = 0; i < length; i++) {
+ data_t val;
+ get_vec_element(v, i, &val);
+ *dest = *dest OP val;
+ }
+}
+```
+
+> [!tip] 代码移动优化
+> 将 `vec_length()` 调用从循环体内移到循环之前。循环不变量外提(Loop-Invariant Code Motion)是最基本的优化之一。
+
+### 3.4 combine3:消除过程调用(Reduction in Strength)
+
+```c
+void combine3(vec_ptr v, data_t *dest) {
+ long int i;
+ long int length = vec_length(v);
+ data_t *data = get_vec_start(v); // 获取数据指针
+ *dest = IDENT;
+ for (i = 0; i < length; i++) {
+ *dest = *dest OP data[i]; // 直接数组访问
+ }
+}
+```
+
+**优化要点**:避免每次迭代都调用 `get_vec_element()` 函数来获取元素,而是在循环前获取数据指针,循环内直接进行指针引用。
+
+### 3.5 combine4:消除不必要的内存引用
+
+```c
+void combine4(vec_ptr v, data_t *dest) {
+ long int i;
+ long int length = vec_length(v);
+ data_t *data = get_vec_start(v);
+ data_t acc = IDENT; // 使用局部变量累积
+ for (i = 0; i < length; i++)
+ acc = acc OP data[i];
+ *dest = acc; // 最后一次性写回
+}
+```
+
+**汇编对比**:
+
+```
+combine3 的循环体(每次迭代需要3条内存指令):
+ movss (%rbp), %xmm0 # 从 dest 读取累加值
+ mulss (%rax,%rdx,4), %xmm0 # 乘以 data[i]
+ movss %xmm0, (%rbp) # 写回 dest
+ addq $1, %rdx # i++
+ cmpq %rdx, %r12 # 比较 i:limit
+ jg .L498 # 循环跳转
+
+combine4 的循环体(只有1条内存指令):
+ mulss (%rax,%rdx,4), %xmm0 # acc *= data[i]
+ addq $1, %rdx # i++
+ cmpq %rdx, %rbp # 比较 limit:i
+ jg .L488 # 循环跳转
+```
+
+> [!important] 优化效果
+> - `combine3`:每次迭代需要 **1次读 + 1次写** 内存
+> - `combine4`:每次迭代只需要 **0次** 额外内存访问(`acc` 在寄存器中)
+> - 局部变量 `acc` 告诉编译器:不需要在每轮循环都检查内存别名
+
+### 3.6 优化效果汇总
+
+| 函数 | 优化方法 | 整数+ CPE | 整数* CPE | 浮点+ CPE |
+|------|---------|----------|----------|----------|
+| combine1 | 抽象接口 | 12.00 | 12.00 | 12.00 |
+| combine2 | 代码移动 | -- | -- | -- |
+| combine3 | 消除过程调用 | -- | -- | -- |
+| combine4 | 使用临时变量累积 | **2.00** | **3.00** | **3.00** |
+
+---
+
+## 四、代码移动(Code Motion)详解
+
+### 4.1 循环不变量外提
+
+将循环中每次迭代结果相同的计算移到循环之前:
+
+```c
+// 优化前:strlen 在每次循环都被调用
+void lower1(char *s) {
+ int i;
+ for (i = 0; i < strlen(s); i++) // O(n^2) 复杂度!
+ if (s[i] >= 'A' && s[i] <= 'Z')
+ s[i] -= ('A' - 'a');
+}
+
+// 优化后:strlen 只调用一次
+void lower2(char *s) {
+ int i;
+ int len = strlen(s); // O(n) 复杂度
+ for (i = 0; i < len; i++)
+ if (s[i] >= 'A' && s[i] <= 'Z')
+ s[i] -= ('A' - 'a');
+}
+```
+
+> [!warning] 为什么编译器不能自动做这个优化?
+> `strlen` 是一个函数调用,编译器**不知道**它是否有副作用,也不知道字符串长度在循环中是否会改变。编译器必须保守处理。
+
+### 4.2 strlen 的内部实现
+
+```c
+size_t strlen(const char *s) {
+ int length = 0;
+ while (*s != '\0') {
+ s++;
+ length++;
+ }
+ return length;
+}
+```
+
+`strlen` 本身是 $O(n)$ 的。如果在循环中每次迭代都调用它,整体复杂度就变成了 $O(n^2)$。
+
+---
+
+## 五、优化障碍(Optimization Blockers)
+
+编译器在进行优化时受到两大根本性约束:
+1. 编译器在**任何可能的条件下**都不能改变程序的行为
+2. 当程序员知道的信息比编译器多时,需要程序员主动干预
+
+### 5.1 指针别名(Memory Aliasing)
+
+> [!danger] 别名问题
+> 当两个不同的内存引用指向同一个存储位置时,就产生了别名。C语言允许地址算术运算和直接访问存储结构,因此别名问题非常容易出现。
+
+```c
+// twiddle1 和 twiddle2 看似等价,但实际上不一定!
+void twiddle1(int *xp, int *yp) {
+ *xp += *yp;
+ *xp += *yp;
+}
+
+void twiddle2(int *xp, int *yp) {
+ *xp += 2 * *yp;
+}
+```
+
+**当 `xp == yp` 时(别名情况)**:
+- `twiddle1`:`*xp = *xp + *xp = 2*xp`,然后 `*xp = 2*xp + 2*xp = 4*xp`
+- `twiddle2`:`*xp = *xp + 2*(*xp) = 3*xp`
+- 结果不同!编译器不能将 `twiddle1` 优化为 `twiddle2`
+
+**别名实例演示**:
+
+```c
+// v = [2, 3, 5]
+combine3(v, get_vec_start(v) + 2); // dest 指向 v->data[2]
+combine4(v, get_vec_start(v) + 2); // dest 指向 v->data[2]
+```
+
+| 函数 | 初始 | i=0 | i=1 | i=2 | 最终 |
+|------|------|-----|-----|-----|------|
+| combine3 | [2,3,5] | [2,3,1] | [2,3,2] | [2,3,6] | [2,3,36] |
+| combine4 | [2,3,5] | [2,3,5] | [2,3,5] | [2,3,5] | [2,3,30] |
+
+> [!tip] 避免别名问题的方法
+> 养成使用**局部变量**的习惯。在循环中用局部变量累积结果,循环结束后再写回目标地址。这是告诉编译器"不需要检查别名"的方式。
+
+### 5.2 函数调用的副作用
+
+```c
+int f(int);
+
+// func1:调用 f 4次
+int func1(int x) {
+ return f(x) + f(x) + f(x) + f(x);
+}
+
+// func2:调用 f 1次
+int func2(int x) {
+ return 4 * f(x);
+}
+```
+
+看起来 `func1` 可以优化为 `func2`,但当 `f` 有副作用时就不行了:
+
+```c
+int counter = 0;
+int f(int x) {
+ return counter++; // 每次调用返回不同的值!
+}
+```
+
+> [!warning] 编译器的保守策略
+> 编译器通常假设函数调用可能有副作用,因此不会轻易将多次函数调用合并。程序员可以:
+> - 使用 `inline` 关键字建议编译器内联
+> - 使用 `const`、`pure` 等属性标记无副作用的函数
+> - 将函数调用结果缓存到局部变量中
+
+---
+
+## 六、循环优化技术
+
+### 6.1 循环展开(Loop Unrolling)
+
+减少循环控制开销,增加指令级并行性:
+
+```c
+// 优化前
+for (int i = 0; i < n; i++) {
+ sum += a[i];
+}
+
+// 2x 循环展开
+int i;
+for (i = 0; i < n - 1; i += 2) {
+ sum += a[i] + a[i+1];
+}
+for (; i < n; i++) { // 处理剩余元素
+ sum += a[i];
+}
+```
+
+### 6.2 循环合并(Loop Fusion)
+
+将多个独立循环合并为一个,改善数据局部性:
+
+```c
+// 优化前:两次遍历
+for (int i = 0; i < n; i++)
+ a[i] = b[i] + c[i];
+for (int i = 0; i < n; i++)
+ d[i] = a[i] * 2;
+
+// 优化后:一次遍历
+for (int i = 0; i < n; i++) {
+ a[i] = b[i] + c[i];
+ d[i] = a[i] * 2;
+}
+```
+
+### 6.3 循环不变量外提(Loop-Invariant Code Motion)
+
+将循环中不变的计算移到循环外:
+
+```c
+// 优化前
+for (int i = 0; i < n; i++) {
+ a[i] = b[i] * (x * y + z); // x*y+z 在循环中不变
+}
+
+// 优化后
+int tmp = x * y + z;
+for (int i = 0; i < n; i++) {
+ a[i] = b[i] * tmp;
+}
+```
+
+### 6.4 循环优化流程图
+
+```mermaid
+flowchart TD
+ A["循环代码"] --> B{"循环体中有
不变量?"}
+ B -->|是| C["循环不变量外提"]
+ B -->|否| D{"循环迭代
次数少?"}
+ C --> D
+ D -->|是| E["循环展开"]
+ D -->|否| F{"多个循环
数据相关?"}
+ E --> F
+ F -->|是| G["循环合并"]
+ F -->|否| H["保持原样"]
+ G --> I["优化后的循环"]
+ H --> I
+```
+
+---
+
+## 七、内存访问优化与缓存友好性
+
+### 7.1 空间局部性与时间局部性
+
+程序的局部性原理(参见 [[13_存储管理基础]])是内存优化的基础:
+
+- **空间局部性**:访问了某个地址,附近地址很可能也会被访问
+- **时间局部性**:刚访问过的数据很可能再次被访问
+
+### 7.2 按行优先 vs 按列遍历
+
+```c
+#define M 2048
+#define N 2048
+
+// 方式P1:按行遍历(空间局部性好)
+int sumarrayrows(int a[M][N]) {
+ int i, j, sum = 0;
+ for (i = 0; i < M; i++)
+ for (j = 0; j < N; j++)
+ sum += a[i][j]; // 顺序访问内存
+ return sum;
+}
+
+// 方式P2:按列遍历(空间局部性差)
+int sumarraycols(int a[M][N]) {
+ int i, j, sum = 0;
+ for (j = 0; j < N; j++)
+ for (i = 0; i < M; i++)
+ sum += a[i][j]; // 每次跳过 N 个元素
+ return sum;
+}
+```
+
+> [!important] 实测性能差距
+> 在 2GHz Intel Pentium 4 上:
+> - **P1(按行遍历)**:59,393,288 时钟周期
+> - **P2(按列遍历)**:1,277,877,876 时钟周期
+> - P1 比 P2 **快 21.5 倍**!
+
+### 7.3 局部性分析
+
+| 数据 | 按行遍历(P1) | 按列遍历(P2) |
+|------|-------------|-------------|
+| 数组 `a` | 空间局部性好(顺序访问) | 空间局部性差(每次跳2048个单元) |
+| 变量 `sum,i,j` | 时间局部性好(循环中反复访问) | 时间局部性好 |
+| 循环指令 | 空间局部性好 + 时间局部性好 | 空间局部性好 + 时间局部性好 |
+
+### 7.4 缓存友好的代码编写原则
+
+```mermaid
+graph LR
+ A["缓存友好原则"] --> B["按行优先遍历多维数组"]
+ A --> C["使用连续内存布局"]
+ A --> D["减少跨步访问"]
+ A --> E["数据结构对齐缓存行"]
+
+ style A fill:#e8f5e9
+```
+
+---
+
+## 八、函数调用优化
+
+### 8.1 内联函数(Inline)
+
+将函数体直接展开到调用处,消除函数调用开销:
+
+```c
+// 优化前:函数调用有开销
+int square(int x) {
+ return x * x;
+}
+int result = square(5);
+
+// 使用 inline 关键字
+static inline int square_inline(int x) {
+ return x * x;
+}
+int result = square_inline(5); // 编译器可能直接展开为 5*5
+```
+
+> [!note] 编译器的内联决策
+> 即使不使用 `inline` 关键字,`-O2` 及以上优化级别下,编译器也会自动内联小型函数。但大型函数的内联会增加代码体积,可能导致指令缓存不友好。
+
+### 8.2 消除过程调用
+
+如 combine3 的优化所示,将函数调用替换为直接内存访问:
+
+```c
+// 优化前:每次迭代调用函数
+for (i = 0; i < length; i++) {
+ data_t val;
+ get_vec_element(v, i, &val); // 函数调用开销
+ *dest = *dest OP val;
+}
+
+// 优化后:直接指针访问
+data_t *data = get_vec_start(v);
+for (i = 0; i < length; i++) {
+ *dest = *dest OP data[i]; // 直接数组访问
+}
+```
+
+### 8.3 尾调用优化
+
+当函数最后一步是调用另一个函数时,编译器可以复用当前栈帧:
+
+```c
+// 优化前:递归可能导致栈溢出
+int factorial(int n) {
+ if (n <= 1) return 1;
+ return n * factorial(n - 1); // 不是尾调用(乘法在递归之后)
+}
+
+// 尾递归版本
+int factorial_tail(int n, int acc) {
+ if (n <= 1) return acc;
+ return factorial_tail(n - 1, n * acc); // 尾调用
+}
+```
+
+---
+
+## 九、SIMD 与底层优化
+
+### 9.1 SIMD 概念
+
+SIMD(Single Instruction, Multiple Data)允许一条指令同时处理多个数据元素:
+
+```c
+// 标量版本:一次处理一个元素
+for (int i = 0; i < n; i++) {
+ c[i] = a[i] + b[i];
+}
+
+// SSE 向量化(伪代码):一次处理4个float
+// 编译器在 -O3 下可能自动生成 SIMD 指令
+```
+
+### 9.2 编译器自动向量化
+
+```bash
+# 查看编译器是否进行了向量化
+gcc -O3 -ftree-vectorize -fopt-info-vec prog.c
+
+# 明确启用 SIMD 优化
+gcc -O3 -mavx2 prog.c # 使用 AVX2 指令集
+gcc -O3 -msse4.2 prog.c # 使用 SSE4.2 指令集
+```
+
+### 9.3 数据对齐
+
+```c
+// 确保数据对齐以获得最佳 SIMD 性能
+float *a = aligned_alloc(32, n * sizeof(float)); // 32字节对齐(AVX)
+```
+
+---
+
+## 十、优化编译器的局限性
+
+### 10.1 编译器的基本约束
+
+> [!important] 根本约束
+> 编译器在**任何可能的条件下**都不能改变程序的行为。这意味着即使某些行为只在极端情况下发生,编译器也必须保守地保留这些行为。
+
+### 10.2 编译器难以优化的情况
+
+| 情况 | 原因 | 程序员对策 |
+|------|------|-----------|
+| 指针别名 | 编译器不知道两个指针是否指向同一位置 | 使用局部变量累积结果 |
+| 函数副作用 | 编译器不知道函数是否有副作用 | 使用 `inline`、`const`、`pure` 属性 |
+| 数据范围 | 编译器不知道变量的实际取值范围 | 使用更精确的数据类型 |
+| 循环边界 | 编译器不确定循环次数 | 使用常量或 `restrict` 关键字 |
+
+### 10.3 `restrict` 关键字
+
+C99 引入的 `restrict` 告诉编译器指针没有别名:
+
+```c
+// 使用 restrict 告诉编译器 src 和 dest 不重叠
+void copy(int *restrict dest, const int *restrict src, int n) {
+ for (int i = 0; i < n; i++) {
+ dest[i] = src[i];
+ }
+}
+```
+
+---
+
+## 十一、性能测量与 Profiling 工具
+
+### 11.1 常用性能分析工具
+
+| 工具 | 用途 | 命令示例 |
+|------|------|---------|
+| `time` | 测量程序总执行时间 | `time ./prog` |
+| `perf` | Linux 性能计数器分析 | `perf stat ./prog` |
+| `gprof` | GNU 函数级 profiling | `gcc -pg prog.c && ./a.out && gprof` |
+| `valgrind/callgrind` | 缓存和分支预测分析 | `valgrind --tool=callgrind ./prog` |
+| `cachegrind` | 缓存命中率分析 | `valgrind --tool=cachegrind ./prog` |
+
+### 11.2 使用 perf 进行分析
+
+```bash
+# 基本性能统计
+perf stat ./prog
+
+# 详细缓存分析
+perf stat -e cache-references,cache-misses ./prog
+
+# 函数级热点分析
+perf record ./prog
+perf report
+```
+
+### 11.3 使用 gprof 进行分析
+
+```bash
+# 编译时加入 profiling 支持
+gcc -pg -O2 prog.c -o prog
+
+# 运行程序(生成 gmon.out)
+./prog
+
+# 查看分析结果
+gprof prog gmon.out > analysis.txt
+```
+
+### 11.4 优化工作流程
+
+```mermaid
+flowchart TD
+ A["编写正确的代码"] --> B["选择合适的算法"]
+ B --> C["使用 profiling 找到瓶颈"]
+ C --> D{"瓶颈在哪里?"}
+ D -->|循环| E["循环优化:展开、外提、合并"]
+ D -->|内存| F["内存优化:局部性、缓存友好"]
+ D -->|函数调用| G["内联、消除过程调用"]
+ D -->|I/O| H["缓冲、批量处理"]
+ E --> I["测量优化效果"]
+ F --> I
+ G --> I
+ H --> I
+ I --> J{"性能满足要求?"}
+ J -->|否| C
+ J -->|是| K["完成"]
+```
+
+---
+
+## 十二、优化总结
+
+### 12.1 机器无关优化的核心策略
+
+| 优化策略 | 说明 | 对应函数 |
+|---------|------|---------|
+| **代码移动** | 将循环不变量移出循环 | combine1 → combine2 |
+| **消除过程调用** | 用直接内存访问替代函数调用 | combine2 → combine3 |
+| **消除不必要内存引用** | 使用局部变量/寄存器累积 | combine3 → combine4 |
+| **循环展开** | 减少循环控制开销 | psum1 → psum2 |
+
+### 12.2 关键原则
+
+> [!abstract] 代码优化的核心原则
+> 1. **先正确,后优化**:保证代码正确性是前提
+> 2. **度量驱动**:使用 profiling 工具找到真正的瓶颈
+> 3. **算法优先**:$O(n \log n)$ 的算法比 $O(n^2)$ 的优化更重要
+> 4. **利用局部性**:按行访问数组,使用连续内存布局
+> 5. **减少函数调用**:在热循环中避免不必要的函数调用
+> 6. **使用局部变量**:告诉编译器数据没有别名问题
+> 7. **信任编译器**:`-O2` 通常足够,除非有明确的性能需求
+
+---
+
+## 本章术语
+
+| 术语 | 英文 | 说明 |
+|------|------|------|
+| 代码移动 | Code Motion | 将循环不变量计算移到循环外 |
+| CPE | Cycles Per Element | 每元素处理所需的时钟周期数 |
+| 别名 | Aliasing | 两个不同指针指向同一内存位置 |
+| 循环展开 | Loop Unrolling | 复制循环体以减少循环控制开销 |
+| 内联 | Inline | 将函数体展开到调用处 |
+| SIMD | Single Instruction Multiple Data | 单指令多数据并行 |
+| 向量化 | Vectorization | 利用 SIMD 指令并行处理数据 |
+| 代码选择 | Code Selection | 编译器选择合适的机器指令 |
+| 寄存器分配 | Register Allocation | 将变量分配到 CPU 寄存器 |
+| `restrict` | Restrict Qualifier | 告诉编译器指针没有别名 |
+
+---
+
+## 复习与思考
+
+1. 为什么 `combine1` 到 `combine4` 的优化能显著降低 CPE?每一步消除了什么开销?
+2. 当 `xp` 和 `yp` 指向同一地址时,`twiddle1` 和 `twiddle2` 的结果为什么不同?
+3. 为什么按列遍历二维数组比按行遍历慢很多?这与 [[13_存储管理基础]] 中的局部性原理有什么关系?
+4. 编译器为什么不能自动将 `func1` 优化为 `func2`?程序员应该如何帮助编译器?
+5. 在什么情况下应该使用 `-O3` 而不是 `-O2`?有什么潜在的代价?
diff --git a/操作系统/实验/实验01_IO编程.md b/操作系统/实验/实验01_IO编程.md
new file mode 100644
index 0000000..f8f9660
--- /dev/null
+++ b/操作系统/实验/实验01_IO编程.md
@@ -0,0 +1,337 @@
+# 实验01 Linux I/O 编程
+
+## 实验目的
+
+1. 练习 UNIX I/O 函数(`open`、`close`、`read`、`write`、`lseek`)的使用
+2. 掌握标准 I/O 函数(`fgets`、`fread`、`fwrite`)的操作方式
+3. 建立 API 开销的概念,理解系统调用与库函数的性能差异
+4. 熟悉结构体的二进制 I/O 读写方法
+5. 综合运用文件 I/O 完成文本处理任务
+
+## 涉及知识点
+
+- 文件描述符与 `open`/`close`/`read`/`write` 系统调用
+- 标准 I/O:`fopen`/`fclose`/`fgets`/`fprintf`/`fread`/`fwrite`
+- 文件打开模式:`O_RDONLY`、`O_WRONLY`、`O_CREAT`、`O_TRUNC`、`O_APPEND`
+- 结构体与文件 I/O 结合(二进制序列化)
+- `gettimeofday` 高精度计时
+- 字符串处理:`strtok`、`strcmp`、`strstr`、`sscanf`、`%[^:]`
+- 排序算法(词频统计中的字典序排列)
+
+---
+
+## 任务一:task41.c —— 学生信息文件字段处理
+
+### 任务要求
+
+1. 创建文件 `student.txt`,写入若干学生记录,每行格式为 `姓名:学号:学院:年龄:性别`
+2. 从 `student.txt` 中查找所有属于"计算机与网络安全学院"的记录
+3. 将找到的记录字段顺序调整为 `学号:姓名:性别:年龄:学院`
+4. 将调整后的记录写入 `csStudent.txt`
+
+### 关键代码提示
+
+```c
+#include