第 6 章 输入/输出系统
《计算机系统:系统架构与操作系统的高度集成》
Umakishore Ramachandran & William D. Leahy Jr. 著
6.1 I/O 系统概述
I/O 系统的组成:
I/O 设备:键盘、鼠标、磁盘、网卡、显示器、打印机...
设备控制器:连接设备和总线的接口电路
总线:连接 CPU、内存和设备控制器
设备驱动程序:操作系统中控制设备的软件
I/O 设备的分类:
字符设备(Character Device):
按字节流传输,不可随机访问
例:键盘、串口、终端
Linux:/dev/ttyS0, /dev/input/event0
块设备(Block Device):
按固定大小的块传输,支持随机访问
例:磁盘、SSD、U盘
Linux:/dev/sda, /dev/nvme0n1
网络设备(Network Device):
通过网络协议传输数据包
例:以太网卡、Wi-Fi 网卡
Linux:eth0, wlan0
I/O 性能指标:
带宽(Bandwidth):每秒传输的数据量(MB/s 或 GB/s)
延迟(Latency):完成一次 I/O 操作的时间(ms 或 μs)
IOPS:每秒 I/O 操作次数(对随机访问重要)
典型设备性能:
设备类型 | 带宽 | 延迟
------------|-------------|--------
HDD | 100~200MB/s| 5~20ms
SATA SSD | 500MB/s | 0.1ms
NVMe SSD | 3~7GB/s | 0.02ms
DDR4 内存 | 25GB/s | 0.1μs
PCIe 4.0 x16| 32GB/s | -
6.2 I/O 接口与设备控制器
设备控制器(Device Controller):
连接设备和系统总线的硬件接口
包含的寄存器:
数据寄存器(Data Register):存储要传输的数据
状态寄存器(Status Register):指示设备当前状态
BUSY:设备正在处理请求
READY:设备就绪,可以接受新请求
ERROR:发生错误
控制寄存器(Control Register):接收 CPU 的命令
CPU 与设备控制器的通信方式:
1. 端口映射 I/O(Port-Mapped I/O,PMIO):
使用专用的 I/O 地址空间(与内存地址空间分离)
使用专用指令(IN/OUT)访问
例:x86 的 I/O 端口(0~65535)
x86 汇编:
IN AL, 0x60 ; 从键盘控制器端口0x60读取数据
OUT 0x43, AL ; 向定时器控制端口0x43写入命令
2. 内存映射 I/O(Memory-Mapped I/O,MMIO):
设备寄存器映射到内存地址空间
使用普通内存访问指令(LOAD/STORE)
例:ARM 处理器的外设寄存器
C语言访问:
volatile uint32_t *uart_status = (uint32_t *)0x44E09014;
volatile uint32_t *uart_data = (uint32_t *)0x44E09000;
优点:可以使用所有内存操作指令(包括Cache操作)
缺点:占用内存地址空间
c
/* 设备控制器访问示例(内存映射I/O)*/
#include <stdio.h>
#include <stdint.h>
/* 模拟UART设备控制器寄存器 */
typedef struct {
volatile uint32_t data; /* 数据寄存器 */
volatile uint32_t status; /* 状态寄存器 */
volatile uint32_t control; /* 控制寄存器 */
volatile uint32_t baud; /* 波特率寄存器 */
} UART_Controller;
/* 状态寄存器位定义 */
#define UART_TX_READY (1 << 5) /* 发送缓冲区为空 */
#define UART_RX_READY (1 << 0) /* 接收数据就绪 */
#define UART_ERROR (1 << 7) /* 错误标志 */
/* 模拟UART控制器(实际应该是硬件地址)*/
static uint32_t uart_regs[4] = {0, UART_TX_READY, 0, 0};
static UART_Controller *uart = (UART_Controller *)uart_regs;
/* 发送一个字节(轮询方式)*/
void uart_send_byte(uint8_t data) {
/* 等待发送缓冲区为空 */
while (!(uart->status & UART_TX_READY)) {
/* 忙等待 */
}
uart->data = data;
printf("[UART发送] 0x%02X ('%c')\n", data, data >= 32 ? data : '?');
}
/* 接收一个字节(轮询方式)*/
uint8_t uart_recv_byte(void) {
/* 等待接收数据就绪 */
while (!(uart->status & UART_RX_READY)) {
/* 忙等待 */
}
return (uint8_t)uart->data;
}
int main() {
printf("=== 设备控制器访问演示 ===\n");
printf("UART状态寄存器: 0x%08X\n", uart->status);
printf("TX_READY位: %d\n", (uart->status & UART_TX_READY) ? 1 : 0);
/* 发送字符串 */
const char *msg = "Hello";
for (int i = 0; msg[i]; i++) {
uart_send_byte(msg[i]);
}
return 0;
}
6.3 I/O 数据传输方式
6.3.1 程序查询方式(轮询)
轮询(Polling)方式:
CPU 主动循环检查设备状态
设备就绪时,CPU 执行数据传输
工作流程:
1. CPU 发出 I/O 命令
2. CPU 循环读取状态寄存器
3. 等待设备就绪(BUSY→READY)
4. CPU 执行数据传输
5. 重复步骤2~4
优点:
实现简单,无需中断机制
适合设备响应极快的场景(如高速网络)
缺点:
CPU 一直在忙等待,无法做其他事情
CPU 利用率低(大量时间浪费在等待上)
不适合慢速设备(如键盘、磁盘)
c
#include <stdio.h>
#include <time.h>
#include <unistd.h>
/* 模拟设备状态 */
volatile int device_ready = 0;
volatile int device_data = 0;
/* 模拟设备(在实际系统中,这是硬件)*/
void simulate_device_operation() {
/* 模拟设备处理延迟 */
usleep(100000); /* 100ms */
device_data = 42;
device_ready = 1;
}
/* 轮询方式读取设备数据 */
int polling_read() {
int poll_count = 0;
/* 忙等待:CPU一直检查设备状态 */
while (!device_ready) {
poll_count++;
/* CPU在这里被浪费了! */
}
int data = device_data;
device_ready = 0;
printf("轮询次数: %d\n", poll_count);
return data;
}
int main() {
printf("=== 轮询方式演示 ===\n");
/* 启动设备操作(模拟)*/
device_ready = 0;
/* 在另一个线程中模拟设备(这里用fork简化)*/
/* 实际中设备是异步工作的 */
printf("等待设备就绪(轮询中)...\n");
/* 模拟:直接调用设备操作 */
simulate_device_operation();
int data = polling_read();
printf("读取到数据: %d\n", data);
return 0;
}
6.3.2 中断驱动方式
中断驱动(Interrupt-Driven)方式:
设备就绪时,主动向 CPU 发送中断信号
CPU 不需要忙等待,可以做其他工作
工作流程:
1. CPU 发出 I/O 命令,继续执行其他任务
2. 设备完成操作后,发送中断信号
3. CPU 暂停当前任务,保存状态
4. CPU 跳转到中断处理程序(ISR)
5. ISR 执行数据传输
6. ISR 返回,CPU 恢复被中断的任务
优点:
CPU 不需要忙等待,可以做其他事情
CPU 利用率高
适合大多数 I/O 设备
缺点:
每次 I/O 都需要中断,中断处理有开销
高频率 I/O 时,中断开销可能成为瓶颈
实现比轮询复杂
c
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
/* 全局缓冲区(中断处理程序和主程序共享)*/
volatile int received_data = 0;
volatile int data_ready = 0;
int work_done = 0; /* CPU做的其他工作量 */
/* 中断处理函数(模拟硬件中断)*/
void interrupt_handler(int signum) {
/* 从设备读取数据(模拟)*/
received_data = 42;
data_ready = 1;
printf("\n[中断] 设备就绪,数据=%d\n", received_data);
}
/* 模拟CPU做其他工作 */
void do_other_work() {
work_done++;
/* 模拟计算工作 */
volatile long sum = 0;
for (int i = 0; i < 1000; i++) sum += i;
}
int main() {
printf("=== 中断驱动方式演示 ===\n");
/* 注册中断处理函数 */
signal(SIGUSR1, interrupt_handler);
printf("发出I/O命令,CPU继续做其他工作...\n");
/* 模拟:3秒后设备发送中断 */
pid_t pid = getpid();
/* 在后台发送中断信号(模拟设备完成)*/
if (fork() == 0) {
sleep(1); /* 模拟设备处理时间 */
kill(pid, SIGUSR1); /* 发送中断信号 */
exit(0);
}
/* CPU做其他工作,等待中断 */
while (!data_ready) {
do_other_work();
usleep(1000); /* 1ms */
}
printf("CPU在等待期间做了 %d 单位工作\n", work_done);
printf("最终读取到数据: %d\n", received_data);
return 0;
}
6.3.3 DMA 方式
DMA(Direct Memory Access,直接内存访问):
DMA 控制器直接在设备和内存之间传输数据
不需要 CPU 参与数据传输过程
工作流程:
1. CPU 配置 DMA 控制器:
- 源地址(设备地址或内存地址)
- 目标地址(内存地址或设备地址)
- 传输长度
- 传输方向(设备→内存 或 内存→设备)
2. DMA 控制器独立完成数据传输
CPU 可以做其他事情
3. 传输完成后,DMA 控制器发送中断通知 CPU
DMA 的优点:
✓ CPU 只需在开始和结束时参与
✓ 适合大块数据传输(磁盘、网络)
✓ 大幅减少 CPU 开销
DMA 的问题:
Cache 一致性:DMA 直接写内存,可能绕过 Cache
解决:DMA 完成后,使相关 Cache 行无效(Invalidate)
DMA 传输模式:
突发模式(Burst Mode):DMA 独占总线,一次传输完所有数据
周期窃取模式(Cycle Stealing):DMA 每次只传输一个字,与CPU交替使用总线
透明模式(Transparent Mode):只在CPU不使用总线时传输
c
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#define BUFFER_SIZE 4096
/* 模拟DMA传输 */
typedef struct {
char *src; /* 源地址(设备缓冲区)*/
char *dst; /* 目标地址(内存缓冲区)*/
int size; /* 传输大小 */
volatile int done; /* 传输完成标志 */
} DMA_Controller;
char device_buffer[BUFFER_SIZE]; /* 设备缓冲区 */
char memory_buffer[BUFFER_SIZE]; /* 内存缓冲区 */
DMA_Controller dma;
/* DMA传输线程(模拟DMA控制器)*/
void *dma_transfer_thread(void *arg) {
DMA_Controller *ctrl = (DMA_Controller *)arg;
printf("[DMA] 开始传输 %d 字节\n", ctrl->size);
usleep(500000); /* 模拟传输延迟 500ms */
/* 实际数据传输(不需要CPU参与)*/
memcpy(ctrl->dst, ctrl->src, ctrl->size);
printf("[DMA] 传输完成,发送中断通知CPU\n");
ctrl->done = 1; /* 发送完成信号(实际是硬件中断)*/
return NULL;
}
int main() {
printf("=== DMA方式演示 ===\n\n");
/* 初始化设备缓冲区 */
memset(device_buffer, 'A', BUFFER_SIZE);
device_buffer[BUFFER_SIZE-1] = '\0';
/* 配置DMA控制器 */
dma.src = device_buffer;
dma.dst = memory_buffer;
dma.size = BUFFER_SIZE;
dma.done = 0;
/* 启动DMA传输 */
pthread_t dma_thread;
pthread_create(&dma_thread, NULL, dma_transfer_thread, &dma);
/* CPU做其他工作(不需要等待DMA)*/
printf("[CPU] DMA传输中,CPU做其他工作...\n");
int cpu_work = 0;
while (!dma.done) {
cpu_work++;
volatile long sum = 0;
for (int i = 0; i < 10000; i++) sum += i;
}
printf("[CPU] DMA完成,CPU做了 %d 单位工作\n", cpu_work);
printf("[CPU] 内存缓冲区前10字节: %.10s\n", memory_buffer);
pthread_join(dma_thread, NULL);
return 0;
}
6.3.4 通道方式
通道(Channel)方式:
更高级的 I/O 处理器,有自己的指令集(通道程序)
可以独立执行复杂的 I/O 操作序列
主要用于大型机(Mainframe)
通道类型:
字节多路通道(Byte Multiplexor Channel):
连接多个低速设备(键盘、打印机)
轮流为每个设备传输一个字节
选择通道(Selector Channel):
连接一个高速设备(磁盘)
一次只为一个设备服务,传输完整数据块
数组多路通道(Block Multiplexor Channel):
连接多个高速设备
轮流为每个设备传输一个数据块
现代等价物:
智能网卡(SmartNIC):有自己的处理器,卸载网络处理
NVMe 控制器:有自己的处理器,管理SSD访问
GPU:有自己的处理器,执行图形/计算任务
6.4 总线结构
6.4.1 总线的基本概念
总线(Bus):
连接计算机各组件的共享通信通道
总线的分类:
数据总线(Data Bus):传输数据(双向)
地址总线(Address Bus):传输地址(单向,CPU→设备)
控制总线(Control Bus):传输控制信号(读/写/中断等)
总线的性能指标:
总线宽度:一次传输的位数(8/16/32/64位)
总线频率:每秒传输次数(MHz)
总线带宽 = 总线宽度 × 总线频率 / 8(字节/秒)
例:PCIe 3.0 x16
每Lane带宽:8Gbps(有效:约1GB/s)
x16带宽:16 × 1GB/s = 16GB/s
总线事务(Bus Transaction):
一次完整的总线操作(读或写)
包括:地址阶段 + 数据阶段
6.4.2 总线仲裁
总线仲裁(Bus Arbitration):
多个设备竞争总线时,决定谁获得使用权
集中式仲裁:
1. 菊花链(Daisy Chain):
优先级固定(靠近仲裁器的设备优先级高)
实现简单,但不公平
2. 集中式并行仲裁:
每个设备有独立的请求线(REQ)和授权线(GNT)
仲裁器根据优先级决定授权哪个设备
更公平,但需要更多信号线
分布式仲裁:
每个设备参与仲裁决策
更公平,但更复杂
例:以太网的 CSMA/CD
总线仲裁算法:
固定优先级:优先级固定,高优先级设备可能饥饿
轮转优先级(Round Robin):轮流给每个设备最高优先级
最近最少使用(LRU):最久未使用的设备获得最高优先级
6.4.3 总线协议
总线协议(Bus Protocol):
规定总线上各设备如何通信的规则
同步总线(Synchronous Bus):
所有操作由统一的时钟信号控制
简单,速度快
缺点:所有设备必须以相同速度工作
异步总线(Asynchronous Bus):
使用握手信号(Handshaking)协调通信
灵活,可以连接不同速度的设备
缺点:握手增加了延迟
握手协议示例:
主设备发出 ReadReq(读请求)
从设备收到后,发出 Ack(确认)
主设备收到 Ack 后,撤销 ReadReq
从设备准备好数据后,发出 DataReady
主设备读取数据,发出 Ack
从设备撤销 DataReady
6.4.4 常见总线标准
常见总线标准:
PCI(Peripheral Component Interconnect):
32位,33MHz,133MB/s(或64位,66MHz,533MB/s)
已被 PCIe 取代
PCIe(PCI Express):
串行差分信号,每条通道(Lane)独立
PCIe 3.0:每Lane 约1GB/s(双向)
PCIe 4.0:每Lane 约2GB/s
PCIe 5.0:每Lane 约4GB/s
配置:x1, x4, x8, x16(Lane数量)
PCIe 4.0 x16:约32GB/s(双向)
USB(Universal Serial Bus):
USB 1.1:12Mbps(全速)
USB 2.0:480Mbps(高速)
USB 3.0:5Gbps(超速)
USB 3.1:10Gbps
USB 4.0:40Gbps(基于Thunderbolt 3)
SATA(Serial ATA):
SATA I:1.5Gbps(实际约150MB/s)
SATA II:3Gbps(实际约300MB/s)
SATA III:6Gbps(实际约600MB/s)
用于连接 HDD 和 SATA SSD
NVMe(Non-Volatile Memory Express):
基于 PCIe,专为 SSD 设计
PCIe 3.0 x4:约3.5GB/s
PCIe 4.0 x4:约7GB/s
延迟:约20μs(比SATA的100μs快5倍)
I2C(Inter-Integrated Circuit):
两线制(SDA+SCL),低速(100kbps~3.4Mbps)
用于连接传感器、EEPROM等低速设备
SPI(Serial Peripheral Interface):
四线制(MOSI+MISO+SCK+CS),速度较快(可达数十MHz)
用于连接Flash、显示屏等设备
6.5 磁盘存储系统
6.5.1 磁盘的物理结构
HDD(Hard Disk Drive)的物理结构:
组成部件:
盘片(Platter):存储数据的磁性圆盘,通常多个
磁头(Read/Write Head):读写数据的装置,每个盘面一个
磁道(Track):盘片上的同心圆,每个盘面数千条
扇区(Sector):磁道上的最小存储单位(512B 或 4KB)
柱面(Cylinder):所有盘片上相同位置的磁道集合
磁盘访问时间:
寻道时间(Seek Time):磁头移动到目标磁道
平均:5~10ms,最大:20ms
旋转延迟(Rotational Latency):等待目标扇区转到磁头下
7200RPM:平均 4.2ms(60s/7200/2)
5400RPM:平均 5.6ms
传输时间(Transfer Time):实际读写数据
通常 < 1ms(取决于数据量和转速)
总访问时间 ≈ 寻道时间 + 旋转延迟 + 传输时间 ≈ 10ms
磁盘容量计算:
容量 = 盘片数 × 每盘面磁道数 × 每磁道扇区数 × 扇区大小
例:2盘片,1000磁道/面,500扇区/磁道,512字节/扇区
容量 = 2 × 2 × 1000 × 500 × 512 = 1,024,000,000 字节 ≈ 1GB
SSD vs HDD 对比:
特性 | HDD | SSD
------------|--------------|-------------
随机访问延迟 | 5~20ms | 0.02~0.1ms
顺序读速度 | 100~200MB/s | 500MB/s~7GB/s
功耗 | 5~15W | 2~5W
抗震性 | 差(机械) | 好(无机械部件)
寿命 | 无限次读写 | 有限次写入(TBW)
价格/GB | 低 | 较高
6.5.2 磁盘调度算法
磁盘调度的目标:
最小化磁头移动距离(减少寻道时间)
提高磁盘吞吐量
常见调度算法:
1. FCFS(先来先服务):
按请求到达顺序处理
公平,但磁头移动距离可能很大
2. SSTF(最短寻道时间优先):
每次选择距当前磁头最近的请求
减少平均寻道时间,但可能导致饥饿
3. SCAN(电梯算法):
磁头向一个方向移动,处理沿途所有请求
到达末端后反向
类似电梯运行方式
4. C-SCAN(循环SCAN):
磁头只向一个方向移动
到达末端后,直接跳回起点(不处理返回途中的请求)
更均匀的等待时间
5. LOOK/C-LOOK:
SCAN/C-SCAN 的改进版
不需要到达磁盘末端,到达最后一个请求就反向
c
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define MAX_REQUESTS 10
/* 计算总寻道距离 */
/* FCFS(先来先服务)*/
int fcfs(int *requests, int n, int head) {
int total = 0, current = head;
printf("FCFS顺序: %d", current);
for (int i = 0; i < n; i++) {
total += abs(requests[i] - current);
current = requests[i];
printf(" → %d", current);
}
printf("\n");
return total;
}
/* SSTF(最短寻道时间优先)*/
int sstf(int *requests, int n, int head) {
int total = 0, current = head;
int visited[MAX_REQUESTS] = {0};
printf("SSTF顺序: %d", current);
for (int i = 0; i < n; i++) {
int min_dist = 99999, min_idx = -1;
for (int j = 0; j < n; j++) {
if (!visited[j] && abs(requests[j] - current) < min_dist) {
min_dist = abs(requests[j] - current);
min_idx = j;
}
}
visited[min_idx] = 1;
total += min_dist;
current = requests[min_idx];
printf(" → %d", current);
}
printf("\n");
return total;
}
/* SCAN(电梯算法)*/
int scan(int *requests, int n, int head, int direction) {
/* direction: 1=向高磁道, -1=向低磁道 */
int total = 0, current = head;
/* 排序请求 */
int sorted[MAX_REQUESTS];
for (int i = 0; i < n; i++) sorted[i] = requests[i];
for (int i = 0; i < n-1; i++)
for (int j = 0; j < n-i-1; j++)
if (sorted[j] > sorted[j+1]) {
int tmp = sorted[j]; sorted[j] = sorted[j+1]; sorted[j+1] = tmp;
}
/* 找到head的位置 */
int pos = 0;
while (pos < n && sorted[pos] < head) pos++;
printf("SCAN顺序: %d", current);
if (direction == 1) {
/* 先向高磁道 */
for (int i = pos; i < n; i++) {
total += abs(sorted[i] - current);
current = sorted[i];
printf(" → %d", current);
}
/* 再向低磁道 */
for (int i = pos-1; i >= 0; i--) {
total += abs(sorted[i] - current);
current = sorted[i];
printf(" → %d", current);
}
}
printf("\n");
return total;
}
/* C-SCAN(循环SCAN)*/
int cscan(int *requests, int n, int head) {
int total = 0, current = head;
int sorted[MAX_REQUESTS];
for (int i = 0; i < n; i++) sorted[i] = requests[i];
for (int i = 0; i < n-1; i++)
for (int j = 0; j < n-i-1; j++)
if (sorted[j] > sorted[j+1]) {
int tmp = sorted[j]; sorted[j] = sorted[j+1]; sorted[j+1] = tmp;
}
int pos = 0;
while (pos < n && sorted[pos] < head) pos++;
printf("C-SCAN顺序: %d", current);
/* 先向高磁道 */
for (int i = pos; i < n; i++) {
total += abs(sorted[i] - current);
current = sorted[i];
printf(" → %d", current);
}
/* 跳回最低磁道(不计入寻道距离,或计入跳转距离)*/
if (pos > 0) {
total += sorted[n-1] + sorted[0]; /* 跳到最低 */
current = sorted[0];
printf(" → [跳转] → %d", current);
for (int i = 1; i < pos; i++) {
total += abs(sorted[i] - current);
current = sorted[i];
printf(" → %d", current);
}
}
printf("\n");
return total;
}
int main() {
int requests[] = {98, 183, 37, 122, 14, 124, 65, 67};
int n = 8, head = 53;
printf("=== 磁盘调度算法比较 ===\n");
printf("初始磁头位置: %d\n", head);
printf("请求队列: ");
for (int i = 0; i < n; i++) printf("%d ", requests[i]);
printf("\n\n");
int r1 = fcfs(requests, n, head);
printf("FCFS总寻道距离: %d\n\n", r1);
int r2 = sstf(requests, n, head);
printf("SSTF总寻道距离: %d\n\n", r2);
int r3 = scan(requests, n, head, 1);
printf("SCAN总寻道距离: %d\n\n", r3);
int r4 = cscan(requests, n, head);
printf("C-SCAN总寻道距离: %d\n\n", r4);
printf("算法比较:\n");
printf(" FCFS: %d\n", r1);
printf(" SSTF: %d(最小,但可能饥饿)\n", r2);
printf(" SCAN: %d\n", r3);
printf(" C-SCAN:%d(更均匀)\n", r4);
return 0;
}
6.5.3 RAID 技术
RAID(Redundant Array of Independent Disks):
将多个磁盘组合成一个逻辑磁盘
提高性能和/或可靠性
RAID 0(条带化,Striping):
数据分散存储在多个磁盘
优点:读写速度提升(N倍)
缺点:任一磁盘故障,数据全部丢失
用途:需要高性能,不需要可靠性(视频编辑)
RAID 1(镜像,Mirroring):
数据同时写入两个磁盘
优点:一个磁盘故障,数据不丢失;读速度提升
缺点:存储利用率50%,写速度无提升
用途:需要高可靠性(数据库日志)
RAID 5(带奇偶校验的条带化):
数据和奇偶校验信息分散在所有磁盘
需要至少3个磁盘
优点:一个磁盘故障可以恢复,存储利用率(N-1)/N
缺点:写操作需要计算奇偶校验,性能略低
用途:平衡性能和可靠性(文件服务器)
RAID 6:
双奇偶校验,可以容忍2个磁盘同时故障
需要至少4个磁盘
RAID 10(RAID 1+0):
先镜像,再条带化
需要至少4个磁盘
优点:高性能 + 高可靠性
缺点:存储利用率50%
用途:数据库服务器
RAID级别对比:
级别 | 最少磁盘 | 利用率 | 容错 | 读性能 | 写性能
------|---------|-----------|------|--------|-------
RAID 0| 2 | 100% | 无 | N倍 | N倍
RAID 1| 2 | 50% | 1块 | 2倍 | 1倍
RAID 5| 3 | (N-1)/N | 1块 | N-1倍 | 略低
RAID 6| 4 | (N-2)/N | 2块 | N-2倍 | 更低
RAID10| 4 | 50% | 1块 | N/2倍 | N/2倍
本章小结
| 知识点 | 核心内容 | 重要程度 |
|---|---|---|
| I/O设备分类 | 字符/块/网络设备 | ⭐⭐⭐⭐ |
| 设备控制器 | 数据/状态/控制寄存器 | ⭐⭐⭐⭐ |
| PMIO vs MMIO | 端口映射vs内存映射 | ⭐⭐⭐⭐ |
| 轮询方式 | CPU忙等待,简单但浪费CPU | ⭐⭐⭐⭐⭐ |
| 中断驱动 | 设备主动通知,CPU利用率高 | ⭐⭐⭐⭐⭐ |
| DMA方式 | 大块数据传输,CPU只参与开始和结束 | ⭐⭐⭐⭐⭐ |
| 总线仲裁 | 菊花链/集中并行/分布式 | ⭐⭐⭐ |
| PCIe/USB | 现代总线标准,速度参数 | ⭐⭐⭐⭐ |
| 磁盘结构 | 盘片/磁头/磁道/扇区,访问时间 | ⭐⭐⭐⭐⭐ |
| 磁盘调度 | FCFS/SSTF/SCAN/C-SCAN | ⭐⭐⭐⭐⭐ |
| RAID技术 | 各级别的特点和适用场景 | ⭐⭐⭐⭐ |
思考题
- 轮询、中断驱动、DMA 三种方式各适合什么场景?
- 为什么 SSTF 算法可能导致某些请求"饥饿"?
- RAID 5 如何从一个磁盘故障中恢复数据?
- 为什么 SSD 不需要磁盘调度算法?
- DMA 传输完成后,为什么需要使 Cache 失效?
参考文献:Umakishore Ramachandran & William D. Leahy Jr.,《计算机系统:系统架构与操作系统的高度集成》