Linux 块设备之Block Layer层架构演变

前言

Block Layer层在整个I/O中负责承上启下,上接文件系统,下接块驱动。

Linux 块设备之Block Layer层架构演变

一、1.0版本

首先我们来了解几个重要的数据结构

1.1 bio

bio代表了一次I/0请求,代表一个块设备的一个扇区或者多个连续扇区的数据请求,扇区是块设备的最小访问单元,bio是文件系统发给Block Layer层的。

1.2 request

request代表块设备可以处理的一次任务单元,一个request由一个bio或者多个扇区相连的bio组成。

1.3 架构图

Linux 块设备之Block Layer层架构演变

1.4 小结

Block Layer将文件系统提供的bio变成request,然后交给块设备驱动处理。

二、2.0版本

目前只支持单个进程访问块设备。为了可以支持多进程,我们在块设备驱动中创建一个队列:request_queue,所有的进程发起bio生成的request,会被投递到request_queue,块设备循环处理request_queue中的request。

2.1 request_queue

每一个块设备都有一个工作队列,request_queue,保存准备就绪待处理的request。

2.2 架构图

Linux 块设备之Block Layer层架构演变

 

2.3 小结

这样子就实现了多个进程同时访问块设备,而且在新的一个bio变成request之前,可以先看看该bio能否合并到request_queue的某个request,这样子就可以提升bio处理的效率。

三、3.0版本

好像看起来2.0已经挺完美了,但是request_queue会有一把锁控制,如果多个进程短时间发起多个bio,多个进程不断竞争锁,这样子性能不是很好,我们就引入了plug。

3.1 plug

plug翻译成中文就是塞子,每一个进程会有一个plug,plug是一个request的list,先将一个进程短时间发起的所有bio生成request先堆积到当前进程的plug list,然后在等待特定时机释放plug中的requests到request_queue,这样子可以提升bio处理的效率。

3.2 架构图

Linux 块设备之Block Layer层架构演变

3.3 小结

架构越来越完美了,除了上面的流程,在新的一个bio变成request之前,先看看能不能合并到plug里面的某个request中,如果不行在,再看看能否合并到request_queue里面的某个request中,如果不行再变成request,然后放到plug list中,等待积攒一定量之后,统一放置request_queue里面。

四、4.0版本

看起来好像3.0已经很完美了,但是我们会发现3.0有两个明显的优化点。

优化点1:

传统的块设备是机械磁盘,这种硬件是靠一个磁头不断移动进行访问数据的,如果我们可以把所有request排序,这样子就可以提升块设备的访问效率。

举个例子:一个外卖员需要送很多外卖到一个办公楼,他将外卖按楼层排序合并,然后一层层的送,肯定比随机拿一个外卖,然后一层层的送的速度快。

优化点2:

每个块设备单位时间处理request的次数是有限的,如果我们可以提升前台的进程request被处理的优先级,这样子就可以带来更好的用户体验。

4.1 scheduler

翻译成中文就是调度器,scheduler就是可以解决上述的两个优化点,scheduler会在内部创建自定义的各种队列来存放plug释放出来的request,然后scheduler对这个所有的request进行一个调度,按照特定的规则再分发给request_queue,让块设备处理。

4.2 架构图

Linux 块设备之Block Layer层架构演变

4.3 小结

有了scheduler,我们就可以实现所有request的重新排序甚至合并,还可以对不同进程的request进行不同的优先级控制,目前linux支持的scheduler有:CFQ,deadline等

5、总结

学习Block Layer层,其实就是学习以上几个重要的部分:bio,request,plug,scheduler,request_queue,而且linux的single-queue架构就是和4.0版的架构差不多,目前手机上基本采用这个single-queue的架构。

linux目前除了支持single-queue,还支持multi-queue,我就不展开讲的,整体的结构很类似。

6、结尾

整个流程好像看起来很爽,其实我们根本没有关注过进程的调度,我说的进程就是task_struct,多个进程同时发送bio,最后将bio变成request送给了scheduler进行调度,这些代码运行在各自的进程上,然后scheduler再将request分发给块设备处理这个又运行在另一个单独的进程上。如何让多个进程发完request之后休眠,如何让块设备驱动处理完之后唤醒这些休眠的进程。这个唤醒的关键点:在于bio结构体中的bio_end_io_t *bi_end_io这个callback函数指针。