块设备是指能随机访问固定大小数据片的设备,如硬盘;字符设备(如串口和键盘)是按照字符流的方式有序访问。区别在于是否可以随机访问数据——也就是能否在访问设备时随意地从一个位置跳转到另一个位置。我们可以感觉到块设备的控制要比字符设备复杂多,实际上内核在块设备上下了大工夫——块I/O层。
基础概念
- 块设备中最小的可寻址单元是扇区。
- 文件系统的最小寻址单元是块。
- 所谓的缓冲区是块在内存中的表示。
- 对于一个缓冲区(块),内核需要知道它的控制信息,这时需要一个结构进行描述——缓冲区头。
I/O调度机制
首先,这里的I/O调度指的是磁盘调度。磁盘寻址是很耗时的一项操作,对于同时存在的各种磁盘I/O请求,我们需要利用一些I/O调度程序实现最省时的请求处理——I/O调度程序让整个请求队列按扇区增长方向有序排列。使所有请求按硬盘上扇区的排列顺序有序排列的目的不仅是为了缩短单独一次请求的寻址时间,更重要的优化在于,通过保持磁盘头以直线方向移动,缩短了所有请求的磁盘寻址时间。该排序算法类似于电梯调度——电梯不能随意地从一层跳到另一层,它应该向一个方向移动,当抵达了同一方向上的最后一层后,再掉头向另一个方向移动,所以I/O调度程序称作电梯调度。另外出了排序,合并也是一种减少磁盘寻址时间的方法。合并是将两个或多个请求结合成一个新请求,这样将多次请求的开销压缩成一次请求的开销,更重要的是,请求合并后只需要传递给磁盘一条寻址命令,就可以访问到请求合并前必须要多次寻址才能访问完的磁盘区域,总之减少了磁盘寻址次数。排序,减少寻址时间,而合并,减少寻址次数,二者共同作为I/O调度程序的准则。
下面看一下五个Linux实际使用的I/O调度程序。
(1)Linus电梯
当一个请求加入到队列中时,有可能发生四种操作:
- 如果队列中已存在一个对相邻磁盘扇区操作的请求,那么新请求将和这个已经存在的请求合并成一个请求。
- 如果队列中存在一个驻留时间过长的请求,那么新请求将被插入到队列尾部,以防止其他旧的请求饥饿发生。
- 如果队列中以扇区方向为序存在合适的插入位置,那么新的请求将被插入到该位置,保证队列中的请求是以被访问磁盘物理位置为序进行排列的。
- 如果队列中不存在合适的请求插入位置,请求将被插入到队列尾部。
(2)最终期限I/O调度程序
对磁盘同一位置的连续请求造成较远位置的其他请求永远得不到运行机会,这是一种很不公平的饥饿现象。 在最后期限I/O调度中,每个请求都有一个超时时间,默认情况下,读请求的超时时间是500ms,写请求的是5s,最后期限I/O调度请求类似于Linus电梯,也以磁盘物理位置为次序维护请求队列,这个队列称为排序队列。当一个新请求递交给排序队列时,最后期限I/O调度程序在执行合并和插入请求时类似于Linus电梯,但是最后期限I/O同时也会以请求类型为依据将它们插入到额外队列中。读请求按次序被插入到特定的读FIFO队列中,写请求按次序插入到特定的写FIFO队列中,虽然普通队列以磁盘扇区为序进行排列,但是这些队列是以FIFO(以时间为序)组织的,结果新队列总是被加入到队列尾部,对于普通操作来说,最后期限I/O将请求从排序队列头部取下,再推入到派发队列,由派发队列将请求提交给磁盘驱动,从而保证了最小化的请求寻址。而最后期限I/O的超时体现在读写FIFO队列头的请求超时时,调度程序便会提取请求进行服务,这样不用担心有请求不能得到服务的不公平现象。
(3)预测I/O调度程序
基本上同最终期限,不过它能进行预测。如果读请求一个接着一个,对于最终期限来说要不停地为读请求寻址,并且中断正在进行的写请求,这会损害系统全局吞吐量。如果我们能等待,将一个接着一个的读请求进行统一寻址,这会避免大量的寻址操作。在一个请求提交后,预测会接着有I/O请求在等待期到来,从而有意等待片刻(不是一提交请求就立刻返回处理其他请求),这便是预测I/O的巧妙之处。这种预测依靠一系列的启发和统计工作。
(4)完全公正的排队I/O调度程序
它给每一个进程维护一个队列,请求加入队列后的操作同Linus电梯的四种操作,这其实在进程级提供了公平。
(5)空操作的I/O调度程序
不进行排序,也不预测。它只进行合并——将相邻请求合并,并且维护请求队列以近乎FIFO的顺序排列。
Linux内核设计基础(六)之块I/O层,古老的榕树,5-wow.com