第 6 章 输入/输出系统

第 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技术 各级别的特点和适用场景 ⭐⭐⭐⭐

思考题

  1. 轮询、中断驱动、DMA 三种方式各适合什么场景?
  2. 为什么 SSTF 算法可能导致某些请求"饥饿"?
  3. RAID 5 如何从一个磁盘故障中恢复数据?
  4. 为什么 SSD 不需要磁盘调度算法?
  5. DMA 传输完成后,为什么需要使 Cache 失效?

参考文献:Umakishore Ramachandran & William D. Leahy Jr.,《计算机系统:系统架构与操作系统的高度集成》