Files
obsidian/操作系统/17_IO系统/17_IO系统.md

938 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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软件<br/>(库函数: printf, scanf)"]
end
subgraph 内核空间
B["设备独立性软件<br/>(统一接口、缓冲、分配)"]
C["设备驱动程序<br/>(控制具体设备)"]
D["中断处理程序<br/>(响应设备中断)"]
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**
- CCPU处理一块数据的时间
- 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<br/>距离: 640"] -->|"改进"| B["SSTF<br/>距离: 236"]
B -->|"解决饥饿"| C["SCAN<br/>距离: 299"]
C -->|"优化尽头"| D["LOOK<br/>距离: 299"]
C -->|"解决不均匀"| E["C-SCAN<br/>距离: 350"]
E -->|"优化尽头"| F["C-LOOK<br/>距离: 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["文件系统<br/>(参考 [[04_文件IO编程]])"]
B --> D["存储管理<br/>(参考 [[12_存储管理]])"]
B --> E["进程管理<br/>(参考 [[01_系统运行机制]])"]
C --> F["磁盘管理<br/>(参考 [[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_磁盘空间管理]]