linux驱动由浅入深系列:块设备驱动之二(从用户空间的read、write到实际设备物理操作整体架构分析)

linux驱动由浅入深系列:块设备驱动之一(高通eMMC分区实例)

linux驱动由浅入深系列:块设备驱动之二(从用户空间的read、write到实际设备物理操作整体架构分析)
linux驱动由浅入深系列:块设备驱动之三(块设备驱动结构分析,以mmc为例)

在第一篇文章中介绍了块设备在应用层的操作,本文一起了解一下从用户空间发起read、write后直到相应设备驱动具体对磁盘发起操作的过程。这个过程在linux中的大致框图如下:


1,用户空间使用统一的读写函数操作所有块设备

2,虚拟文件系统屏蔽了磁盘上各种不同文件系统的差异,为用户空间提供统一的操作

3,Cache

当用户发起文件访问请求的时候,首先回到Disk Cache中寻址文件是否被缓存了,如果在Cache,则直接从cache中读取。如果数据不在缓存中,就必须要到具体的文件系统中读取数据了。

使用缓存读写IO叫做buffered IO,有很多好处:
a,缓存 I/O 使用了操作系统内核缓冲区,在一定程度上分离了应用程序空间和实际的物理设备。
b,缓存 I/O 可以减少读盘的次数,从而提高性能。
但在某些场景下不希望操作系统进行缓存,可以使用直接IO,Direct IO。使用的方法是打开文件时,添加O_DIRECT参数。这样cache就被跳过了。

4,文件系统
不同文件系统是指磁盘上存放文件的格式。这部分可以参考本博客的另外两篇博文:
文件系统系列之一:fat文件系统的结构分析
文件系统系列之二:ext3/vfs文件系统的结构分析
文件系统识别到各自文件的组织形式,给出本次请求在磁盘上的地址和扇区个数。

5,通用块层

这部分是把块设备的通用操作抽象了出来,进行归类组合成了若干个对磁盘的请求队列,这样最大限度的提高了磁盘读写的效率。

6,IO队列调度
 I/O Scheduler Layer I/O调度层负责采用某种算法(如:电梯调度算法)将I/O操作进行排序。

电梯调度算法的基本原则:如果电梯现在朝上运动,如果当前楼层的上方和下方都有请求,则先响应所有上方的请求,然后才向下响应下方的请求;如果电梯向下运动,则刚好相反。

7,块设备驱动
块设备驱动中负责读取请求队列,发起对物理硬件的数据请求。

在上面对整个过程简要介绍后,下面来分析一下对应的源码

相应源码文件夹(本文以linux-3.16为例):
linux-3.16.56/block 通用块层源码路径
linux-3.16.56/fs 文件系统源码路径
linux-3.16.56/drivers/block 块设备驱动源码路径

1,用户空间调用的read/write函数实际是kernel暴露给用户空间的系统调用。关于这部分可以参考本博客的另外一篇博客,在此不在赘述:
linux系统调用实现的介绍
2,之后经过虚拟文件系统、cache、相应文件系统的组织最后会通过ll_rw_block()函数向通用块层发出请求。(关于虚拟文件系统中的细节以后有机会完善在文件系统系列的文章中...)
linux-3.16.56 / fs / ext4 / inode.c

如上图中ext4文件系统的实际读操作就是由ll_rw_block()函数向bio层发起。

3,ll_rw_block()函数的定义在:
linux-3.16.56/fs/buffer.c#3115

其中会遍历文件系统中的buffer_head数组,然后调用submit_bh()函数

4,submit_bh()函数中会组织bio(通用块层)机构体,向bio层提交
linux-3.16.56/fs/buffer.c

其中为bio结构开辟了空间,组织bio结构体,然后调用submit_bio()

5,submit_bio()的定义在bio层了,从源码路径可以看到终于离开了文件系统的文件夹
linux-3.16.56/block/blk-core.c

其中对bio结构体做了必要的检查,然后调用了generic_make_request()。其中细节暂时忽略。

6,generic_make_request()是很重要的一个函数了,这个与linux2.6内核中有所区别。
linux-3.16.56/block/blk-core.c

其中代码没有两行,大片的注释解释了代码编写者不得已这么构建程序的原因and so on.简单来看就是对bio列表合理化处理后,调用bdev_get_queue()得到相应的块设备请求队列,然后调用q->make_request_fn()将q和bio提交,bio操作会被入队到相应设备的请求队列,之后就是相应块设备驱动的工作了,下一篇文章介绍。

7,最后再看一眼make_request_fn() 函数的实现吧。
经查找发现q->make_request_fn的赋值位置在:
linux-3.16.56/block/blk-settings.c

blk_queue_make_request函数调用对mfn赋值的位置在
linux-3.16.56 / block / blk-core.c

其中blk_queue_bio是就是我们关注的make_request_fn函数指针的具体实现了,代码位置在:
linux-3.16.56 / block / blk-core.c

其中最关键的就是使用elv_merged()函数对请求队列按电梯调度算法进行了重新排序。
这个函数的后半段如下:

其中调用__blk_run_queue()执行队列的处理函数,函数定义在
linux-3.16.56 / block / blk-core.c

其中只有__blk_run_queue_uncond() 函数,定义在:
linux-3.16.56 / block / blk-core.c

其中q->request_fn就是相应块设备驱动中定义的队列处理函数了,见下篇。

猜你喜欢

转载自blog.csdn.net/RadianceBlau/article/details/79465474