Linux下有两种基本的设备类型,一种是字符设备,另外一种是块设备。如果一个硬件设备是以字符流的方式被访问的话,则属于字符设备;反之,如果一个设备是随机(无序)访问的话,则属于块设备。比如键盘属于字符设备,硬盘属于块设备。字符设备仅仅需要控制一个位置--当前位置,而块设备访问的位置必须能够在截至的不同区间前后移动。
扇区是块设备最小寻址单元,一般为512字节。块是文件系统的一种抽象--只能基于块来访问文件系统。虽然物理磁盘寻址是按照扇区级进行的,但是内核执行的所有磁盘操作都是按块进行的。内核还要求块大小是2的整数倍,而且不能超过一个页的长度。总结来说,扇区是设备的最小寻址单元,而块是文件系统的最小寻址单元。
1.缓冲区和缓冲区头
当一个块被调入内存时,它要存储在一个缓冲区中,每一个缓冲区与一个块对于,它相当于是磁盘块在内存中的表示。每个缓冲区都有一个对应的描述符:buffer_head
,我们称为缓冲区头,它包含了内核操作缓冲区所需要的全部信息。
缓冲区头的目的在于描述磁盘块和物理内存缓冲区之间的映射关系。缺点在于:1.缓冲区头是一个很大且不易控制的数据结构体,在2.6内核中,许多I/O操作都是通过内核直接对页面或地址空间进行操作来完成,不再使用缓冲区头。2.它仅能描述单个缓冲区。
2.bio结构体
目前,内核中块I/O操作的基本容器由bio结构体来表示。该结构体代表了正在现场(活动的)以片段(segement)链表形式组织的块I/O操作。一个片段是一小块连续的内存缓冲区。这样的话,就不需要保证单个缓冲区一定要连续。所以通过用片段来描述缓冲区,即使一个缓冲区分散在内存的多个位置上,bio结构体也能对内核保证I/O操作的执行。像这样的向量I/O就是所谓的聚散I/O。
使用bio结构体的目的主要是代表正在现场执行的I/O操作,所以该结构体中的主要域都是用来管理先关信息的,如下所示:
bi_io_vec
域指向一个bio_vec
结构体数组,该结构体链表包含了一个特定I/O操作所需要使用到得所有片段。每个bio_vec
结构都是一个形为<page, offset, len>
的向量,它描述的是一个特定的片段:片段所在的物理页、块在物理页中的偏移位置、从给定偏移量开始的块长度。整个bio_io_vec
结构体数组表示了一个完整地缓冲区。每一个块I/O请求都是通过一个bio结构体表示,每个请求包含一个或多个块,这些块存储在bio_vec
结构体数组中。
3.两种方式的对比
bio结构体代表的是I/O操作,它可以包括内存中的一个或多个页;而另一方面,buffer_head
结构体代表的是一个缓冲区,它描述的仅仅是硬盘中的一个块。因为缓冲区头关联的是单独页中的单独磁盘块,所以可能会引起不必要的分割,将请求按块分为单位划分,只能靠以后才能再重新组合。由于bio结构体是轻量级的,它描述的块可以不需要连续存储区,并且不需要分割I/O操作。
利用bio结构体代替buffer_head
结构体还有以下好处:
- bio结构体很容易处理高端内存,因为它处理的是物理页而不是直接指针。
- bio结构体既可以代表普通I/O,同时也可以代表直接I/O(指那些不通过页高速缓存的I/O操作)。
- bio结构体便于执行分散-集中块I/O操作,操作中的数据可取自多个物理页面。
- bio结构体相比缓冲区头属于轻量级结构体。因为它只需要包含块I/O操作所需的信息就可以,不用包含与缓冲区本身相关的不必要信息。
4.请求队列
块设备将他们挂起的块I/O请求保存在请求队列中,该队列由reques_queue
结构体表示,包含一个双向请求链表以及相关控制信息。请求队列中的每一项都是一个单独的请求,由reques
结构体表示。因为一个请求可能要操作多个连续的磁盘块,所以每个请求可以由多个bio结构体表示。
5.I/O调度程序
如果简单以内核产生的请求的次序向块设备发送请求的话,性能肯定让人难以接受。磁盘寻址是整个计算机中最慢的操作之一,所以尽量缩短寻址时间是提高系统性能的关键。内核在提交磁盘请求时,先执行名为合并与排序的预操作,这种预操作可以极大地提高系统的整体性能。在内核中负责提交I/O请求的子系统称为I/O调度程序。I/O调度程序将磁盘I/O资源分配给系统中所有挂起的块I/O请求。它决定队列中的请求排列顺序以及什么时候发送请求到块设备。坦率的说,一个I/O调度器可能为了提高系统整体性能而对某些请求不公平。
主要的I/O调度算法包括:Linus电梯算法,最终期限I/O调度程序,预测I/O调度程序,完全公正的排队I/O调度程序,空操作的I/O调度程序。具体可以查看书本内容。
参考
《Linux内核设计与艺术》