第十章 输入/输出系统
10.1 I/O系统概述
10.1.1 I/O系统的作用与重要性
输入/输出系统 (I/O Systems) 是操作系统的重要组成部分,负责管理和控制各种I/O设备。计算机的主要工作就是进行I/O操作,操作系统在I/O中的作用是管理和控制I/O设备。
I/O系统的核心目标包括:
- 为应用程序提供统一的设备访问接口
- 隐藏硬件设备的复杂性和差异性
- 提高I/O操作的效率和性能
- 确保I/O操作的可靠性和安全性
10.1.2 I/O设备的多样性和管理挑战
I/O设备在功能和速度上差异巨大,例如:
- 硬盘驱动器 vs. 键盘输入
- 高速网络设备 vs. 串行端口设备
- 块设备 vs. 字符设备
这种多样性要求操作系统使用不同的方法来管理和控制这些设备,这些方法构成了内核的I/O子系统 (I/O Subsystem)。
10.2 I/O硬件
10.2.1 I/O设备分类
I/O硬件设备可以按功能分为以下几类:
1. 存储设备 (Storage Devices)
- 磁盘驱动器、磁带等
- 提供数据的持久存储
2. 传输设备 (Transmission Devices)
- 网络接口卡、调制解调器等
- 负责系统间的数据传输
3. 人机接口设备 (Human-Interface Devices)
- 屏幕、键盘、鼠标等
- 提供用户与计算机的交互界面
4. 专用设备 (Specialized Devices)
- 战斗机操纵杆、航天飞机控制器等
- 用于特定应用场景的专业设备
10.2.2 设备连接方式
计算机系统通过以下方式连接I/O设备:
1. 端口 (Port)
- 连接点,如串行端口、并行端口
- 提供设备与计算机的物理连接
2. 总线 (Bus)
- 线路集合和严格定义的协议
- 支持菊花链或共享直接访问
- 常见类型:PCI、ISA、SCSI总线
3. 设备控制器 (Device Controller)
- 电子器件集合,能够操作端口、总线或设备
- 具体类型:
- 串行端口控制器
- SCSI总线控制器
- IDE控制器
10.2.3 设备控制器和寄存器
设备寄存器类型
I/O控制器通常包含4种类型的寄存器:
状态寄存器 (Status Registers)
- 指示设备当前状态
- 例如:忙位 (busy bit) 表示控制器是否繁忙
控制寄存器 (Control Registers)
- 接收来自主机的命令
- 例如:命令就绪位 (command-ready bit)
数据输入寄存器 (Data-in Registers)
- 从设备读取数据到主机
数据输出寄存器 (Data-out Registers)
- 从主机写入数据到设备
寄存器访问方式
1. 专用I/O指令 (Direct I/O)
- 使用特殊的I/O指令传输数据到I/O端口地址
- 触发总线信号选择合适的设备
- 在设备寄存器中移入或移出数据位
2. 内存映射I/O (Memory-mapped I/O)
- 将设备寄存器映射到内存地址空间
- 使用标准数据传输指令读写寄存器
- 可与专用I/O指令结合使用
10.2.4 I/O数据传输方式
1. 程序化I/O和轮询 (PIO and Polling)
轮询 (Polling) 是一种基本的I/O控制方式,也称为程序化I/O (Programmed I/O, PIO)。
握手协议工作流程(以主机通过端口写输出为例):
- 主机重复读取忙位,直到变为清除状态(忙等待或轮询)
- 主机在控制寄存器中设置写位,并将字节写入数据输出寄存器
- 主机在控制寄存器中设置命令就绪位
- 控制器发现命令就绪位被设置后,设置忙位
- 控制器读取命令寄存器获得写命令,读取数据输出寄存器获得字节,执行设备I/O
- 控制器清除命令就绪位,清除状态寄存器中的错误位(表示设备I/O成功),清除忙位(表示操作完成)
轮询的缺点:
- 当设备很少准备就绪时,轮询效率低下
- 占用大量CPU时间进行等待
2. 中断驱动I/O (Interrupt-Driven I/O)
中断机制 (Interrupt Mechanism) 允许控制器在设备准备就绪时通知CPU,提高了效率。
基本中断处理流程:
- 中断检测:CPU在执行每条指令后检查中断请求线
- 状态保存:CPU保存当前状态并跳转到内存中固定地址的中断处理程序
- 中断处理:
- 确定中断原因
- 执行必要的处理
- 执行状态恢复
- 执行中断返回指令
高级中断功能:
中断请求线类型:
- 不可屏蔽中断线 (Nonmaskable Interrupt):用于不可恢复的内存错误等事件
- 可屏蔽中断线 (Maskable Interrupt):用于设备控制器,可在关键指令执行前被CPU关闭
中断向量 (Interrupt Vector):
- 包含专用中断处理程序的内存地址
- 减少中断处理程序搜索所有可能中断源的需要
中断链接 (Interrupt Chaining):
- 当设备数量超过中断向量元素数量时使用
- 每个向量元素指向中断处理程序链表的头部
- 逐个调用链表中的处理程序,直到找到能够服务请求的处理程序
中断优先级 (Interrupt Priority Level):
- 允许CPU延迟处理低优先级中断而不屏蔽所有中断
- 允许高优先级中断抢占低优先级中断的执行
- 线程化内核架构非常适合实现多重中断优先级
中断的其他用途:
- 异常处理:除零、访问受保护或不存在的地址、执行用户特权指令
- 系统调用:软件中断或陷阱,相对较低的中断优先级
- 页面错误:保存少量处理器状态,然后调用内核中的特权例程
3. 直接内存访问 (DMA)
直接内存访问 (Direct Memory Access, DMA) 是一种高效的数据传输方式,将一些程序化I/O工作卸载给专用的DMA控制器。
DMA工作原理:
DMA初始化:
- CPU将命令块写入内存
- 命令块包含:源指针、目标指针、传输字节数
- CPU将命令块地址写入DMA控制器,然后继续其他工作
DMA传输过程(六步骤):
- 设备驱动程序被调用,获取DMA命令块地址
- 设备驱动程序指示磁盘控制器将数据传输到DMA缓冲区
- 磁盘控制器启动DMA传输
- DMA控制器启动传输
- DMA控制器将每个字节传输到内存
- DMA控制器发送中断信号传输完成
DMA优势:
- 绕过CPU直接在I/O设备和内存间传输数据
- 周期挪用 (Cycle Stealing):DMA控制器占用内存总线时,阻止CPU访问主内存,但提高总体系统性能
10.3 应用程序I/O接口
10.3.1 设备驱动程序的作用
设备驱动程序 (Device Drivers) 是内核I/O结构的重要组成部分,其主要作用包括:
- 为每个设备量身定制,隐藏设备控制器间的差异
- 向内核I/O子系统提供统一的设备访问接口
- 将高级I/O操作转换为具体的硬件操作
10.3.2 I/O设备分类和接口
内核I/O系统提供众多服务,包括I/O调度、缓冲、缓存、假脱机等。为了向应用程序隐藏硬件差异,系统调用将设备行为封装在几个通用类中。
I/O设备特征维度
设备在多个维度上有所不同:
- 字符流 vs. 块设备
- 顺序访问 vs. 随机访问
- 可共享 vs. 专用设备
- 操作速度差异
- 读写、只读或只写访问
标准I/O接口类型
1. 块I/O设备 (Block I/O)
- 包括磁盘驱动器等面向块的设备
- 命令包括:read、write、seek
- 支持原始I/O或文件系统访问
- 可能支持内存映射文件访问
2. 字符流I/O设备 (Character-stream I/O)
- 包括键盘、鼠标、串行端口
- 命令包括:get()、put()
- 上层库提供行编辑功能
3. 网络设备 (Network Devices)
- 与块设备和字符设备差异较大,拥有自己的接口
- Unix和Windows系统包括套接字接口
- 将网络协议与网络操作分离
- 包括select()功能
- 方法多样(管道、FIFO、流、队列、邮箱)
4. 时钟和定时器 (Clocks and Timers)
- 提供当前时间、运行时间、定时器功能
- 可编程间隔定时器用于定时和周期性中断
- ioctl()(在UNIX中)涵盖I/O的特殊方面,如时钟和定时器
10.3.3 阻塞式和非阻塞式I/O
1. 阻塞式I/O (Blocking I/O)
- 特点:进程挂起直到I/O完成
- 优点:易于使用和理解
- 缺点:对某些需求不足
2. 非阻塞式I/O (Nonblocking I/O)
方式一:立即返回
- I/O调用返回当前可用的数据量
- 适用于用户界面、数据复制(缓冲I/O)
- 通过多线程实现
- 快速返回已读或已写的字节数
方式二:异步I/O (Asynchronous I/O)
- 进程在I/O执行时继续运行
- 使用较困难
- I/O子系统在I/O完成时向进程发信号
10.4 内核I/O子系统
内核为I/O提供多种服务,包括I/O调度、缓冲、缓存、假脱机和设备预约、错误处理等。
10.4.1 I/O调度
I/O调度 (I/O Scheduling) 是内核I/O子系统的重要功能:
- 为设备维护I/O请求队列
- 某些操作系统尝试公平性调度(如磁盘I/O)
- 重新排序I/O请求以提高效率
- 考虑设备特性(如磁盘的寻道时间)
10.4.2 缓冲机制
缓冲 (Buffering) 是在设备间传输数据时将数据存储在内存中的机制。
缓冲的目的
应对设备速度不匹配
- 协调不同速度的设备
- 例如:快速CPU与慢速磁盘之间的数据传输
应对设备传输大小不匹配
- 协调不同传输单位的设备
- 例如:网络数据包大小与磁盘块大小不同
维持“复制语义” (Copy Semantics)
- 确保数据一致性
- 应用程序缓冲区的变化不影响已提交的I/O操作
缓冲类型
- 单缓冲:一个缓冲区用于设备和应用程序间的数据传输
- 双缓冲:两个缓冲区交替使用,提高效率
- 环形缓冲:多个缓冲区形成环形队列
10.4.3 缓存机制
缓存 (Caching) 是将数据副本保存在更快存储中的机制。
缓存与缓冲的区别
- 缓冲区:可能保存数据项的唯一现有副本
- 缓存:仅保存存储在其他地方的数据项副本
缓存的作用
- 性能的关键:显著提高I/O性能
- 减少对慢速设备的访问次数
- 利用局部性原理提高数据访问效率
10.4.4 假脱机和设备预约
假脱机 (Spooling)
- 保存设备输出直到设备可以使用
- 适用于一次只能服务一个请求的设备
- 典型应用:打印机队列管理
设备预约 (Device Reservation)
- 提供对设备的独占访问
- 通过系统调用进行分配和取消分配
- 需要注意死锁问题
10.4.5 错误处理
错误处理 (Error Handling) 是I/O子系统的重要功能:
可恢复错误
- 操作系统可以从某些错误中恢复
- 例如:磁盘读取错误、设备不可用、瞬时写入失败
错误报告
- 大多数I/O请求失败时返回错误号或代码
- 系统错误日志保存问题报告
错误处理策略
- 重试机制
- 错误纠正码
- 备用设备使用
10.4.6 I/O保护
I/O保护 (I/O Protection) 确保系统的安全性和稳定性:
保护措施
- 特权指令:所有I/O指令定义为特权指令
- 系统调用:I/O必须通过系统调用执行
- 内存保护:内存映射和I/O端口内存位置必须受到保护
保护目的
- 防止用户进程意外或故意通过非法I/O指令干扰正常操作
- 确保系统资源的合理分配和使用
10.5 I/O请求到硬件操作的转换
10.5.1 从文件名到设备的映射
操作系统如何将应用程序请求连接到硬件的过程以从磁盘读取文件为例:
DOS系统映射过程
对于文件名如 C:\junk.txt
:
- 找到分区信息
- 从FAT中读取起始条目
- 获取文件在磁盘上的位置信息
Unix系统映射过程
对于路径如 /zapp/root/
:
- 从挂载表中找到最长匹配
- 获取
<major, minor>
设备号 - 读取inode获取数据块位置
10.5.2 实际I/O操作的执行过程
系统调用处理流程
- 确定保存文件的设备
- 将文件名转换为设备表示
- 从磁盘物理读取数据到缓冲区
- 使数据对请求进程可用
- 将控制权返回给进程
详细读取过程
当执行 sys_read
系统调用时:
缓存检查:
- 如果数据在缓存中,直接从缓存读取
- 否则,从硬盘读取
I/O请求处理:
- 发出读取请求并将自己添加到队列
- 进程阻塞等待
设备驱动操作:
- 设备驱动程序分配接收数据的缓冲区
- 执行数据传输
中断处理:
- 驱动程序获取数据并中断操作系统
- 进程被解除阻塞
10.6 STREAMS机制
10.6.1 STREAMS架构
STREAMS 是Unix System V中一个有趣的机制,它是用户级进程和设备之间的全双工通信通道 (Full-duplex Communication Channel)。
STREAMS组成
STREAMS包含以下组件:
STREAMS头部 (STREAM Head)
- 与用户进程接口
- 处理用户级的读写操作
驱动程序端 (Driver End)
- 与设备接口
- 处理实际的硬件操作
STREAMS模块 (STREAM Modules)
- 位于头部和驱动程序端之间
- 零个或多个模块
- 每个模块包含读队列和写队列
10.6.2 STREAMS的工作原理
主要特性
动态组装:
- 使应用程序能够动态组装驱动程序代码管道
- 灵活配置I/O处理流程
消息传递:
- 使用消息传递在队列间通信
- 异步处理机制
流控制:
- 使用流控制调节流量
- 防止数据丢失和缓冲区溢出
工作流程
- 用户进程通过STREAMS头部发起I/O请求
- 请求以消息形式在各模块间传递
- 每个模块可以对消息进行处理或转换
- 最终到达驱动程序端执行实际硬件操作
- 响应沿相反路径返回用户进程
10.7 I/O性能
10.7.1 I/O对系统性能的影响
I/O是影响系统性能的主要因素,其影响体现在:
1. CPU负载
- 大量CPU需求:
- 执行设备驱动程序
- 执行内核I/O代码
- 公平高效地调度进程(处理阻塞和解除阻塞)
2. 上下文切换开销
- 中断导致的上下文切换给CPU和硬件缓存带来压力
- 频繁的进程状态转换影响系统效率
3. 内存总线负载
- 数据复制开销:
- 控制器与物理内存间的数据复制
- 内核缓冲区与应用程序数据空间间的数据复制
4. 网络流量压力
网络流量特别容易造成系统压力,因为:
- 数据量大
- 实时性要求高
- 协议处理复杂
10.7.2 性能优化方法
1. 减少上下文切换次数
- 合并多个小的I/O请求
- 使用异步I/O减少进程阻塞
- 批处理I/O操作
2. 减少数据复制次数
- 零拷贝技术:直接在设备和应用程序间传输数据
- 使用内存映射文件
- 减少中间缓冲区的使用
3. 减少中断次数
- 使用大传输:减少小数据传输的频率
- 智能控制器:让控制器处理更多工作
- 轮询方式:在忙等待最小化的情况下使用轮询
4. 使用DMA控制器或通道
- 将简单数据复制工作从CPU卸载
- 减少CPU参与I/O操作的程度
5. 将处理原语移入硬件
- 硬件实现常用操作
- 减少软件处理开销
6. 平衡系统性能
- 平衡CPU、内存、总线和I/O性能
- 获得最高吞吐量
- 避免系统瓶颈
10.7.3 实现层次的选择
在不同层次实现I/O功能有不同的权衡:
1. 应用程序级实现
- 优点:
- 代码灵活
- 应用程序错误不太可能导致系统崩溃
- 避免重启或重新加载设备驱动程序
- 缺点:
- 性能较低
- 功能受限
- 适用:实验性I/O算法的测试
2. 内核(驱动程序)级实现
- 优点:
- 代码运行速度快
- 完全访问系统资源
- 缺点:
- 调试困难
- 错误可能导致系统崩溃
- 适用:生产环境的高性能I/O
3. 控制器或设备(硬件)级实现
- 优点:
- 速度最快
- 减少CPU负载
- 缺点:
- 灵活性最低
- 开发成本高
- 适用:标准化的高频I/O操作