#### 参考资料

- 《操作系统的设计与实现》

#### I/O设备概述

大致有两种I/O设备：块设备和字符设备。

块设备的特点是按块编址。磁盘是最常见的块设备。

字符设备的特点是收发字符流。它无法编址，也不存在任何寻址操作。字符设备有：打印机，网络接口，鼠标，大部分与磁盘不同的设备。

这种分类方式并不完美，有些设备就不符合它，比如时钟。但字符设备和块设备的模型有足够的一般性，操作系统足以使用这一模型来处理不同的I/O设备。比如文件系统只处理抽象的块设备，设备相关的部分则交给设备驱动来处理。

I/O设备有巨大的速度范围。要在这些跨数量级的数据速率之间表现良好，软件的压力很大。

#### 设备控制器

I/O设备是由机械部件和电子部件两部分组成的。电子部件叫做**设备控制器**或**适配器**。在个人计算机上，电子部件就是可插到扩展槽上的印刷电路卡。机械部件就是设备自身。

控制器电路卡有一个连接器，设备可以通过电缆挂接到连接器上。控制器一般都可以连接多个设备。在控制器和设备之间的接口是有标准的，这样控制器厂商和设备厂商就可以生产满足接口标准的控制器或设备。

操作系统只要管理控制器就行了，不用管理设备。CPU和控制器之间是通过总线通信的。

控制器和设备之间的接口是低层次的。

#### 两种I/O

每个控制器都有一些寄存器，用于与CPU通信。通过写入这些寄存器，操作系统可以命令设备发送数据，接受数据，打开或关闭自己，或以其他方式执行一些操作。通过读取这些寄存器，操作系统可以了解设备的状态，是否准备接受新命令，等等。

除了控制寄存器之外，许多设备都有一个数据缓冲区，操作系统可以读取和写入。例如，计算机在屏幕上显示像素的一种常见方法是有一个视频RAM，它基本上只是一个数据缓冲区，供程序或操作系统写入。

CPU与外设(寄存器和数据缓冲区)有两种交互方式：内存映射IO(MMIO)与端口IO(PMIO)。

还有一种混合方案：数据缓冲区使用内存映射IO，控制寄存器则使用端口映射IO。奔腾就使用这种架构，I/O端口是0到64K，数据缓冲区是640K到1M。使用640K~1M的地址是为了与IBM PC兼容。

当CPU想从内存或I/O端口读取一个字时，它将需要的地址放在总线的地址线上，然后在总线控制线上断言一个read信号。还需要第二个信号线来告诉是否需要I/O空间或内存空间。如果是内存空间，内存会响应请求。如果是I/O空间，则I/O设备响应请求。如果只有内存空间，每个内存模块和每个I/O设备将地址线与它服务的地址范围进行比较。如果地址在其范围内，则响应请求。由于没有地址同时分配给内存和I/O设备，所以没有歧义和冲突。

##### 端口映射I/O

1. 端口读写指令只有2条：in, out 。这两个指令的硬件实现本质上和读写内存是相同的。
1. 端口地址范围：0～65535  (本机端口：/proc/ioports)  
1. 只能用ax或al来存放与端口交换的数据  

大多数早期计算机，包括几乎所有的大型计算机，如IBM 360及其后继产品，都是这样工作的。

##### 内存映射I/O

内存映射IO指的是把I/O寄存器作为内存地址空间的一部分。每个控制寄存器被分配一个唯一的内存地址，内存不会使用这块内存地址。通常，被分配的地址位于地址空间的顶部。

#### 中断

通常，控制器寄存器有一个或多个**状态位**，可以对这些位进行测试来确定输出操作是否完成或输入设备是否有新数据可用。CPU可以执行一个循环，每次测试一个状态位，直到设备准备接受或提供新数据。这称为**轮询**或**忙等待**。在I/O领域中，您可能需要等待很长时间才能让外界接受或生成数据，轮询是不可接受的，除非非常小的专用系统没有运行多个进程。

除了状态位，许多控制器使用中断来告诉CPU何时准备好读取或写入寄存器。大多数接口设备提供了一个输出，这个输出在逻辑上与寄存器的“操作完成”或“数据就绪”状态位相同，但它是用来驱动系统总线的IRQ(中断请求)线路之一。因此，当一个中断使能的操作完成时，它中断CPU并开始运行中断处理例程。这段代码告诉操作系统I/O已经完成。然后，操作系统可能会检查状态位，以验证是否一切正常，并获取结果数据或发起重试。

中断控制器的输入数量可能是有限的，奔腾级pc只有15个可用于I/O设备。有些控制器是硬连接到系统主板上的，例如IBM PC的磁盘和键盘控制器。在较老的系统上，设备使用的IRQ是由与控制器相关联的开关或跳线设置的。如果用户购买了一个新的插件板，他必须手动设置IRQ，以避免与现有的IRQ冲突。很少有用户能正确地做到这一点，这导致业界开发了**即插即用**技术, BIOS可以在启动时自动为设备分配irq，以避免冲突。

#### DMA

内存映射(MMIO和PMIO)作为一种CPU对I/O设备(CPU-to-device)的通信方法，并不影响DMA(直接内存访问), 因为DMA是一种绕过CPU的内存对设备(memory-to-device)的通信方法。

无论一个系统是否有内存映射的I/O，它的CPU都需要寻址设备控制器，以便与它们交换数据。CPU可以一次一个字节地从I/O控制器请求数据，但是对于像磁盘这样产生大量数据块的设备这样做会浪费CPU的时间，所以经常使用一种称为DMA(**直接内存访问**)的不同方案。操作系统只能在硬件有DMA控制器的情况下使用DMA，而大多数系统都有DMA控制器。有时该控制器集成到磁盘控制器和其他控制器，但这样的设计需要为每个设备单独的DMA控制器。更常见的是，可以使用单个DMA控制器(例如，在主板上)来调节对多个设备的传输，这样的传输通常是并发的。

无论物理位置在哪里，DMA控制器都可以独立于CPU访问系统总线。它包含几个寄存器，可以被CPU写入和读取。这些寄存器包括一个内存地址寄存器、一个字节计数寄存器和一个或多个控制寄存器。控制寄存器指定要使用的I/O端口、传输方向(从I/O设备读取或写入I/O设备)、传输单元(每次字节或每次字)以及一次突发传输的字节数。

为了解释DMA是如何工作的，让我们首先看看当不使用DMA时磁盘读取是如何发生的。首先，控制器从驱动器上串行地、逐位地读取块(一个或多个扇区)，直到整个块都在控制器的内部缓冲区中。接下来，它计算校验和，以验证没有发生读取错误。然后控制器触发一个中断。当操作系统开始运行时，它可以通过执行循环每次从控制器的缓冲区读取一个字节或一个字，将其存储在内存中，增加内存地址，并减少要读取的条目的计数，直到它达到零。

当使用DMA时，过程是不同的。首先，CPU通过设置寄存器来给DMA控制器编程，这样它就知道在哪里传输什么。DMA控制器还向磁盘控制器发出一个命令，告诉它从磁盘读取数据到其内部缓冲区，并验证校验和。当磁盘控制器的缓冲区中有有效数据时，DMA就可以开始了。

DMA控制器通过总线向磁盘控制器发出一个读请求(步骤2)来启动传输。这个读请求看起来像任何其他的读请求，磁盘控制器不知道也不关心它是来自CPU还是来自DMA控制器。通常，要写入的内存地址在总线的地址线上，因此当磁盘控制器从其内部缓冲区取下一个字时，它知道将其写入何处。写入内存是另一个标准的总线周期(步骤3)。当写入完成时，磁盘控制器也通过总线(步骤4)向磁盘控制器发送确认信号。然后，DMA控制器增加内存地址来使用，并减少字节计数。如果字节计数仍然大于0，则重复步骤2到4，直到计数达到0。此时，控制器引起中断。当操作系统启动时，不需要将块复制到内存，它已经在那里了。

并不是所有的计算机都使用DMA。反对它的理由是，主CPU通常比DMA控制器快得多，可以更快地完成工作(当限制因素不是I/O设备的速度时)。如果没有其他工作需要它做，那么让(快速)CPU等待(慢速)DMA控制器完成工作是毫无意义的。此外，去掉DMA控制器并让CPU在软件中完成所有工作可以节省资金，这对低端(嵌入式)计算机来说非常重要。
