块设备
设备文件通常分为两大类型:块设备和字符设备。块设备指的是可以随机访问固定大小数据片的硬件设备,例如硬盘,软盘,闪存等。字符设备指的是以字符流方式被访问的硬件设备,比如键盘,串口和网卡。块设备的管理要比字符设备细致复杂得多,因为字符设备仅仅需要控制一个位置—当前位置,而块设备访问的位置可以在介质的不同区间前后移动。所以内核提供了一个专门的子系统来管理块设备,被称为块IO层。块设备中最小的可寻址单元是扇区,扇区大小一般是2的整数倍,比如最常见的大小为512字节。扇区的大小是设备的物理属性,块设备无法对比扇区还小的单元进行寻址和操作。块是文件系统的一种抽象,内核执行的所有磁盘操作都是按照块进行的。由于扇区是设备的最小可寻址单元,所以块不可能比扇区还小,同时内核要求块大小是2的整数倍,且不能超过一个页的长度。
缓冲区和缓冲区头
当一个块被调入内存时,它要存储在一个缓冲区中。每个缓冲区与一个块对应,它相当于磁盘块在内存中的表示。每个缓冲区都有一个对应的描述符,存储缓冲区相关的控制信息和缓冲区到物理内存的映射关系,该描述符用buffer_head结构体表示,称作缓冲区头。b_state域表示缓冲区的状态,b_count域表示了缓冲区的引用计数,与该缓冲区对应的磁盘物理块由b_blocknr域索引,其值为b_bdev域指明的块设备中的逻辑块号。与缓冲区对应的内存物理页为b_page域,b_data域直接指向对应的块,块的大小由b_size域表示。
bio结构体
内核为块IO操作引入了一种新型的,灵活的,轻量级容器,即bio结构体。该结构体表示了正在现场的(活动的)以片段(segment)链表形式组织的块io操作。一个片段就是一小块连续的内存缓冲区,这样使得即使一个缓冲区的数据分散在多个离散的内存上,bio结构体也能对内核保证IO操作的执行。bio结构体的bi_io_vec域指向一个bio_vec结构体数组,该结构体链表包含一个特定I/O操作所需要使用到的所有片段。每个bio_vec结构都是一个 请求队列 块设备将它们挂起的块IO请求保存在请求队列中,该队列由request_queue结构体表示,包含一个双向请求列表和相关控制信息。队列中的请求由request表示,因为一个请求可能要操作多个连续的磁盘块,所以每个请求可以由多个bio结构体组成。虽然磁盘上的块必须连续,但是在内存中这些块并不一定要连续。 IO调度程序 如果简单地以内核产生请求的次序将请求直接发向块设备的话,性能肯定难以让人接受。磁盘寻址是整个计算机中最慢的操作之一,所以尽量缩短寻址时间无疑是提高系统性能的关键。在内核中负责提交IO请求的子系统成为IO调度程序,IO调度程序将磁盘IO资源分配给系统中所有挂起的块IO请求。具体地说,这种资源分配是通过将请求队列中挂起的请求合并和排序来完成的。合并是指将两个或多个请求合并成一个新请求,考虑这种情况:两个IO请求访问的磁盘扇区相邻,那么这两个请求便可以合并为一个对相邻磁盘扇区访问的新请求。排序是指将请求队列中的所有请求按照扇区增长方向有序排列,这样可以使得磁盘头以直线方向移动,缩短了所有请求的磁盘寻址时间,减少系统开销。Linus电梯是一种IO调度程序,其可以执行合并和排序预处理。当有新请求加入到请求队列时,linus电梯首先会检查所有挂起的请求是否可以和新请求合并。如果合并尝试失败,那就会寻找一个合适的插入点(新请求在队列中的位置必须符合以扇区方向有序排列的原则),如果没有找到插入点或者发现队列中有驻留时间过长的请求,新请求就会被放到队列尾。最终期限IO调度程序是为了解决linus电梯所带来的饥饿问题而提出的。最终期限IO调度程序除了会将请求像linus电梯一样合并和排序存储在请求队列之外,还会将读请求保存在一个读FIFO中,将写请求保存在一个写FIFO中。对于普通操作来说,最后期限IO调度程序将请求从排序队列中读出,再推送到派送队列中。如果读FIFO或者写FIFO队列的头请求超时,那么便从读FIFO或者写FIFO队列读取请求,并推入到派送队列中。默认情况下,读请求的超时时间是500ms,写请求的超时时间是5S。预测IO调度程序,在最终期限IO调度程序的基础上,增加了预测启发能力。预测IO调度程序跟踪并且统计每个应用程序的块IO操作行为习惯,并且请求超时前提交请求时,会延迟几ms的时间,这样使得临近磁盘位置的操作请求可以立刻得到处理,而不会浪费磁盘寻址。排队IO调度程序为每个进程维护了一个请求队列,然后调度程序以时间片进行轮转调度队列,从每个进程队列中选取固定的请求数,再进行下一轮调度。空操作IO调度程序不做排序,也不做其他形式的预寻址操作,不过空操作IO调度程序对相邻的请求进行合并处理。空操作IO调度程序主要用于闪存等支持随机访问的块设备,因为这类设备没有寻址的负担。