Lecture 18: 磁盘(Magnetic Disks)

目录 · ← l13 · l15 →

Lecture 18: 磁盘(Magnetic Disks)

概述

文件系统建立在磁盘之上,而磁盘是”高延迟、顺序访问快”的设备。本讲介绍硬盘(HDD)的物理结构、一次读写的时间构成(寻道 + 旋转延迟 + 传输),以及操作系统与 I/O 设备的交互方式:内存映射 I/O、设备寄存器、轮询 vs 中断、PIO vs DMA,最后是现代的高效设备接口(命令队列/响应队列 + 门铃)。

核心概念与系统机制图解

磁盘物理结构

           ┌──────────────────────────┐
           │      盘片(Platters) 1-10   │   以 5000–15000 RPM 旋转
           │  ╭─── 磁道(Track) ───╮   │
           │  │  ╭──────────────╮ │   │   磁道 = 磁头固定位置扫过的圆环
           │  │  │ 扇区(Sector)  │ │   │   扇区 = 磁道上的弧段(4096字节)
           │  │  ╰──────────────╯ │   │   典型容量 500GB – 30TB+
           │  ╰───────────────────╯   │
           └──────────────────────────┘
   磁头(Head)悬在盘面上方 3–10nm 处"飞行"(空气轴承), 由磁臂(Actuator Arm)移动

一次磁盘读写的时间构成

1. 寻道(Seek): 磁臂移动到目标磁道        典型 3–10 ms
2. 选择磁头
3. 旋转延迟(Rotational Latency): 等目标扇区转到磁头下  平均半圈 ≈ 4ms @7500RPM
4. 传输(Transfer): 扇区在磁头下掠过时读写            150–280 MB/s
────────────────────────────────────────────────────────
   延迟(Latency) = 寻道 + 旋转延迟 ≈ 5–15 ms
  • 磁盘被抽象为线性块数组read(startSector, count, addr) / write(...);磁道/扇区几何结构被设备隐藏(坏扇区自动重映射)。

与设备通信:内存映射 I/O(MMIO)

  • CPU 把 I/O 设备的设备寄存器映射到物理地址空间:对特定地址做 load/store 就是读写设备寄存器(无缓存 load/store)。
  • 设备寄存器位有三类用途:
    • 参数(CPU 写):如起始扇区号;
    • 状态(CPU 读):如”操作完成”“出错”;
    • 控制(CPU 写):如”开始磁盘读”。
  • 注意:设备寄存器不像普通内存——”开始”位可能永远读为 0,状态位可能被设备自行改变。

设备完成信号:轮询 vs 中断

轮询(Polling):           中断(Interrupt):
OS 自旋读 ready 位        设备完成时强制 CPU 陷入(trap) OS
浪费 CPU 时间              CPU 在设备工作时可做别的
  • 中断处理流程:设备发中断 → CPU 跳到中断向量(与系统调用、缺页共用)→ OS 识别设备、服务(确认中断、启动新操作)→ 返回陷阱,恢复执行。
  • 中断让 OS 高效:可同时让多个设备忙碌并运行用户代码;多核机器分散中断负载。

数据传输:PIO vs DMA

| 方式 | 机制 | 评价 | | :— | :— | :— | | PIO(Programmed I/O) | CPU 亲自 load/store 搬运数据 | 简单但浪费 CPU | | DMA(Direct Memory Access) | 设备直接把数据搬到物理内存 | 现代主流:CPU 只写”缓冲地址”到设备寄存器 |

现代设备接口(DMA + 门铃)

OS                                   设备
┌──────────────────┐    DMA 读命令    ┌──────────────────┐
│ 命令队列 Command  │ ◄─────────────── │                  │
│    [读扇区32→地址X]│                  │  执行: DMA 写数据  │
│ 响应队列 Response │ ───────────────► │  到地址X          │
└──────────────────┘   中断通知        └──────────────────┘
    OS 写"门铃(doorbell)"地址(uncached store) 通知设备有新命令
    设备 DMA 读命令 → DMA 传数据 → DMA 写完成响应 → 发中断

代码示例与系统调用解说

示例:一次磁盘读取的系统调用视角(read)

#include <fcntl.h>
#include <unistd.h>
#include <cstdio>

int main() {
    int fd = open("/dev/sda", O_RDONLY);       // 打开块设备
    if (fd < 0) { perror("open"); return 1; }

    char buf[4096];                            // 一页/一个扇区组
    ssize_t n = pread(fd, buf, sizeof(buf),     // 从扇区偏移处读
                      0x100000);               // 字节偏移 = 扇区号 × 512
    if (n < 0) perror("pread");
    close(fd);
    return 0;
}

【代码做了什么?】 直接以字节偏移读取块设备,由内核把”偏移 → 扇区号”并驱动磁盘完成传输。

【系统机制透视】

  • 应用发出 pread 系统调用 → 陷入内核 → 文件系统/块层把字节偏移换算成扇区号 → 内核构建设备命令(写入命令队列 + 门铃)→ 设备 DMA 把数据搬到内核缓冲 → DMA 完成 → 设备中断 → 内核把数据拷贝到用户缓冲 → 系统调用返回。
  • 这一路上:PIO 只用于极小的控制交互(门铃),数据搬运全靠 DMA;中断把”等待 I/O”的进程置为 BLOCKED,让 CPU 去跑别的线程——这与 Lecture 2–3 的线程状态机完美衔接。

关键要点

  1. 磁盘延迟 = 寻道(3–10ms)+ 旋转延迟(~4ms)+ 传输(150–280MB/s);顺序访问远比随机访问快。
  2. 磁盘向软件暴露线性块数组;几何细节(内外磁道扇区数、坏块重映射)被设备隐藏。
  3. 设备通过内存映射寄存器交互:参数/状态/控制三类位。
  4. 轮询浪费 CPU,中断让 OS 高效并发处理多个设备;DMA 让设备直接搬数据,CPU 只发命令。
  5. 现代接口 = 命令/响应队列 + 门铃 + DMA + 中断。

常见陷阱与注意事项

  • 把磁盘访问想成”即时”:一次随机读 5–15ms ≈ 数百万个 CPU 周期。
  • 轮询密集设备:大量设备都轮询会让 CPU 空转。
  • 忘记设备寄存器与普通内存的区别:读”开始位”永远 0、状态位会自发改变。
  • PIO 搬大块数据:浪费 CPU;大传输必须 DMA。
  • 忽视寻道代价:随机小块 I/O 是磁盘性能杀手(调度算法见 Lecture 20)。

思考题

  1. 问题:为什么现代磁盘把”线性块数组”暴露给软件而不是磁道/扇区结构?
    • 答案:隐藏物理几何(内外磁道扇区数不同、坏块重映射、预留空间)使软件接口简单稳定;设备内部自行管理几何细节,可随技术演进而不改变接口。
  2. 问题:DMA 相比 PIO 节省了什么?
    • 答案:PIO 中 CPU 要逐字搬运数据(占用 CPU 时间);DMA 中设备直接访问内存,CPU 只需设置缓冲地址与发起命令,可以转而执行其它线程——吞吐量与 CPU 利用率都大幅提升。
  3. 问题:为什么”等待 I/O 完成”时进程进入阻塞而非忙等?
    • 答案:磁盘延迟毫秒级,忙等会让 CPU 空转数毫秒;阻塞后调度器把 CPU 交给其它就绪线程,I/O 完成时中断唤醒该进程——资源利用率与响应时间都更优。