IO
IO硬件原理
IO设备
IO设备可以大致分为两类:块设备与字符设备。
块设备将信息存储在固定大小的块中,每个块有自己的地址,所有传输以一个或多个完整的块位单位。每个块可以独立于其他块而读写。硬盘、USB等都是块设备。
字符设备以字符位单位发送或接收一个字符流,而不考虑任何块结构。字符设备不可寻址,也没有任何寻道操作,例如打印机、网络接口、鼠标等。
其他类别还有
- 时钟:按照预先规定好的时间间隔产生中断
- 内存映射的显示器等。
设备控制器
IO设备一般分机械部件与电子部件。电子部件称为设备控制器,常以芯片形式出现。
内存映射IO
每个控制器有几个寄存器用来与CPU进行通信,通过写入这些寄存器,OS可以命令设备发送数据、接收数据、开启或关闭,或执行某些其他操作。通过读取寄存器,OS可以了解设备的状态,是否准备好接收一个新的命令等。
通常设备还会有一个OS可以读写的数据缓冲区。
CPU与IO通信
CPU如何与设备的控制寄存器或缓冲区进行通信?
- 方法1:早期的OS工作方式,每个控制寄存器被分配一个IO端口号,是一个8/16位的整数。所有IO端口形成IO端口空间,并只有OS可以进行访问。
- 使用一条特殊的IO指令例如
IN REG ,PORT
。CPU可以读取控制寄存器PORT的内容,并将结果存到CPU寄存器REG当中。
- 使用一条特殊的IO指令例如
- 方法2:内存映射IO。将所有控制寄存器映射到内存空间中,每个控制寄存器被分配唯一的一个内存地址,并且不会有内存被分配这一地址,
- 混合方法。
当CPU想要读入一个字的时候,无论是从IO端口还是内存读入,都要将需要的地址放到总线的地址线上,然后在总线的一条控制线上置起一个READ信号。如果是内存空间,内存将响应请求,内存当中的每个内存模块和IO设备都会将地址线与它所服务的地址范围比较,比较成功则响应请求。
中断
在硬件层面上,中断的工作是当一个IO设备完成交给它的工作时,它就产生一个中断,它是通过在分配给它的一条总线信号线上置起信号而产生中断的。
处理中断
设备与中断控制器之间的连接实际上使用的是总线上的中断线。如果有更高优先级或有其他中断正在处理,则暂时不会理会中断。
为了处理中断,中断控制器在地址线上放置一个数字表明哪个设备需要关注,并置起一个中断CPU的信号。中断信号导致CPU停止当前正在做的工作并且开始做其他的事情,地址线上的数字被用作指向一个被称为中断向量的表格索引,以便读取一个新的程序计数器。
中断服务过程开始运行后,立刻通过将一个确定的值写入中断控制器的某个IO端口来对中断作出应答。
精确中断和不精确中断
由于现代OS的乱序执行以及CPU优化操作,当发生中断时,可能之前的指令还没有执行完,并且由于并行处理,则在处理中断时,很多指令还处于不同的执行阶段,即很难明确当前程序计数器到底执行到了哪里。
将机器留在一个明确状态的中断称为精确中断,其具有4个特性:
- PC(程序计数器)保存在一个已知的地方
- PC所指向的指令之前的所有指令已经完全执行
- PC所指向的指令之后的所有指令都没有执行。并非禁止执行,只是中断发生前必须撤销它们对寄存器或内存的修改。
- PC所指向的指令的执行状态是已知的。
不满足这些要求的称为不精确中断。
IO软件层次
IO软件通常组织成四个层次,每一层具有一个要执行的定义明确的功能和一个定义明确的与临近层次的接口。
中断处理程序
当中断发生时,中断处理程序 215
设备驱动程序
与设备无关的操作系统软件
用户级IO软件
时钟
时钟负责维护时间,并且防止一个进程垄断CPU,此外还有其他功能。
时钟硬件
通常使用两种类型的时钟,简单的时钟连接到电源线上,每个电压周期产生一个中断,现在非常少。
另一种时钟由三个部件组成:晶体振荡器、计数器和存储寄存器。它用来给计算机的各种电路提供同步信号,该信号被送到计数器,使其递减计数至0,当计数器变为0产生一个CPU中断。
可编程时钟通常具有几种操作模式,
- 一次完成模式:当时钟启动时,它把存储寄存器的值赋值到计数器中,然后来自晶体的每一个脉冲使计数器-1,当计数器位0产生一个中断,并停止工作,直到软件再一次显式启动它
- 方波模式。当计数器变为0并且产生中断后,存储寄存器的值自动复制到计数器中,并且整个过程无限期继续下去。
时钟软件
软定时器
IO复用
进程需要一种预先告知内核的能力,使得内核一旦发现进程指定的一个或多个IO调节就绪,即输入已准备好被读取,或者描述符已能承载更多的输出,它就停止进程。
IO复用是使用select和poll这两个函数支持的。IO复用典型使用在以下网络应用场合:
- 当客户处理多个描述符时,必须使用IO复用。
- 一个客户同时处理多个套接字。
- 如果一个TCP服务器既要处理监听套接字,又要处理已连接套接字,一般要使用IO复用。
- 如果一个服务器要处理多个服务或多个协议,一般要使用IO复用。
IO模型
Unix下可用的5种IO模型:
- 阻塞式IO。
- 非阻塞式IO。
- IO复用,select和poll。
- 信号驱动式IO,SIGIO。
- 异步IO,AIO系列。
而一个输入操作通常包括两个不同的阶段:
- 等待数据准备好。
- 从内核向进程复制数据。
IO复用模型
我们可以调用select或poll,阻塞在这两个系统调用种的某一个之上,而不是阻塞在真正的IO系统调用上:
select()
该函数允许进程指示内核等待多个事件中的一个发生,并在只有一个或多个事件发生或经历一段指定的事件后才唤醒它:
我们可在调用select()时告知内核仅在下列情况发生时才返回,例如:
- 集合{1,2,4}中的任何描述符准备好读取。
- 集合{2,7}中的任何描述符准备好写入。
- 集合{1,4}中的任何描述符有异常条件待处理。
- 已经历10.2秒。
1 | //若有就绪描述符则为其数目,若超时则为0,若出错则为-1 |
maxfdpl。指定待测试的描述符个数,它的值是待测试的描述符+1,即描述符会从0…maxfdpl-1均将被测试。
timeout。告知内核等待所指定描述符中的任何一个就绪可花多长事件。
1
struct timeval{ long tv_sec,long tv_usec};
该参数有三种可能:
- 永远等待下去:仅在有一个描述符准备好IO时才返回,此时设置该参数为空指针。
- 等待一段固定时间:在有一个描述符准备好IO时返回,但是不超过由该参数所指定的时间。
- 根本不等待:检查描述符后立即返回,称为轮询。
描述符就绪条件
当满足以下条件之一,一个套接字准备好读:
- 该套接字接收缓冲区中的数据字节数 >= 套接字接收缓冲区低水位标记的当前大小。
- 该连接的读半部关闭,即接收了FIN的TCP连接,对于这样的套接字的读取操作将不阻塞并返回0。
- 该套接字是一个监听套接字,且已完成的连接数不为0。对这样的套接字accept通常不会阻塞。
- 其上有一个套接字错误待处理。此时对其读取将返回-1,即错误。
当满足以下条件之一,一个套接字准备好写:
- 该套接字发送缓冲区的可用空间字节数 >= 套接字发送缓冲区的低水位标记的当前大小。并且或者该套接字已连接,或该套接字不需要连接如UDP。
- 即如果我们把这样的套接字设置为非阻塞,写操作将不阻塞并返回一个正值。
- 该连接的写半部关闭。
- 使用非阻塞式connect的套接字已建立连接,或connect已经以失败告终。
- 其上有一个错误待处理。