I/O介绍
什么是I/O
I/O即:输入/输出,是在主存和外部设备(例如磁盘驱动器、终端和网络)之间复制数据的过程。输入操作是从I/O设备复制数据到主存,而输出操作是从主存复制数据到I/O设备。
使用场景
I/O操作几乎遍及计算机用户的每一个行为,包括文字录入、音视频播放、文件读写和网络相关的所有行为。
涉及对象
以存储器为中心的双总线结构框图如下,主要涉及到CPU、总线、主存、外设等。
举例读操作(交公粮)
以读磁盘的数据为例,这里举个例子,以此来说明下面要说的几个对象,希望能够更好的理解。
假设整套计算机组件就是一个小村庄,那么CPU就是村委会,总线就是马路或者电话了,外设可以是众多的村民家庭。
那么现在需要交公粮了,就是要把村民家的粮食转移到村子管理的粮食仓库。
- 首先,村长打了个电话,相当于CPU在控制总线上面发出了一个信号;
- 打到哪里去?某个村民家里,电话号码是多少?就是设备的编号(地址);
- 谁接的电话?村民,即设备控制器;
- 村民接到电话后,积极准备粮食,从七零八落的地方凑齐了放在一块,放在哪里呢?设备的缓存;
- 村民准备好之后,回拨了一个电话,说准备好了,你来拉走吧。这个就是向CPU发起了一个中断;
- CPU收到中断之后,让马车(数据总线)拉到仓库中,放到内存中。
读取完毕。下面简单说说上面的例子中提到的概念。
I/O设备
I/O设备大致可以分为两类:块设备和字符设备。
块设备把信息存储在固定大小的块中,每个块有自己的地址。所有传输以一个或多个完整的块为单位。块设备的特征是每个块都能独立于其他的块而读写。
字符设备以字符为单位发送或接收一个字符流,而不考虑任何块结构。字符设备是不可寻址的,也没有任何寻道操作。例如打印机、网络和鼠标等。
设备控制器
I/O设备一般都会有一个设备控制器,或者叫作适配器。负责将设备的数据处理成为一种规整简单的容易处理的数据输出,或者把传输给设备的比特流适配成为设备可以识别的数据交付。
以磁盘为例,控制器的任务就是把磁盘输出的串行的比特流转换为字节块,并进行必要的错误校正工作。字节块通常首先在控制器内部的一个缓冲区中按位进行组装,然后再进行数据校验并证明没有错误后,再将它复制到主存中。
编址方式
设备控制器可以通过设备的寄存器或者数据缓冲区来与CPU进行通信。
那么如何进行通信呢?就像上面提到的例子中说的,打电话需要有电话号码,邮寄需要有邮编,而外设也需要有一个地址来标识。
CPU对外设IO端口物理地址的编址方式有两种:端口映射I/O和内存映射I/O。
在计算机中,内存映射I/O(MMIO)和端口映射I/O(PMIO)是CPU和外部设备之间的两种互为补充的I/O方法。
两种内存存在方式如下图所示:
端口映射I/O
Port-mapped I/O (PMIO),每个控制寄存器被分配一个I/O端口号,所有的I/O端口形成I/O端口空间,并且受到保护使得普通的用户程序不能对其进行访问,只有操作系统可以访问。大多数早期计算机,包括几乎所有大型机,都是以这种方式进行工作的。
在这种方式下,内存和I/O设备有不同的地址空间,如上图a所示。
端口映射I/O通常使用一种特殊的CPU指令,专门执行I/O操作。在Intel的微处理器中,使用的指令是IN和OUT。例如
IN REG, PORT
CPU可以读取控制寄存器PORT的内容并将结果存入到CPU寄存器REG中。
由于I/O设备有一个与内存不同的地址空间,为了实现地址空间的隔离,要么在CPU物理接口上增加一个I/O引脚,要么增加一条专用的I/O总线。由于I/O地址空间与内存地址空间是隔离的,所以有时将PMIO称为被隔离的IO(Isolated I/O)。
内存映射I/O
Memory-mapped I/O (MMIO),内存映射I/O,是PDP-11(迪吉多电脑的PDP-8系列的后续机种)引入的系统,它将所有控制寄存器映射到内存空间中,每个控制寄存器被分配唯一的一个内存地址,并且不会有内存被分配这一地址。
在MMIO中,内存和I/O设备共享同一个地址空间。如上图b所示。
它使用相同的地址总线来处理内存和I/O设备,I/O设备的内存和寄存器被映射到与之相关联的地址。当CPU访问某个内存地址时,它可能是物理内存,也可以是某个I/O设备的内存。因此,用于访问内存的CPU指令也可来访问I/O设备。每个I/O设备监视CPU的地址总线,一旦CPU访问分配给它的地址,它就做出响应,将数据总线连接到需要访问的设备硬件寄存器。为了容纳I/O设备,CPU必须预留给I/O一个地址区域,该地址区域不能给物理内存使用。
需要注意的是,读写这段内存地址并不是直接读写设备的IO资源,内存映射就是映射到内存,读写的始终是内存而不是设备IO,之所以会有作用,是因为内核帮我们操作转化了。
I/O 控制方式
有了地址之后,就可以对设备进行读取的操作。按照 I/O 控制器功能的强弱,以及和 CPU 之间联系方式的不同,可把I/O设备的控制方式分为四类,它们的主要差别在于中央处理器和外围设备并行工作的方式不同,并行工作的程度不同。
程序查询方式
以上面交公粮的例子来说,村长每隔一个小时打电话问一遍,粮食是否凑齐,凑齐了就去拉,没凑齐就一直打电话去问。
这种方式也称为忙等待(busy waiting),是最简单的实现方式,是指直接在程序控制下进行数据的输入/输出操作。
其缺点是要占据CPU,CPU一直轮询设备直到对应的I/O操作完成。CPU处于主动地位,而外设处于被动地位,这就是常说的对外设的轮询,效率低。
中断传送方式
以上面交公粮的例子来说,首先村长需要打电话通知村民需要多少粮食要准备,然后村长就去忙别的了,村民把粮食都准备好之后,回拨村长一个电话(中断)说搞好了,然后村长就派人去拉就好了。
简单来说,当外设需要与CPU进行信息交换时,由外设向CPU发出请求信号,使CPU暂停正在执行的程序,转而去执行数据输入/输出操作,待数据传送结束后,CPU再继续执行被暂停的程序。
相比于上面的程序查询方式来说,这种方式减少了CPU轮询的消耗,是一种异步无阻塞的策略。但是,如果传输内容过多,由于输入输出操作直接由CPU控制,每传送一个字符或一个字,都要发生一次中断,仍然会消耗大量CPU时间,因此适合少量数据的传送。
直接存储器存取 DMA
以上面交公粮的例子来说,村长每次都过问这种小事,无比厌烦,就找了个小蜜做这件事。然后小蜜就用村委会的电话开始做村长之前做的事情。然后,在村长无感的情况下,村民的粮食就跑到了仓库中了。而村民并不知道到底是村长的指示还是小蜜的要求。
程序查询方式和中断的方式,均由CPU控制数据传输,而第三种方式是为I/O使用一种特殊的直接存储访问(Direct Memory Access,DMA)芯片,它可以控制在内存和设备控制器之间的比特流,而无须持续的CPU干预。从上面的例子也可以看出,DMA在工作期间需要接管总线的控制权,村民也并不知道是CPU的指令还是DMA的指令。
内部实现机制稍复杂,不再赘述。
I/O通道控制
DMA 方式与程序中断方式相比,使得CPU对IO的干预从字(字节) 为单位的减少到以数据块为单位。而且,每次 CPU干预时,并不要做数据拷贝,仅仅需要发一条启动 I/O 指 令 ,以及完成 I/O 结束中断处理。
但是,每发出一次I/O指令,只能读写一个数据块,如果用户希望一次读写多个离散的数据块,并能把它们传送到不同的内存区域,或相反时,则需要由CPU分别发出多条启动I/O指令及进行多次 I/O 中断处理才能完成。
通道方式进一步减少了CPU对I/O操作的干予,减少为对多个数据块,而不是仅仅一个数据块,及有关管理和控制的干予。
通道又称输入输出处理器。它能完成主存储器和外围设备之间的信息传送,与中央处理器并行地执行操作。
Unix中的I/O
在我们网络服务开发过程中,应用一般是部署在Unix系列的主机上面,比如说Linux系统,所以下面主要说一下Unix系列主机对I/O设备的操作模式。
文件
以Linux为例,所有的I/O设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当作对相应文件的读和写来执行。这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行。
一个Linux文件就是一个m个字节的序列,每个文件都有一个类型(type)来表明它在系统中的角色。
文件主要包含下面三种类型:
普通文件包含任意数据。应用程序通常要区分文本文件和二进制文件,文本文件是只含有ASCII或Unicode字符的普通文件;二进制文件是所有其他的文件。对内核而言,文本文件和二进制文件没有区别。
目录是包含一组链接的文件,其中每个链接都将表示一个文件或者目录。
套接字(socket)是用来与另一个进程进行跨网络通信的文件。
标准I/O
除了Linux内核的I/O接口之外,C语言还为程序员提供了一套替代Unix I/O的高级输入输出函数,称为标准I/O库。标准I/O库将一个打开的文件模型化为一个流。对于程序员而言,一个流就是一个指向文件类型的结构的指针。
而在接口的选择上面,只要有可能就使用标准I/O。而对socket的I/O一般会使用另外一套方法:RIO函数,因为使用标准I/O可能会出现一些问题,这里不再展开。
引用
《现代操作系统》
《深入理解计算机系统》