Lecture 18: 磁盘(Magnetic Disks)
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 的线程状态机完美衔接。
关键要点
- 磁盘延迟 = 寻道(3–10ms)+ 旋转延迟(~4ms)+ 传输(150–280MB/s);顺序访问远比随机访问快。
- 磁盘向软件暴露线性块数组;几何细节(内外磁道扇区数、坏块重映射)被设备隐藏。
- 设备通过内存映射寄存器交互:参数/状态/控制三类位。
- 轮询浪费 CPU,中断让 OS 高效并发处理多个设备;DMA 让设备直接搬数据,CPU 只发命令。
- 现代接口 = 命令/响应队列 + 门铃 + DMA + 中断。
常见陷阱与注意事项
- 把磁盘访问想成”即时”:一次随机读 5–15ms ≈ 数百万个 CPU 周期。
- 轮询密集设备:大量设备都轮询会让 CPU 空转。
- 忘记设备寄存器与普通内存的区别:读”开始位”永远 0、状态位会自发改变。
- PIO 搬大块数据:浪费 CPU;大传输必须 DMA。
- 忽视寻道代价:随机小块 I/O 是磁盘性能杀手(调度算法见 Lecture 20)。
思考题
- 问题:为什么现代磁盘把”线性块数组”暴露给软件而不是磁道/扇区结构?
- 答案:隐藏物理几何(内外磁道扇区数不同、坏块重映射、预留空间)使软件接口简单稳定;设备内部自行管理几何细节,可随技术演进而不改变接口。
- 问题:DMA 相比 PIO 节省了什么?
- 答案:PIO 中 CPU 要逐字搬运数据(占用 CPU 时间);DMA 中设备直接访问内存,CPU 只需设置缓冲地址与发起命令,可以转而执行其它线程——吞吐量与 CPU 利用率都大幅提升。
- 问题:为什么”等待 I/O 完成”时进程进入阻塞而非忙等?
- 答案:磁盘延迟毫秒级,忙等会让 CPU 空转数毫秒;阻塞后调度器把 CPU 交给其它就绪线程,I/O 完成时中断唤醒该进程——资源利用率与响应时间都更优。
