□通用块设备层
Linux内核中与块设备所存储数据相关的元素有sector(扇区)、block(块)、segment(段)、page(页),它们一起被用于描述内核通用块设备层,如图7-2所示。其中,sector代表存储控制器每次能读写的最小数据单位(512B),I/O调度器和具体设备驱动也能以sector为单位进行数据操作;block是文件系统存储文件数据块的最小单位(通常是1 KB),通常由多个sector组成;segment是一个内存页,它包含有多个block的存储数据内容;page frame(4 KB)是存储cache中由一个或多个segment组成的最小数据单位。
通用块设备层将会处理所有与块设备相关的数据请求,比如将数据拷贝至内存、管理逻辑卷(LVM)、I/O请求调度、拓展磁盘控制器功能等,与这些紧密相关的一个重要数据结构是bio。Bio中包含一系列将要与正在执行的块设备操作链表,以提交块设备I/O请求函数make_request()为例,它首先会检查bio->bi_sector以确定此请求没有超过设备边界,然后将此请求加入与此设备关联的请求队列中,返回值为指向请求队列的结构体指针。当请求被加入队列后,就需要I/O调度器对这些请求进行调度了。
图7-2 通用块设备层数据单位示意图
I/O调度算法
●Noop
最直接的调度算法,它在请求队列的FIFO(First In First Out)基础上,添加了相邻扇区请求的合并机制。新加入的请求会被排列至队列末尾,直到前面的请求被处理完,它才会被执行。这种算法比较适用于SSD等快速存储设备,对于读写周期相对较长的机械盘来说效果可能并不突出。
●CFQ(www.xing528.com)
CFQ是为了实现完全公平的排队(Complete Fair Queueing)而发明的算法,它尽量保证每一个进程都获得相对公平的I/O带宽。当新的请求来到时,内核会根据这个请求进程所在线程组(thread group)的标识符哈希值将其插入至相应队列中。同时内核会默认维持64个队列,并以round robin形式对这些队列进行扫描,选中队列中的某些请求并将它们移动到此队列的末尾,从而保证此队列中的请求都获得相对公平的I/O带宽。这种算法目前是诸多发型版中的默认调度算法,在多数桌面与服务器场景中表现都比较平稳,适用于读写周期较长的机械硬盘。
●Deadline
Deadline算法中内核则只需要维持4个队列,其中的两个排序队列分别包含读请求和写请求,请求是根据起始扇区编号排序的。另外两个deadline队列包含相同的读和写请求,但这是根据它们的“截止时间”排序的。默认的读请求截止时间为500ms,写请求截止时间为5秒,当队列中的第一个请求的操作时间达到对应的截止时间时,就会被移动到队列末尾。这种算法一般适用于尽量避免写操作被“饿死”的数据库服务场景。
●Anticipatory
这是一种启发式的调度算法,类似deadline算法,它也维持了4个请求对列。区别在于当它处理完一个请求后并不会直接返回处理下一个请求,而是默认等待6ms,如果期间有新来并被预测为针对当前扇区邻居的请求,那么会直接处理新请求。当等待时间结束后,调度器才开始处理队列中的下一个请求。这种算法适合于随机I/O和顺序I/O请求数量相当的场景,比如文件服务。
□块设备驱动
Linux存储栈的中实际物理设备之上就是真正的块设备驱动了,它从上层调度器获得I/O请求后开始向存储设备读写数据。
块设备驱动的编写与其他驱动类似,也需要定义一系列设备操作。但是不同于普通驱动,它需要额外定义中断处理程序,以允许DMA操作将数据直接从硬盘拷贝至内存。另外,SCSI设备驱动的由上中下3层实现,含有设备节点的上层驱动用于与通用块设备层交互,下层控制具体的存储设备,中间层则类似网络路由功能,它将收到的请求按照一定规则传递至不同的设备节点以诸如容错等额外功能。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。