操作系统-输入输出系统
7.1 I/O 系统的功能、模型与接口
I/O 系统的任务并不只是“把数据读进来、写出去”这么简单。它实际上负责把复杂而异构的硬件设备包装成上层程序可统一使用的接口,让 CPU、内存和外设之间的协作尽量高效、尽量可靠。
I/O 系统的基本功能
- 隐藏 I/O 设备细节:用户和应用程序不需要了解设备的具体物理特性
- 保证设备无关性:上层程序不依赖某一台特定设备
- 提高处理机和 I/O 设备利用率:尽量让 CPU 与外设并行工作
- 控制 I/O 设备:驱动设备完成实际数据传输
- 确保设备共享正确进行:协调多个进程对同一设备的访问
- 处理 I/O 错误:检测并处理传输过程中的异常情况
I/O 系统的层次结构与模型
I/O 系统的层次结构
为了把复杂性分层隔离,I/O 软件通常自上而下组织为四层:
- 用户层软件:提供面向用户的接口
- 与设备无关的 I/O 软件:负责设备命名、设备保护、设备分配与回收、缓冲管理等统一功能
- 设备驱动程序:把抽象 I/O 命令翻译为具体设备操作
- 中断处理程序:处理 I/O 完成后的中断信号
这种分层的价值在于:越靠上的层越关心“功能”,越靠下的层越关心“硬件细节”。这样既方便统一管理,也方便替换具体设备。
I/O 系统的模型
- 上接口:I/O 系统对上层软件提供统一的 I/O 命令接口
- 下接口:软件与硬件之间的接口,负责和具体设备交互
- I/O 系统本体:通常由中断处理程序、设备驱动程序、与设备无关的 I/O 软件三部分构成
I/O 系统的接口
不同类型设备在访问方式上差异很大,因此 I/O 系统通常提供不同风格的接口来适配。
块设备接口
- 块设备以数据块为单位进行存储和传输,例如磁盘
- 隐藏磁盘的二维结构:磁盘物理寻址常表现为柱面、磁头、扇区等二维或多维结构,但对上层通常抽象成线性地址
- 抽象命令到低层操作的映射:例如
read/write最终会被解释为具体的磁盘读写操作
字符设备接口
- 字符设备以字符为单位进行输入输出,例如键盘
- get / put 操作:分别表示从设备取字符和向设备送字符
- 字符缓冲区:用来暂存与排队字符数据
- 控制类操作:例如设置波特率、数据位等参数
网络通信接口
- 用于支持网络通信场景下的 I/O 访问
7.2 I/O 设备和设备控制器
如果说 I/O 系统是软件层面的管理体系,那么 I/O 设备和设备控制器就是它在硬件层面的落点。理解这两者的关系,是后面理解中断、DMA、通道的基础。
I/O 设备
I/O 设备的类型
按使用特性分类:
- 人机交互设备:键盘、显示器、打印机等,用于人与计算机之间交换信息
- 存储设备:磁盘、光盘等,用于保存程序和数据
- 网络通信设备:网卡、调制解调器等,用于计算机之间传输数据
按传输速率分类:
- 低速设备:键盘、鼠标等,传输速率通常是每秒几个到几百个字节
- 中速设备:激光打印机等,传输速率通常是每秒数千个字节
- 高速设备:磁盘、光盘等,传输速率通常是每秒数万个字节
设备与设备控制器之间的接口
设备和设备控制器之间通常通过三类信号线连接:
- 数据信号线:传输数据信号
- 控制信号线:发送控制命令
- 状态信号线:反馈设备当前状态
设备控制器
设备控制器可以看作 CPU 与 I/O 设备之间的中介。CPU 不直接操纵复杂的外设细节,而是把命令交给控制器,由控制器负责具体执行。
设备控制器的基本功能
- 接收和识别命令:识别来自 CPU 的
read、write等命令 - 数据交换:在设备和主机之间完成数据传输
- 标志并报告设备状态:向 CPU 报告忙、空闲、出错等状态
- 地址识别:判断当前 CPU 要访问的是哪台设备
- 设置缓冲区:缓和主机与设备速度不匹配的问题
- 差错控制:检测并纠正数据传输过程中的错误
设备控制器的组成
- 设备控制器与 CPU 的接口
- 设备控制器与设备的接口
- I/O 逻辑
这三部分分别对应“接命令”“连设备”“做执行”三个层面。
内存映像 I/O
CPU 访问 I/O 设备有两种常见方式:
- 专用 I/O 指令方式:使用专门的 I/O 指令访问设备,I/O 地址空间与内存地址空间分离
- 内存映像 I/O 方式:把 I/O 寄存器映射到内存地址空间中,CPU 像访问内存一样访问设备
后者的好处是统一,前者的好处是边界更清晰。考试里常问这两者的区别。
I/O 通道
I/O 通道的引入
如果所有 I/O 管理工作都由 CPU 亲自处理,那么 CPU 很容易被大量低层细节拖住。为此系统引入了通道,它是一种专门负责 I/O 操作的处理器,可以独立执行通道程序并控制设备进行数据传输。
通道类型
- 字节多路通道:按字节交叉方式工作,适合连接多台低速设备,不适合高速设备
- 数组选择通道:能连接多台高速设备,但某时刻通常只能服务一台设备,利用率偏低
- 数组多路通道:结合前两者优点,既能连接多台设备,又能较好支持并行
瓶颈问题
当通道数不足时,会出现 I/O 瓶颈。解决思路不是一味增加通道,而是增加设备到主机间的通路而不增加通道,例如让一个设备连接多个控制器,一个控制器再连接多个通道。
I/O 设备的控制方式
I/O 控制方式体现了 CPU 参与程度的不断下降,也是非常典型的层层演进考点。
轮询的可编程 I/O 方式
CPU 不断询问设备“好了没有”,绝大多数时间都花在等待上,利用率很低。
中断驱动方式
CPU 发出 I/O 命令后继续执行其他任务,等设备完成后再通过中断通知 CPU。这样 CPU 和 I/O 设备就能并行工作,效率明显提升。
DMA 方式
DMA 的关键改进是:数据不再经过 CPU 中转,而是由设备直接与内存交换数据。
- 数据传输的基本单位是数据块
- 数据由 I/O 设备直接送入内存,或由内存直接送到 I/O 设备
- 只有在传输开始和结束时才需要 CPU 干预
DMA 控制器通常由三部分组成:
- 主机与 DMA 控制器的接口
- DMA 控制器与块设备的接口
- I/O 控制逻辑
DMA 控制器中常见的寄存器包括:
- 命令寄存器 CR
- 内存地址寄存器 MAR
- 数据寄存器 DR
- 数据计数器 DC
I/O 通道方式
I/O 通道方式是在 DMA 之上的进一步发展。DMA 把 CPU 的干预从“逐字节”降到了“逐数据块”,而通道方式则进一步把干预降低为“按一组数据块及其控制管理”为单位。
通道程序由一系列通道指令组成,每条指令通常包含:
- 操作码
- 内存地址
- 计数
- 通道程序结束标志 P
- 记录结束标志 R
7.3 中断和中断处理程序
中断是 I/O 系统实现并行工作的关键机制。没有中断,CPU 就只能傻等设备;有了中断,CPU 才能先去做别的事,等设备完成后再回来处理结果。
中断简介
中断和陷入
- 中断:又称外中断或硬中断,由外部设备引起
- 陷入:又称内中断、软中断或陷阱,来源于 CPU 内部事件
中断向量表和中断优先级
中断向量表的作用是把“中断号”和“处理程序入口地址”对应起来:
- 每个设备中断请求都有一个中断号
- 每个中断号在中断向量表中对应一个表项
- 表项中保存相应中断处理程序的入口地址
中断优先级的意义在于:不同中断源的紧急程度不同,系统必须决定谁先被处理。
常见处理方式有两种:
- 屏蔽中断:处理一个中断时暂时禁止其他中断
- 嵌套中断:允许高优先级中断打断低优先级中断
前者实现简单,后者响应性更强,但实现也更复杂。
中断处理程序
中断处理的一般过程可以概括为五步:
- 检测中断请求:判断是否有未响应的中断
- 保护现场:保存当前进程的 CPU 现场
- 转入对应的中断处理程序:根据中断向量表定位入口地址
- 处理中断:完成数据传输、状态更新等任务
- 恢复现场并返回:恢复寄存器和执行环境,继续原进程
Linux 系统中的中断处理
注册中断处理程序
- 设备驱动程序通过
request_irq()向系统注册中断处理程序 - 注册时通常需要提供中断号、中断处理函数、中断标志、设备名等信息
中断处理
Linux 为了缩短中断停留时间,通常把处理拆成两部分:
- 上半部:先处理最紧急的事务
- 下半部:把不紧急的工作延后处理,可通过软中断、tasklet、工作队列等机制完成
中断注销
- 驱动程序卸载时通过
free_irq()注销中断处理程序
7.4 设备驱动程序
设备驱动程序是 I/O 系统里最贴近硬件的一层。上层只关心“我要读数据”“我要写数据”,驱动程序则必须真的知道该怎么操作寄存器和控制器。
设备驱动程序概述
设备驱动程序的功能
- 接收:接收来自上层的抽象 I/O 请求
- 检查:检查请求是否合法、参数是否有效
- 发出:把抽象命令翻译成具体的寄存器操作
- 响应:处理中断与设备错误
设备驱动程序的特点
- 设备驱动程序属于低级系统程序,与普通应用程序有明显区别
设备处理方式
- 为每类设备设置一个进程
- 为整个系统设置统一的 I/O 进程
- 不设置专门设备处理进程,只保留驱动程序供用户或系统进程调用
设备驱动程序的执行过程
- 把抽象要求转化为具体要求
- 校验服务请求
- 检查设备状态
- 传送必要参数
- 启动 I/O 设备
这个顺序本身就是常考流程题。
设备驱动程序的框架
设备驱动程序与外界的接口
- 与 OS 内核的接口:通过统一操作函数集向内核注册
- 与系统引导的接口:支持系统启动时自动加载与初始化
- 与设备的接口:直接操作设备控制器寄存器
设备驱动程序的组成
- 注册与注销
- 设备打开与释放
- 设备读写
- 设备控制
- 中断与轮询处理
7.5 与设备无关的 I/O 软件
前面的驱动程序已经很靠近硬件了,但上层程序并不希望面对这么多设备差异。于是系统在驱动之上又加了一层“与设备无关的软件”,专门负责把这些差异统一起来。
与设备无关软件的基本概念
- 通过物理设备名使用设备:早期系统里用户直接指定具体设备
- 引入逻辑设备名:为同类设备提供统一名称,使用户不依赖具体硬件
- 逻辑设备名到物理设备名的转换:通过逻辑设备表完成映射,实现设备无关性
与设备无关软件的共有操作
- 提供统一驱动接口:向上层提供统一 I/O 接口
- 缓冲管理:协调 I/O 缓冲区,缓和设备和 CPU 之间的速度矛盾
- 差错控制:处理暂时性错误和持久性错误
- 独占设备的分配与回收
- 提供逻辑数据块:屏蔽不同设备物理块大小的差异
设备分配与回收
设备分配中的数据结构
- 设备控制表 DCT:每个设备一张
- 控制器控制表 COCT:每个控制器一张
- 通道控制表 CHCT:每个通道一张
- 系统设备表 SDT:整个系统一张
设备分配时应考虑的因素
- 设备固有属性:独占、共享、虚拟设备的策略不同
- 设备分配算法:如先来先服务、优先级高者优先
- 安全性:是否可能引发死锁
独占设备的分配程序
基本流程是:
- 先分配设备
- 再分配控制器
- 最后分配通道
更具体地说,就是从 SDT 找到 DCT,再由 DCT 找到 COCT,再由 COCT 找到 CHCT,逐层检查空闲状态。
常见改进包括:
- 增加安全性检查,避免死锁
- 引入设备请求队列,提高分配效率
将逻辑设备名映射到物理设备名
逻辑设备表
每个表项通常包含三部分:
- 逻辑设备名
- 物理设备名
- 驱动程序入口地址
逻辑设备表的设置方式
- 全系统共用一张表:实现简单,但灵活性差
- 每个用户一张表:各用户独立维护自己的映射关系
I/O 调度
- I/O 调度能改善系统整体性能
- 能在进程间更公平地共享设备
- 能减少完成 I/O 操作的平均等待时间
7.6 用户层的 I/O 软件
从用户视角看,I/O 最终表现为系统调用和库函数。也就是说,应用程序通常不会直接接触驱动程序,而是通过更高层的接口完成输入输出。
系统调用与库函数
系统调用
- 系统调用:用户程序请求操作系统服务的接口
- I/O 相关系统调用通常有
open、read、write、close - 用户程序不能直接操作硬件,必须进入内核态后由操作系统完成设备管理
库函数
- 库函数:建立在系统调用之上的更方便的封装接口
- 它既可能直接调用系统调用,也可能先在用户态完成一部分缓冲与格式处理
- 标准输入输出函数本质上最终仍依赖系统调用完成 I/O
系统调用与库函数的关系
- 系统调用是操作系统提供的接口
- 库函数是给程序员使用的更友好接口
- 两者不等价,但很多库函数最终会间接调用系统调用
假脱机技术与假脱机系统
假脱机技术是这一章的一个典型重点,因为它非常鲜明地体现了“把独占设备共享化、把低速设备虚拟化”的思路。
假脱机技术
- 假脱机技术 SPOOLing:在联机情况下同时完成外围设备操作的一种技术
- 核心思想是利用磁盘作为中介,把低速独占设备改造成可排队、可共享的形式
- 本质上是用空间换时间
假脱机系统的组成
输入井和输出井:
- 输入井:暂存输入数据的磁盘区域
- 输出井:暂存输出数据的磁盘区域
输入缓冲区和输出缓冲区:
- 输入缓冲区:先暂存输入设备送来的数据,再批量写入输入井
- 输出缓冲区:先暂存待输出数据,再批量送入输出井或设备
输入进程和输出进程:
- 输入进程:负责控制输入设备并写入输入井
- 输出进程:负责从输出井取出作业并送往输出设备
井管理程序:
- 井管理程序:统一管理输入井和输出井中的作业存放、调度和回收
假脱机系统的特点
- 提高 I/O 速度
- 把独占设备改造成共享设备
- 实现虚拟设备功能
假脱机打印机系统
最典型的例子就是打印机:
- 多个进程先把打印结果写入输出井
- 再由输出进程按顺序交给打印机
- 用户提交请求后即可继续执行,不必一直等待打印机空闲
守护进程
- 守护进程:在后台持续运行、负责某类管理任务的进程
- 在假脱机打印系统中,它负责检查并处理输出井中的打印任务
7.7 缓冲区管理
缓冲机制几乎是 I/O 系统里最常见的性能优化手段。因为 CPU 和 I/O 设备速度差距太大,如果没有缓冲,CPU 不是等设备,就是设备等 CPU。
缓冲的引入
- 缓和速度矛盾
- 减少中断频率
- 放宽中断响应时间
- 解决数据粒度不一致问题
- 提高并行性
单缓冲区和双缓冲区
- 单缓冲区:处理时间约为
max(C, T) + M - 双缓冲区:处理时间约为
max(C + M, T)
其中:
C表示 CPU 处理时间T表示设备传输时间M表示数据移动时间
双缓冲的优势就在于 CPU 与 I/O 可以轮换使用两个缓冲区,从而提高重叠程度。
环形缓冲区
- 组成:多个缓冲区首尾相连形成环
- 关键指针:
Nexti指向空缓冲区,Nextg指向满缓冲区,Current指向当前使用缓冲区 - 操作:
Getbuf获取满缓冲区,Releasebuf释放为空缓冲区
同步问题也很典型:
Nexti追上Nextg:说明输入太快,缓冲区全满,输入进程需要等待Nextg追上Nexti:说明处理太快,缓冲区全空,处理进程需要等待
缓冲池
- 组成:空白队列
emq、输入队列inq、输出队列outq - 操作:
Getbuf取缓冲区,Putbuf放回缓冲区
四种常见工作方式:
- 收容输入
- 提取输入
- 收容输出
- 提取输出
更具体地说,就是输入进程、输出进程和计算进程围绕这些队列来回取放缓冲区,以实现解耦与并行。
7.8 磁盘性能概述和磁盘调度
磁盘调度是 I/O 系统里另一个非常高频的考点。它的核心不是“谁先来谁先做”这么简单,而是如何减少磁头移动时间,从而提高整体访问效率。
磁盘性能概述
- 数据组织与格式:磁盘由盘面、磁道、扇区组成,通常按柱面、磁头、扇区顺序编址
- 磁盘类型:
- 固定头磁盘:每个磁道一个磁头,无需移动,速度快但成本高
- 移动头磁盘:一个盘面通常只有一个磁头,需要移动寻道,更常见
- 磁盘访问时间:
- 寻道时间
Ts - 平均旋转延迟时间
Tr - 传输时间
Tt - 总访问时间约为
Ts + Tr + Tt
- 寻道时间
早期的磁盘调度算法
- FCFS:先来先服务,公平但平均寻道时间较长
- SSTF:最短寻道时间优先,优先服务离当前磁头最近的请求,但可能让远处请求长期挨饿
基于扫描的磁盘调度算法
- SCAN:电梯算法,磁头沿一个方向移动并处理沿途请求,到端点后反向
- CSCAN:循环扫描,只沿一个方向服务,到末端后直接回起点,等待时间更均匀
- NStepSCAN:把请求队列分成多个子队列,逐个用 SCAN 处理
- FSCAN:维护两个队列,当前扫描一个,新请求进入另一个,处理完再切换