# 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**
- 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<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_磁盘空间管理]]
