块IO层(Linux内核源码分析)

背景

本篇博客重点分析块IO层,试图了解在linux源码中是如何实现块IO的。

基本知识

块设备与字符设备

块设备与字符设备都是物理外设。简单来说,块设备与字符设备的最大区别在于块设备都随机对数据片段进行读写的,而字符设备都以顺序对数据片段进行读写的。
比如磁盘、CD-ROM盘、闪存就属于块设备。键盘、串口属于字符设备。

扇区与块

扇区是块设备的最小寻址单元,也就是说,是物理上的最小单元。而块则不同,块是文件系统进行IO的最小单元,就是说,块是逻辑上的最小单元。所以,对于编程者来说,可能更为在意的是块的概念。
由此看来,扇区与块的关系就很明了了,块一定是扇区整数倍,而且一定要小于内存中一个页的长度。通过扇区的大小是512字节。

页与块

页是内存中的一个基本管理单元。
块是文件系统的一种抽象,内核执行的所有磁盘操作都是按照块进行的。

源码分析

我们可以先思考一下这个问题,从磁盘这样的块设备中要读取数据到内存中,从整体来看内核需要干什么?
首先是不是需要知道磁盘哪块的数据要放入到内存中的哪个位置?
然后需要知道是如何将数据放入内存中,也就是以怎样的方式?
知道了前面的之后,就需要考虑一个问题,内核中需要处理的块IO一定不只一个,如何在一小段时间内来了许多需要进行块IO的请求,这个时候该怎么处理呢?难道不需要管理随便的找块IO请求进行处理吗?
接下来我们就通过源码逐个分析这些问题。

buffer和buffer_head

从磁盘到内存,我们首先需要知道它们的对应关系是什么,前面基础里提到,内存的基本管理单元是页,磁盘的基本管理单元是块(逻辑上来说)。所以说的具体点就是,哪个块对应哪个页。
当一个块的数据被调入内存中,会有一个缓冲区与之对应,这个缓冲区就相当于块在内存中的表示。一个缓冲区就是buffer。buffer_head结构体就是用来描述它们之间对应关系的。
接下来我们就直接看看这个结构体(位于include/linux/buffer_head中)
块IO层(Linux内核源码分析)_第1张图片
70的b_bdev指向具体的块设备。
66中的b_blocknr指向逻辑块号。我们就知道了块是哪个块。
64的b_page指向缓冲区位于哪个页面之中。
68的b_data指向页面中的缓冲区开始的位置。
67的b_size显示了缓冲区的大小是多少。
从这几个域我们就知道了磁盘与内存的映射关系了。在内存页中,数据起始于b_data,到b_data+b_size。
其它还有一些域,如b_state显示了buffer的状态。b_count表示了这个buffer_head的被用次数,若被用了就需要给它进行原子加1操作,这样其它地方就不能再重复使用了。
显然,只知道内存与磁盘数据的对应关系还不行,我们还需要知道在内存中的具体的区域,也就是需要知道数据的容器。这个容器就是结构体bio。

bio结构体

我们先来直接看看bio结构体的源码。
块IO层(Linux内核源码分析)_第2张图片
95行bi_io_vec指向了一个bio_vec结构体数组。这个bio_vec结构体是一个比较关键的东西。我们看一下这个结构体。
这里写图片描述
此结构体中的page大家应该比较熟悉,这个内存中的一个页面。bv_offset表示页中数据的偏移量,bv_len表示这个数据的长度。这下我们应该明白了。bio结构体拥有bio_vec结构体数组,每个结构体包含了一个页面中的一些“片段”。而这“片段”就是之前buffer_head描述的磁盘与内存中对应的块的连续数据。换句话说,“片段”由连续的内存缓冲区组成。
现在再回过头来看bio结构体。
既然知道了bi_io_vec指向bio_vec结构体数组的首地址。那么肯定就得知道数组的长度与当前正操作的bio_vec的索引。
这就是72行bi_vcnt域和73行bi_idx域。bi_vcnt记录数组的数量,bi_idx记录当前bi_vec结构体的索引数。
至此,磁盘与内存的映射关系的结构体我们了解了,块在内存中的数据容器的结构体我们也了解了。现在有个问题,若内核中有多个块IO请求,那么这些IO请求就按照先来先处理的方式来进行吗?很显然这样不合适,我们需要用一些IO调度程序来管理这些IO请求。具体的IO调度程序这里就暂时先不讲了。以后有时间再分析具体的IO调度程序。

你可能感兴趣的:(linux内核源码分析)