操作系统从入门到入土⑤:输入 / 输出
1. I/O 硬件原理
1.1. I/O 设备
I/O 设备大致可以分为 块设备(block device)和 字符设备(character device)。
块设备:
块设备把信息存储在固定大小的块中,大小在 512 字节至 65536 字节之间。所有传输以一个或多个完整的连续块为单位。
块设备的基本特征是每个块都能独立于其他块而读写。硬盘、蓝光光盘和 USB 是最常见的块设备。
字符设备:
字符设备以字符为单位发送或接收一个字符流,而不考虑任何块结构。
字符设备是不可寻址的,也没有任何寻道操作。打印机、网络接口、鼠标 (用作指点设备)、以及大多数与磁盘不同的设备都可看作字符设备。
1.2. 设备控制器
I/O 设备一般由机械部件和电子部件两部分组成。机械部件则是设备本身;而电子部件又称作 ** 设备控制器(device controller)** 或 适配器(adapter)。通常是个人计算机主板上的芯片,或是(PCI)扩展槽中的印刷电路板。
作用:
控制一个或多个 I/O 设备,以实现 I/O 设备和计算机之间的数据交换。
控制器把串行的位流转换为字节块,并进行必要的错误校正工作。
字节块通常首先在控制器内部的一个缓冲区中按位进行组装,然后进行校验再将它复制到主存中。
LCD 显示器的控制器也是一个位串行设备。它从内存中读入包含待显示字符的字节,产生信号以便使相应的像素改变背光的极化方式,从而将其写到屏幕上。
1.3. 内存映射 I/O
寄存器:
每个控制器有几个寄存器用来与 CPU 进行通信。
- 通过写入这些寄存器,操作系统可以命令设备发送数据、接收数据、开启或关闭,或者执行某些其他操作。
- 通过读取这些寄存器,操作系统可以了解设备的状态,是否准备好接收一个新的命令等。
数据缓冲区:
许多设备还有一个操作系统可以读写的数据缓冲区。
例如,在屏幕上显示像素的常规方法是使用一个视频 RAM,这一 RAM 基本上只是一个数据缓冲区,可供程序或操作系统写入数据。
寻址控制器方案:
CPU 如何与寄存器和数据缓冲区通信?
- I/O 端口号(I/O port):
每个控制寄存器被分配一个 I/O 端口号(8 位或 16 位整数),所有 I/O 端口形成 I/O 端口空间,只有操作系统可以访问。
在这一方案中,内存地址空间和 I/O 地址空间是不同的。可以使用一条特殊的 I/O 指令,例如 IN REG, PORT
,CPU 可以读取控制寄存器 PORT 的内容并将结果存入到 CPU 寄存器 REG 中。
- 内存映射 I/O(memory-mapped I/O):
将所有控制寄存器映射到内存空间中,每个控制寄存器被分配唯一的一个内存地址,这样的系统称为内存映射 I/O。
内存映射 I/O 优点:
- I/O 指令读写设备控制寄存器,需要使用汇编代码,这增加了控制 I/O 的开销。而对于内存映射 I/O,I/O 设备驱动程序可以用 C 语言编写。
- 不需要特殊的保护机制来阻止用户进程执行 I/O 操作。
- 可以引用内存的每一条指令也可以引用控制寄存器。
例如,如果存在一条指令 TEST 可以测试一个内存字是否为 0,那么它也可以用来测试一个控制寄存器是否为 0。
如果不是内存映射 I/O,必须首先将控制寄存器读入 CPU,然后再测试。
实际工作原理:
当 CPU 想要读入一个字的时候,将需要的地址放到总线的地址线上,然后在总线的一条控制线上置起一个 READ 信号。
还要用到第二条信号线来表明需要的是 I/O 空间还是内存空间。如果是内存空间,内存将响应请求。如果是 I/O 空间,I/O 设备将响应请求。
1.4. 直接存储器存取
非 DMA 数据交换过程:
CPU 需要寻址设备控制器以便与它们交换数据,在没有使用 DMA 时磁盘读取数据效率不高,过程如下:
- 首先控制器从磁盘驱动器串行地、一位一位地读一个块(一个或多个扇区),直到将整块信息放入控制器的内部缓冲区中。
- 接着再计算校验和,以保证没有读错误发生。
- 然后控制器产生一个中断。当操作系统开始运行时,它重复地从控制器的缓冲区中一次一个字节或一个字地读取该块的信息,并将其存人内存中。
DMA 数据交换过程:
DMA 控制器能独立于 CPU 访问系统总线,使用 DMA 时的过程:
- CPU 对 DMA 控制器进行编程,设置其的寄存器,以便让 DMA 控制器知道将什么数据传送到什么地方。
DMA 控制器还要向磁盘控制器发送命令让它从磁盘读数据到其内部的缓冲区中,必要性:
- 磁盘控制器可以在传送之前检验校验和。如果校验和是错误的,那么将发出一个表明错误的信号并且不会进行传送。
- 如果控制器要将数据直接写到内存,则它必须为要传送的每个字取得系统总线的控制权,会导致总线忙。
如果块被放入内部缓冲区,则在 DMA 启动前不需要使用总线。
- DMA 控制器通过在总线上发出一个读请求到磁盘控制器而发起 DMA 传送。
- 磁盘控制器从内部缓冲区中读取字写到内存,这是另一个标准总线周期。
- 当写操作完成时,磁盘控制器在总线上发出一个应答信号到 DMA 控制器。
随后 DMA 控制器增加要使用的内存地址,并且减少字节计数。
如果字节计数仍然大于 0,则重复第 2 步到第 4 步,直到字节计数到达 0。
然后 DMA 控制器将中断 CPU 以便让 CPU 知道传送现在已经完成了。当操作系统开始工作时,用不着将磁盘块复制到内存中,因为它已经在内存中了。
DMA 硬件集成方案:
DMA 需要硬件的支持。DMA 控制器可以集成到磁盘控制器和其他控制器之中,这就要求每个设备有一个单独的 DMA 控制器。也可以将 DMA 控制器集成到主板上,由它调控到多个设备的数据传送。
传输模式:
许多总线能够以两种模式操作:每次一字模式和块模式。某些 DMA 控制器也能够以这两种模式操作。
- 周期窃取(eycle stealing):每次一字模式下,DMA 控制器请求传送一个字并且得到这个字,传送多次。
该模式下设备控制器会偶尔从 CPU 偷走一个临时的总线周期,从而轻微地延迟 CPU。这一机制称为周期窃取。
- 突发模式(burst mode):在块模式中,DMA 控制器通知设备获得总线,发起一连串的传送,然后释放总线。这操作形式称为突发模式。
它比周期窃取效率更高,因为获得总线占用了时间,并且以一次总线获得的代价能够传送多个字。
突发模式的缺点是,如果正在进行的是长时间突发传送,有可能将 CPU 和其他设备阻塞相当长的周期。
联想:与 JVM 垃圾回收器串行收集与并行收集的区别类似。并行会暂停用户线程,专注于垃圾回收,吞吐量大,但也会加大用户响应时间,类似这里的突发模式。
某些 DMA 控制器是让设备控制器将字发送给 DMA 控制器,然后发起第 2 个总线请求将该字写到它应该去的任何地方。
采用这种方案,每传送一个字需要一个额外的总线周期,但是更加灵活,因为它可以执行设备到设备的复制甚至是内存到内存的复制(通过首先发起一个到内存的读,然后发起一个到不同内存地址的写)。
1.5. 中断
产生中断信号:
当一个 I/O 设备完成交给它的工作时,其通过在分配给它的一条总线信号线上置起信号从而产生中断。该信号被主板上的中断控制器芯片检测到,由中断控制器芯片决定做什么。
为了处理中断,中断控制器在地址线上放置一个数字表明哪个设备需要关注,并且置起一个中断 CPU 的信号。
如果有另一个中断正在处理中,该设备将继续在总线上置起中断信号,直到得到 CPU 的服务才被继续处理。
中断信号导致 CPU 停止当前正在做的工作去做其他的事情。
地址线上的数字是一个指向 ** 中断向量(interrupt vector)** 表格的索引,用于读取指向中断服务过程开始位置的程序计数器。
服务开始前保存:
在开始服务程序之前,硬件总是要保存一定的信息。必须保存程序计数器,这样被中断的进程才能够重新开
始。另一个极端是所有可见的寄存器和很多内部寄存器或许也要保存。
将这些信息保存到什么地方是一个问题:
- 在内部寄存器中保存信息。在需要时操作系统可以读出这些内部寄存器。
其缺点是,为避免第二个中断重写寄存器,以致在相关信息被读出前中断控制器无法得到应答。
这一策略在中断被禁止时将导致长时间的死机,并且可能丢失中断和丢失数据。
- 在堆栈中保存信息。
其带来的问题:
- 如果使用当前堆栈,则它很可能是用户进程的堆栈,其堆栈指针甚至可能不是合法的,当硬件在其所指的地址写入时将导致致命错误。
另外,若它指向一个页面的末端,那么在若干次内存写之后,页面边界可能被超出并且产生页面故障。这将会引起更大的问题:在何处保存状态以处理页面故障?
- 如果使用内核堆栈,堆栈指针是合法的并且指向一个固定的页面。
但是,切换到核心态可能要求改变 MMU 上下文,并且可能使高速缓存和 TLB 的大部分或全部失效。静态地或动态地重新装载所有这些东西将增加处理一个中断的时间,因而浪费 CPU 的时间。
采用应答避免竞争:
中断服务过程开始运行后,立刻通过将一个确定值写到中断控制器的某个 I/O 端口来对中断做出应答,来告诉中断控制器可以发出另一个中断。通过这种方式来避免多个同时发生的中断相互竞争。
2. I/O 软件原理
2.1. I/O 软件的目标
设备独立性(device independence):设计的程序应该可以访问任意 I/O 设备。例如,读取一个文件作为输入的程序应该能够在硬盘、DVD 或者 USB 盘上读取文件,无需为每一种不同的设备修改程序。
统一命名(uniform naming):一个文件或一个设备的名字应该是一个简单的字符串或一个整数,它不应依赖于设备。用这种方法,所有文件和设备都采用相同的方式一路径名进行寻址。
例如,一个 USB 盘可以安装到目录 /usr/ast/backup 下,这样复制一个文件到 /usr/ast/backup/monday 就是将文件复制到 USB 盘上。
错误处理(error handling):一般来说,错误应该尽可能地在接近硬件的层面(低层)得到处理,只有低层软件处理不了才将错误上交高层处理。
当控制器发现了一个读错误时,如果它能够处理那么就应该自已设法纠正这一错误。如果控制器处理不了,则交给设备驱动程序处理,可能只需重读一次这块数据就正确了。
同步传输(阻塞)和异步传输(中断驱动):
- 异步:CPU 启动传输后便转去做其他工作,直到中断发生。
- 阻塞:在 read 系统调用之后,程序将自动被挂起,直到缓冲区中的数据准备好。
缓冲(buffering):
数据离开一个设备之后通常并不能直接存放到其最终的目的地。数据必须预先放置到输出缓冲区之中,从而消除缓冲区填满速率和缓冲区清空速率之间的相互影响,以避免缓冲区欠载。缓冲涉及大量的复制工作,并且经常对 I/O 性能有重大影响。
例如,从网络上进来一个数据包时,直到将该数据包存放在某个地方并对其进行检查,操作系统才知道要将其置于何处。此外,某些设备具有严格的实时约束(如数字音频设备)。
共享设备和独占设备:
有些 I/O 设备(磁盘)能够同时让多个用户使用。多个用户同时在同一磁盘上打开文件不会引起什么问题。
有些设备(磁带机)则必须由单个用户独占使用,直到该用户使用完,另一个用户才能拥有该磁带机。让多个用户随机将混杂的数据块写入同一磁带,这样是不能工作的。
2.2. 程序通知 I/O
I/O 可以采用三种方式来实现。程序控制 I/O、中断驱动 I/O 和使用 DMA 的 I/O。
程序控制 I/O(programmed I/O):让 CPU 做全部工作。
例如一个用户进程想通过串行接口在打印机上打印 8 个字符的字符串”ABCDEFGH“。
- 软件首先要在用户空间的一个缓冲区中组装字符串,如 a) 所示。
- 然后,用户进程调用打开打印机之类的系统调用来获得打印机。
如果打印机当前被另一个进程占用,该系统调用将失败并返回一个错误代码,或者将阻塞直到打印机可用。
成功获取打印机后,用户进程就发出一个系统调用打印字符串。
- 然后,操作系统将字符串缓冲区复制到内核空间中的一个数组中。
- 若打印机可用,操作系统就复制第一个字符到打印机的数据寄存器中,在这个例子中使用了内存映射 I/O。
这一操作将激活打印机。字符也许还不会出现在打印机上,因为某些打印机在打印任何东西之前要先缓冲一行或一页。