Chapter5 IO
原创大约 9 分钟
I/O 硬件
I/O 设备
- 块设备:把信息存储在定长的块中,每个块都有自己的地址。块的大小从 512B 到 32KB 不等。可以独立读写每个块。例如:硬盘,U 盘等
- 字符设备:发送或者接收一个字符序列,不可寻址,也没有 seek 操作。例如:打印机、网卡、鼠标等。
设备控制器
- 有的是集成在主板上,有的是从外部插入的。例如 PCIe 插槽中的插入部件。通常就是设备控制器(controller)或者叫做设备适配器(adapter)
- 作用是:
- 把串行的二进制 01 流转化成字节块
- 执行可能的错误处理
- 与内存交互
每个控制器都会有一些寄存器来与 CPU 通信,操作系统可以通过写入这些寄存器来和外部设备通信,也可以通过读取这些寄存器来获取设备的状态信息。
引出了问题,怎么对这些控制器寄存器进行寻址,以及怎么在控制器和内存之间传输数据。
方法:
- 独立的 I/O 和内存空间
- 内存映射 I/O
- 混合方案。
I/O 寻址方案
独立的 I/O 和内存空间
- 每个控制器寄存器都被分配了一个特殊的地址,被称为 I/O port number。一些特殊的 I/O 指令被用来向这些控制器寄存器中进行读/写。例如
和 分别进行写入和读取 - 要注意
和 在这种情况下是有不同的语义的,前者的 4 代表的是在控制器寄存器中地址为 4,后者的 4 代表的是在主存中地址为 4。
内存映射 I/O
- 把内存地址空间中的一部分作为控制器寄存器的地址空间
优缺点
优点:
- 不需要特殊的指令来进行对控制寄存器的读写操作
- 不需要特殊的保护机制来保护控制寄存器不被用户直接访问
- 每个指令既可以引用内存,也可以引用控制寄存器
缺点:
- 如果设备控制器寄存器的值是缓存,这就会出现问题,我们不想要读取缓存的软拷贝值,我们想要的是寄存器的值。解决方法是禁用对应控制寄存器的那部分虚页的缓存功能
- 由于只有一个地址空间,需要检查每个内存的引用来确定是要主存响应还是要 IO 设备响应。
对总线有要求
混合实现
- 数据寄存器使用内存映射的地址空间
- 控制寄存器使用独立的地址空间
I/O 软件
简介
- 设备独立性:程序可以访问任何 IO 设备,不需要提前指定设备
- 通用命名:命名不依赖机器
- 错误处理
- 同步 vs 异步:阻塞传输 vs 中断驱动
- 缓冲
- 可共享的设备 vs 专用的设备:例如硬盘是可共享的,磁带驱动器不是。
I/O 实现
程序控制的 I/O(Programmed I/O)
- 在 IO 完成前,CPU 一直处于 busy 状态
- 在单进程系统中没问题,但是在多道程序系统和分时系统中就不行了,因为 IO 很消耗时间。
- 以打印机打印“ABCD”为例
/*buffer is user buffer
p is kernel buffer
count is number of bytes to be copied
*/
copy_from_USer(buffer, p,couunt);
for (i = 0;i< count;i++){
while (*printer status reg != READY);
*printer_data_register=p[i];
}
return_to_user();
- CPU 会不断轮询打印机寄存器的状态,知道所有字符串打印完毕,这大大浪费了其他的 CPU 时间
中断驱动的 I/O(Interrupt-Driven I/O)
同样以打印机打印"ABCD"为例
每次打印完发出一个中断,再接着进行下一次打印
缺点是每个字符打印都会产生中断,而 CPU 要响应每个中断十分耗时
DMA(直接内存访问)
- 因此在 DMA 中,DMA 控制器会把字符从内核缓冲区直接“喂”给打印机控制器,CPU 就不用直接响应这个中断了。直到整个缓冲区内的字符都被传输完了,CPU 被中断抢占。
I/O 软件层次结构
- 最顶层是用户空间,中间三层是内核空间,最底层是硬件。
I/O 处理器(Handler)
- 最好把 I/O 处理的过程隐藏起来,因此中断处理通常由引起中断的上层进行(Interrupt Handler)
中断处理过程
- 中断向量就是中断服务程序的首地址
软件处理过程
- 保存没有被中断硬件存储的寄存器的值
- 设置中断服务程序的上下文
- 设置中断服务程序的栈
- 响应中断控制器,重新开启中断
- 将寄存器从保存位置复制到进程表
- 运行服务程序
- 选择下一个要进行的进程
- 设置 MMU 上下文为下一个要运行的进程
- 加载新进程的寄存器
- 开始运行新进程
设备驱动程序(Device Drivers)
- 把一个与设备无关的 I/O 请求对应到一个具体的硬件设备上
- 通常是由设备厂商编写
- 操作系统通常会定义一个接口来规范所有驱动程序
- 操作系统通常会把驱动分成块设备驱动和字符设备驱动
设备驱动程序功能
- 从上层接受一个抽象的读写请求,与设备无关的请求,传化为一个与设备有关的具体的请求。例如从操作系统中接收一个访问某个块号的请求,转化为具体的磁道、扇区。
- 初始化设备
- 检查该设备是否被其他请求使用,利用队列管理请求
- 可以向设备发送命令控制设备
- 检查设备错误信息
设备统一接口
主设备号与次设备号
- 主设备号(Major number)用来定位驱动程序,次设备号(Minior number)用来定位这个驱动程序控制下的某个具体设备
Buffer
错误处理
- 有一些错误可以在底层设备处理,例如一些校验和错误,通过随机 bit 尝试修复 block
- 底层设备处理不了的话就传到上层设备驱动程序处理,例如无法读取 block 的时候重新读取
- 再无法处理的错误就传到 OS 的设备无关软件层处理,例如尝试向只读设备中写入数据
设备无关的块大小
- 不论具体设备的块大小(例如不同磁盘有不同的扇区大小),对于文件系统来说都是固定的块大小
用户空间 I/O 软件
- I/O 库提供 I/O 函数来给用户提供对应的 I/O 系统调用的实现,还提供例如格式化的 I/O 的操作(printf())
- 有些应用程序不直接写入 IO 设备,而是写入假脱机文件(spooler)
硬盘
硬盘结构
- 被分成柱面(cylinders),每个柱面包含磁道(tracks),每个磁道被分成了一些扇区(sectors),扇区大小通常为 512bytes
- 物理上的硬盘随着半径的增长,扇区的数量可能会增加,因此硬盘给操作系统提供了图二的结构抽象,使得每个磁道上都有固定数量的扇区
硬盘格式
- 硬盘生产完时是没有任何数据的,要进行格式化,而格式也包括低级格式(如下图所示)和高级格式
柱面斜进(cylinder skew)
- 就是为了补偿寻道过程中盘片的旋转时间
- 例如:10000rpm(6ms/圈),一个磁道有 300 个扇区(20μs/sector),寻道时间为 800μs,那么就要斜进 40 个扇区。
交错(interleaving)
- 从硬盘控制器的缓冲区把数据复制到内存中需要时间,这就会导致在连续读取的时候有可能磁头就“过站”了,再访问下一个连续的扇区反而要多转一圈,因此通过交错的方法,使得访问的下一个逻辑扇区不是下一个物理扇区,也就是提高两个扇区之间的时间间隔。
硬盘表现的度量
- 访问时间(access time):从读写请求到达到数据传输开始的时间
- 寻道时间(seek time):从当前磁道移动到指定磁道的时间,平均有一半的寻道时间是最差情况(一般是 4-10ms)。
- 旋转延迟(Rotational Latency):扇区从要被访问到出现在磁头下的时间,平均有一半是最差情况,一般的硬盘要花费 4-11ms
- 数据传输速率(data-transfer rate):从硬盘读写数据的速度
- 平均无故障时间(Mean time to failure ,MTTF):平均无故障连续运行的时间。(通常 3-5 年)
磁盘臂调度算法(Disk Arm Scheduling Algorithms)
先到先服务(FCFS)
- 就是先到的先处理。
最短寻道时间(Shortest Seek First, SSF)
- 先服务寻道时间最短的,但是这种方法可能导致饥饿的情况
电梯算法(Elevator algorithm)
- 让磁盘臂沿一个方向运行,直到那个方向没有别的请求了,再回头,还可以变式成在完成最高编号的柱面后,移动到编号最小的柱面再向上移动(也就是把最小编号看成最大编号的相邻柱面),这样有更好的响应时间。
- 好处是给定一组请求,磁盘臂移动的次数的上界永远是柱面数的两倍。
错误处理
- 预留扇区来给坏扇区使用,当检测到有扇区损坏的时候,就把预留扇区映射到原来的坏扇区中。