Bootstrap

bio,request,request_queue的关系

通用块层的核心数据结构称为bio描述符,它描述了块设备的io操作。每一个bio结构都包含一个磁盘存储区标识符(存储区中的起始扇区号和扇区数目)和一个或多个描述与IO操作相关的内存区段(bio_vec数组)
bio结构中的字段

/*
 * main unit of I/O for the block layer and lower layers (ie drivers and
 * stacking drivers)
 */
struct bio {
	struct bio		*bi_next;	/* request queue link 链接到请求队列中的下一个bio*/
	struct block_device	*bi_bdev;  /* 指向块设备描述符的指针 */
	unsigned int		bi_flags;	/* status, command, etc bio的状态标志*/
	int			bi_error;
	unsigned long		bi_rw;		/* bottom bits READ/WRITE, I/O操作标志(低位是读写位,高位是优先级)
						 * top bits priority
						 */

	struct bvec_iter	bi_iter;

	/* Number of segments in this BIO after
	 * physical address coalescing is performed.
	 */
	unsigned int		bi_phys_segments;  //合并之后bio中物理段的数目

	/*
	 * To keep track of the max segment size, we account for the
	 * sizes of the first and last mergeable segments in this bio.
	 */
	unsigned int		bi_seg_front_size;  
	unsigned int		bi_seg_back_size;

	atomic_t		__bi_remaining;

	bio_end_io_t		*bi_end_io;  //bio的I/O操作结束时调用的方法

	void			*bi_private;  //通用块层和块设备驱动程序的I/O完成方法使用的指针
#ifdef CONFIG_BLK_CGROUP
	/*
	 * Optional ioc and css associated with this bio.  Put on bio
	 * release.  Read comment on top of bio_associate_current().
	 */
	struct io_context	*bi_ioc;
	struct cgroup_subsys_state *bi_css;
#endif
	union {
#if defined(CONFIG_BLK_DEV_INTEGRITY)
		struct bio_integrity_payload *bi_integrity; /* data integrity */
#endif
	};

	unsigned short		bi_vcnt;	/* how many bio_vec's bio的引用计数器*/

	/*
	 * Everything starting with bi_max_vecs will be preserved by bio_reset()
	 */

	unsigned short		bi_max_vecs;	/* max bvl_vecs we can hold bio_vec数组中允许的最大段数*/

	atomic_t		__bi_cnt;	/* pin count */

	struct bio_vec		*bi_io_vec;	/* the actual vec list 存放段的数组*/

	struct bio_set		*bi_pool; //备用的bio内存池

	/*
		为了避免重复申请少量的bio_vec,使用内联一定数量的bio_vec数组,将它放在bio结构的尾部。
	 * We can inline a number of vecs at the end of the bio, to avoid
	 * double allocations for a small number of bio_vecs. This member
	 * MUST obviously be kept at the very end of the bio.
	 */
	struct bio_vec		bi_inline_vecs[0]; /*一般一个bio就一个段,bi_inline_vecs就
    可满足,省去了再为bi_io_vec分配空间*/
};

bio中的每个段是由一个bio_vec数据结构描述的,bio_vec数据结构如下

//bio段就是描述所要读或写的数据在内存中位置
struct bio_vec{
     struct page* bv_page //指向段在页框描述符的指针
     unsigned int bv_len //段的字节长度
     unsigned int bv_offset //页框中数据的偏移量
} 

bio中的bi_io_vec字段指向bio_vec数组的第一个元素,bi_vcnt则说明了数组当前元素的个数,而bi_max_vecs则限定了数组的长度。
下面两幅图可以很好的说明bio与bio_vec的关系
在这里插入图片描述
在这里插入图片描述
在通用块层启动一次新的IO操作时,会调用bio_alloc函数分配一个新的bio结构,bio是由slab分配器分配的。内核同时也为bio_vec结构分配内存池。

参考:https://blog.csdn.net/zhufengtianya/article/details/42145985?utm_source=copy

bio与bio段的关系
一个bio可能有很多个bio段,这些bio段可能在内存上不连续(位于不同的页),但他们在磁盘上对应的位置是连续的。一般上层构建bio的时候都是只有一个bio段,可以参考_submit_bh函数。

在块io操作期间bio的内容一直保持更新,例如,块设备驱动在一次分散聚集DMA操作中不能一次完成全部数据的传送,那么bio的bi_idx就会更新来指向待传送的第一个段。
bio,request,request_queue的学习
bio:代表了一个io请求

request:一个request中包含了一个或多个bio,为什么要有request这个结构呢?它存在的目的就是为了进行io的调度。通过request这个辅助结构,我们来给bio进行某种调度方法的排序,从而最大化地提高磁盘访问速度。

request_queue:每个磁盘对应一个request_queue.该队列挂的就是request请求。

具体如下图:(有颜色方框头表示数据结构的名字)
在这里插入图片描述
请求到达block层后,通过generic_make_request这个入口函数,在通过调用一系列相关的函数(具体参见我另一篇博客)把bio变成了request。具体的做法如下:如果几个bio要读写的区域是连续的,即积攒成一个request(一个request上挂多个连续的bio,就是我们通常说的“合并bio请求”),如果一个bio跟其他的bio都连不上,那它就自己创建一个新的request,把自己挂在这个request下。当然,合并bio的个数也是有限的,这个可以通过配置文件配置。

对于上段补充一点:上层的一次请求可能跨越了多个扇区,形成不连续的扇区段,那么该请求构造的每个bio对应着一个连续的扇区段。故一个请求可以构造出多个bio。

合并后的request放入每个设备对应的request_queue中。之后设备驱动调用peek_request从request_queue中取出request,进行下一步处理。

这里要注意的是,在实现设备驱动时,厂家可以直接从request_queue中拿出排队好的request,也可以实现自己的bio排队方法,即实现自己的make_request_fn方法,即直接拿文件系统传来的bio来自己进行排队,按需设计,想怎么排就怎么排,像ramdisk,还有很多SSD设备的firmware就是自己排。(这里就和我另一篇博客,说到为什么generic_make_request要设计成一层递归调用联系起来)。

网上有个问答也是我的疑惑:这里我写一遍,加强记忆

bio结构中有bio_vec数组结构,该结构的的数组可以指向不同的page单元,那为什么不在bio这一级就做了bio合并工作,即把多个bio合并成一个bio,何必加入一个request这么麻烦?

答:每个bio有自己的end_bio回调,一旦一个bio结束,就会对自己进行收尾工作,如果合并了,或许有些bio会耽误,灵活性差。

;