操作系统导论习题解答(36. I/O Devices)
I/O Devices
带着问题学习:如何将I/O集成到系统中?一般机制是什么?如何使它们高效?
1. System Architecture
上图所示,I/O设备被分成三层。但是为什么需要将其分层?
最直接的原因:物理(physics)和成本(cost)。高性能通道没有太多空间插入设备且设计总线的成本高。采用分层结构,使性能越高的通道离CPU越近。
当然,随着科技的发展,现今的系统结构又进一步提升了性能:
DMI:Direct Media Interface
USB:Universal Serial Bus
PCIe:Peripheral Component Interconnect Express
ATA:AT Attachment
SATA:Serial ATA
eSATA:external SATA
从ATA到eSATA,每一次改变都提升了性能。
2. A Canonical Device
从上图我们能看出一个设备有两个重要组成成分:外部接口(Interface)和内部结构(Internals)。
3. The Canonical Protocol
再看一下Figure 36.3。外部接口由三个寄存器组成:
- 状态寄存器(a status register):读取当前设备状态。
- 命令寄存器(a command register):通知设备执行确定任务。
- 数据寄存器(a data register):在操作系统和设备之间传输数据。
接下来看一些操作系统控制外设的操作协议:
while (STATUS = BUSY)
; // wait until device is not busy
Write data to DATA register
Write command to COMMAND register
(starts the device and executes the command)
while (STATUS == BUSY)
; // wait until device is done with your request
上述基本协议简单且有效。但是,涉及一些低效且不方便之处。
第一点就是while()
循环效率非常低。它消耗了大量的CPU时间去等待设备完成任务,而不是转换到下一个进程使用CPU。
那么如何避免循环产生的CPU时间浪费?
4. Lowering CPU Overhead With Interrupts
解决上述问题的方案其实我们已经在前面学过了:中断(interrupt)。操作系统能发出一个请求,使循环进程进入睡眠状态,然后转换到另一个进程。
中断允许计算和I/O重叠,这是提升CPU利用率的关键。
看一下图更好理解:
上图不使用进程转换。进程1使用CPU(CPU中用1表示);进程1进入循环等待(CPU中用p表示部分都是CPU时间的浪费)。
上图使用进程转换。当进程进入等待状态时,进程开始运行(充分利用CPU)。
中断能提高CPU利用率。但是要注意一点:使用中断并不总是最好的解决方法。考虑一个情况,如果进行进程转换的开销比进程进入等待状态的自循环开销还大,那为什么要使用中断呢?
另一个不建议使用中断的地方是网络(networks)。
还有一个基于中断的优化是合并(coalscing)。可以将多个中断合并成一个中断传递,从而降低了中断处理的开销。当然,等待时间太长会增加请求的延迟(鱼和熊掌不可兼得)。
5. More Efficient Data Movement With DMA
如下图所示:进程1运行一段时间(CPU标记为1),然后希望将一些数据写入磁盘。它将启动PIO(programmed I/O),该I/O必须使用CPU一次将一个数据从内存复制到设备(图中标记为c),复制完成后,I/O在磁盘上开始,然后进程2可以使用CPU(CPU标记为2)。
上图可以看出启动PIO就需要使用CPU,这就浪费了CPU时间。如何使PIO开销更低呢?
上述问题的解决方法就是DMA(Direct Memory Access)。DMA本质上是系统中非常特殊的设备,无需过多的CPU干预即可协调设备与主存之间的传输。
DMA工作过程如下图所示:当进程1需要使用I/O时,通过DMA将数据从内存复制到设备,此时CPU处于空闲状态,转换到进程2使用CPU。
6. Methods Of Device Interaction
前面说了这么多,我们还是没有说明一个问题:如何与外设进行交互?
经过长时间的研究,提出了两种主要的设备交互方法:
- I/O指令(instructions)。这是一种最古老的方法,不同的指令具有不同的优先级。
- 内存映射IO(memory-mapped I/O)。硬件设备寄存器就像存储位置一样可用。
这两种方法严格来说其实没有优劣之分,需要具体情况具体分析。
7. Fitting Into The OS: The Device Driver
讨论最后一个问题:为了保持通用性,如何将每个都有特定接口的设备安装到操作系统中?
解决上述问题要使用一种古老的技术:抽象(abstraction)。
不妨看一下下图:上层应用对下层应用是如何实现的毫不关心,而仅仅是使用下层应用提供的操作。
上图还显示了设备的原始接口(raw interface)。该接口使特殊应用程序直接读取或写入块,而无需使用抽象。
上图看到的封装(encapsulation)也可能有其缺点。如果某个设备具有许多特殊功能,但必须提供与内核其余部分的接口,则这些特殊功能不能使用。
8. Case Study: A Simple IDE Disk Driver
为了更深入一点,让我们来看一个例子:
IDE磁盘与系统之间有一个由4种类型寄存器组成的简单接口:
- 控制寄存器
- 命令寄存器
- 状态寄存器
- 错误寄存器
系统与设备交互的基本协议如下(假设已经被初始化):
- 等待驱动准备就绪(wait for drive to be ready)
- 向命令寄存器中写入参数(write parameters to command registers)
- 执行I/O(start the I/O)
- 数据传输(data transfer)
- 中断处理(handle interrupts)
- 错误处理(error handling)
上述这些协议大部分都能在xv6 IDE驱动程序中找到: