IO设备

IO设备

设备控制器

  1. 控制设备的部件
    • 解析主机发来的命令,控制设备进行操作
  2. 组成
    • 与主机的接口:主机与设备间传递命令、状态、数据
      • 硬件接口:PCIe SATA USB
    • 控制寄存器:1个或多个,控制设备操作
      • 写控制寄存器,命令设备执行指定的操作
      • 读控制寄存器,获得设备的状态
    • 数据缓存区(data buffer)
      • 缓冲数据
      • 使用主机端和设备内的DRAM缓冲数据
      • 如果该缓冲区可用服务读请求,也将其视作数据缓存区

寻址

  1. 控制寄存器和设备数据缓冲区
    • I/O端口:I/O地址空间(Port Mapped IO,PMIO)
      • 端口号:8位或16位的数值
      • I/O专用指令in/out(例如x86):特权指令
      • 控制线:指示CPU发出的地址是内存空间还是I/O空间
      • 内存地址空间和I/O设备地址空间隔离
    • 内存映射I/O(Memory Mapped IO,MMIO)
      • 统一地址空间,内存地址空间预留一部分给I/O设备内存和寄存器
      • 使用常规的访存指令(例如RISC指令集)
      • 内存地址与I/O地址无重叠
      • CPU发出的地址,内存模块和所有设备都要解析
    • 两者可用结合
      • 控制寄存器采用I/O端口寻址
      • 数据缓冲区采用内存映射I/O
    • 好处
      • 编程方便
      • 保护方便 & 灵活:利用虚存的保护机制
      • 高效:访问数据缓冲区不需要专用指令
  2. 内核缓冲区
    • 为什么需要内核缓冲区
      • 生产者(应用)和消费者(IO设备)直接速度不匹配
      • 字符设备、块设备速度较慢,适配不同的数据传输速率
    • 用作数据读缓冲
      • 可以服务对同一数据的读请求
      • 减少实际访问设备的I/O请求

数据传输

  1. 设备数据传输
    • 启动设备 + 数据传输
    • 启动时间(开销)
      • CPU用于启动设备进行操作的时间
    • 带宽
      • 启动设备后数据传输的速率(Bytes/Sec)
    • 延迟
      • 传输1字节的时间(启动时间 + 将1字节传输到目的地的时间)
  2. 通用方法:不同的传输速率
    • 字符设备:对字节流传输的抽象,键盘、串口、打印机等
      • 字节粒度访问:以若干字节为传输粒度,顺序读写
    • 块设备:对块传输的抽象,磁盘、光盘等
      • 块粒度访问:以若干块为传输粒度和存储粒度,按块寻址,整块读写,随机读写

数据传输方式

PIO(Programmed IO)

  1. 例子:RS-232串口
    • 简单的串行控制器
      • 状态寄存器:就绪、忙、...
      • 数据寄存器
  2. 输出数据时
    • CPU:
      • 等待设备状态变为非"忙"
      • 写数据到数据寄存器
      • 通知设备"就绪"
    • 设备:
      • 等待直到状态变为"就绪"
      • 消除"就绪"标志,设置"忙"标志
      • 从数据寄存器中拿走数据
      • 清除"忙"标志
  3. PIO的轮询(Polling)
    • CPU等待直到设备状态变为非"忙"
    • 好处:简单
    • 坏处:慢且浪费CPU资源
    • 改进:使用中断机制可以避免CPU轮询
  4. 支持中断的设备
    • 例如:鼠标
    • 简单的鼠标控制器
      • 状态寄存器(完成、中断、...)
      • 数据寄存器(X、Y、按键)
    • 输入数据时
      • 鼠标
        • 等待直到状态变为"完成"
        • 将相应的值保存到数据寄存器
        • 发中断
      • CPU(中断处理)
        • 清除"完成"标志
        • 将数据寄存器的值读到内核缓冲区(内存)中
        • 置"完成"标志
        • 调用调度器

DMA(Direct Memory Access)

  1. 基于数据寄存器的不足
    • 数据寄存器满后,发送中断请求,CPU进行中断处理
    • 传输大量数据时,中断频繁,需要CPU频繁处理中断
  2. DMA的基本思想
    • 以数据块为单位进行传输,由DMA控制器控制完成外设与主机间的数据传输
    • DMA需要连续的内核缓冲区
    • CPU再数据开始传输前设置DMA控制器
    • 数据传输结束后,DMA控制器发送中断给CPU,CPU再进行处理
    • 减少占用CPU资源
  3. 例子:磁盘
    • 简单的磁盘控制器
      • 状态寄存器(完成、中断、...)
      • DMA内存地址和字节数
      • DMA控制寄存器:命令、设备、传输模式及粒度
      • DMA数据缓冲区
    • DMA写
      • CPU:
        • 等待DMA设备状态为"就绪"
        • 清除"就绪"
        • 设置DMA命令为write,地址和大小
        • 设置"开始"
        • 阻塞当前的线程/进程
      • 磁盘控制器:
        • DMA方式将数据传输到缓冲区(Count--;addr++)
        • 当count==0时,发中断
      • CPU(中断处理)
        • 将被该DMA阻塞的线程/进程加入到就绪队列
      • 磁盘
        • 将数据从缓冲区写入磁盘

同步I/O 和 异步I/O

同步I/O

  1. read()和write()将会阻塞用户进程,直到读写完成
  2. 在一个进程做同步I/O时,OS调度另一个进程执行

异步I/O

  1. aio_read()和aio_write()不阻塞用户进程
  2. 在I/O完成以前,用户进程可以做别的事
  3. I/O完成将通知用户进程

同步/异步读过程

  1. 用户进程P1调用read()系统调用
  2. 系统调用代码检查正确性和缓存
  3. 如果需要进行I/O,会调用设备驱动程序
  4. 设备驱动程序为读数据分配一个buffer,并调度I/O请求
  5. 启动DMA进行读传输
  6. 阻塞当前进程P1,调度另一个就绪的进程P2 // 异步I/O: 直接跳到第12步
  7. 设备控制器进行DMA读传输
  8. 传输完时,设备发送一个中断请求
  9. 中断处理程序唤醒被阻塞的用户进程P1(将P1加入就绪队列
  10. 设备驱动检查结果(是否有错误),返回
  11. 将数据从内核buffer拷贝到用户buffer // 异步I/O: 通知进程P1读操作完成
  12. read系统调用返回到用户程序
  13. 用户进程继续执行

设备驱动

  1. 给操作系统的其它模块提供操作设备的API
  2. 与设备控制器交互
    • 与设备控制器交互以进行数据传输:命令、参数、数据
  3. 主要功能
    • 初始化设备
    • 解析OS发来的命令
    • 多个请求的调度
    • 管理数据传输
    • 接收和处理中断
    • 维护驱动与内核数据结构的完整性

设备驱动的主要流程

  1. 准备工作
    • 参数检查、请求格式转换
    • 设备状态检查:忙 -> 请求入队列
    • 开设备或上电时执行
  2. 操纵设备
    • 将控制命令写入设备的控制寄存器
    • 检查设备状态:就绪 -> 写下一命令
    • 直到设备完成所有命令
  3. 阻塞等待
    • 等待设备完成工作
    • 被中断唤醒
    • 有的设备不需要等待:如显示器
  4. 错误处理
    • 检查设备状态:错误 -> 重试
  5. 返回调用者

设备驱动安装

  1. 静态安装设备驱动
    • 将设备驱动直接编译进内核,系统启动后可以直接调用
    • 新设备的使用需要重启OS
    • 设备驱动修改效率不高,需要重新编译内核
  2. 动态挂载设备驱动
    • 将驱动动态加载进内核空间
    • 不需要重启,而是采用间接指针
      • 设备入口点表:所有设备的入口点
    • 安装入口点,维护相关的数据结构
    • 加载 / 删除设备驱动
      • 分配内核空间 / 删除内核空间
      • 存储驱动代码 / 删除驱动代码
      • 与入口点关联 / 删除入口点

设备驱动的利与弊

  1. 灵活性:
    • 用户可以下载和安装设备驱动
    • 灵活接入不同硬件设备
  2. 安全隐患
    • 设备驱动运行于内核态
    • 有bug的设备驱动会导致内核崩溃,或者引入安全漏洞
  3. 如何让设备驱动更安全
    • 检查设备驱动的代码
    • 为设备驱动构建状态机模型进行检查
  4. 用户态驱动:面向高速设备,提升性能