您的位置:首页 > 产品设计 > UI/UE

block bio queue request等

2015-12-05 10:36 387 查看
参考linux API https://www.kernel.org/doc/htmldocs/kernel-api/
1. queue_flag_set_unlocked(QUEUE_FLAG_NOMERGES, ns->queue);

不允许对队列的request进行merge操作

2. blk_queue_virt_boundary(ns->queue, dev->page_size - 1);

to ensure there are no 'holes' in the presented
sg list (all segments in the middle of the list need to be of PAGE_SIZE).Setting virt_boundary_mask to PAGE_SIZE - 1 guarantees we'll never see
such holes

The block layer can reliably guarantee that SG lists won't
contain gaps (page unaligned) if a driver set the queue
virt_boundary.

With this setting the block layer will:
- refuse merges if bios are not aligned to the virtual boundary
- split bios/requests that are not aligned to the virtual boundary
- or, bounce buffer SG_IOs that are not aligned to the virtual boundary

理解是block层保证request bio中的数据是page对齐的,不对齐的话会分成多个request

对磁盘的抽象genhd.c和对分区的抽象:partition-generic.c和partitions目录下的文件

l 上层文件系统会把对文件访问转变为对多个sector的访问,这些sector很可能在内存中是分离的。所以需要一种数据表示方法,用来表示要读写的数据内容。这这个数据结构叫做bio(很奇怪的是这里为什么不直接使用scsi使用的scatterlist?)

l scsi相关

n 新的scsi标准有DIF/DIX的数据保护机制,无论对于读还是写的数据,都需要一个数据完整性的校验,由于在通用块层存储数据的结构体是bio,所以对其进行校验的文件叫做bio-integraty.c。这个文件完成的是与内存相关的设置,真正的算法在blk-integraty.c中定义的一系列钩子函数。不同的硬件会注册不同的计算方法供本层调用。也就是说,这里实际实现的是DIX协议。

n 本层要知道scsi的接口,本层定义了bsg(blockSCSI generic device)的v4接口,在bsg.c和bsg-lib.c

n t10保护的支持算法t0-pi.c,scsi的ioctl:scsi_ioctl.c

l 连接本层各个功能组件的核心程序:blk-core.c,还包括一些辅助文件实现些特定的周边。这一部分包括

n 内核执行这部分代码不是阻塞的,而是使用内核线程完成的,使用的是kblockd,其定义和相关功能位于blk-core.c中

n request的处理(bio只是数据的存储结构,但是一个命令请求不只有数据,还需要有其他控制和状态信息,这些信息和bio一起被组织到request中),但是要注意的是request和bio都只是本层的数据结构,request服务于电梯算法,bio用于盛放用户传进内核的数据

u 将用户数据映射到bio结构体的blk-map.c

u 将request中的bio数据映射到下层(scsi)使用的scatterlist结构体的处理程序blk-merge.c

u request如果超过了一定的时间需要被time out掉,代码在blk-timeout.c

u request请求到的数据在本层需要有缓冲,可以从中提取提交到上层上层所需要的数据,而丢弃或者缓存一部分上层没有要到的数据。这种行为叫做bounce,功能定义在bounce.c中

n 队列(queue)处理(对于块设备的一系列命令,需要队列缓存,并且这一层最重要的,队列中的各个命令有可能可以合并为一个,例如读取连续的数据的两个命令,由于每次存取数据的量越大,越节省时间,所以这一步是提高效率的关键)

u linux的设计者将对queue的插入执行操作单独的提取出来放到blk-exe.c中

u 对队列的属性进行设置的blk-settings.c

u 对队列中的request添加ID(tag),可以通过该tag直接找到该request,实现在blk-tag.c

u 凡是通信管道都要考虑流量控制问题。queue可以有多个来源,如果某个来源瞬间提交了过多的bio,那么其他来源的bio就可能饥饿。防止这种现象发生需要给队列针对某一个来源添加一个阈值,这个阈值的控制在blk-throttle.c

n 电梯算法接口。上一条说的合并多个request的操作,需要有合并的算法,合并的算法有很多,但是核心部分要为这些算法提供调用的接口函数

n 提交请求。当电梯算法被执行完,多个request和其对应的bio被合并,这个bio就需要被提交到下层(scsi的上层)去实际的执行发送。发送完毕还要执行回调。这部分代码也在这里提供。

l 电梯算法:电梯算法在queue上执行合并操作,是性能优化的关键。代码位于elevator.c,deadline-iosched.c,cfq-iosched.c,noop-iosched.c,还有提供优先级的ioprio.c

l 对于IO上下文的处理。IO上下文是在request上层的数据结构,如果说通用块层处理的request级别的数据结构,文件系统就是处理的IO上下文。而文件系统层次包括同步和异步两种数据模式,这里的IO上下文(io_context)主要是用在异步,异步IO在提交IO请求前必须要初始化一个IO上下文,一个IO上下文会包含多个request。通用块层对IO上下文的处理函数放在blk-ioc.c。

l 正常的逻辑是发送了IO命令,命令请求完毕后会调用回调函数。但是,通用块层允许poll操作,就是没有回调函数,请求执行完后需要用户手动查询和处理。这部分代码在blk-iopoll.c

l 对本层命令队列的处理可以有一个CPU,也可以有多个。如果多个,就需要对队列进行特殊的优化,叫mq,相关代码位于blk-mq.c、blk-mq-cpu.c、blk-mq-cpumap.c、blk-mq.h、blk-mq-sysfs.c、blk-mq-tag.c、blk-mq-tag.h中

l 内核处理命令的返回结果,在通用块层不可能是使用硬中断,所以这里的回调使用的是软中断,定义在blk-softirq.c

l 实现sysfs接口,定义在blk-sysfs.c,实现cgroup子系统的blk-cgroup.c

l 其他的辅助功能组件:将内容flush进磁盘的blk-flush.c、辅助函数blk-lib.c、用来解析磁盘信息返回值的cmdline-parser.c、提供ioctl接口的compat_ioctl.c,ioctl.c

l

从以上可以看出,这一部分的关键组件是:request、queue、bio、elevator和磁盘与分区的抽象。

数据完整性校验
如果要对bio进行数据完整性校验,需要调用bio_integraity_alloc给bio分配对应的空间,之后通过bio_integraty_add_page给bio添加额外的空间,用bio_free就会自动删除掉分配的空间。

具体的计算bip(dif)的算法由具体的驱动提供,驱动调用的是blk_integraty_register来注册自己的计算函数。

在文件系统中,可以通过/sys/block/<bdev>/integraty/目录下的write_generate和read_verify来控制是否执行读写校验。

大部分情况下,数据完整性对于文件系统是透明的,但上层的文件系统仍可以显示的使用DIX。在bio_integraty_enabled为1的情况下,上层调用bio_integrity_prep为bio准备bip。

磁盘设备在注册是可以生成blk_integrity结构体,体面就是存放具体的读写校验函数和tag的大小。

设备抽象
linux的通用快层对磁盘的抽象是gendisk结构体,该层以下的各种设备都是这个结构体的一种。例如scsi磁盘设备scsi_disk就是gendisk的一种。

对于分区的抽象是structpartition。

设备驱动抽象

设备驱动操作抽象
block_device_operations

对设备进行驱动

设备操作指令抽象
对设备进行指令操作的结构体是struct request,连接通用快层和下层设备指令操作的数据结构是bio,bio在request中,被上层识别,也被下层识别。

磁盘检测
这部分描述当发现插入了磁盘或者是删除了磁盘时,内核是如何反应的。

BIO和bio_set
BIO是通用块层表达数据的方式,其将用户传递进来的数据转换为bio存储,bio又包含进了request。多个bio可以组成链接,bio中内生提供链表结构。

struct bio {

struct bio *bi_next; /*BIO 链表*/

struct block_device *bi_bdev; //文件系统层的块设备抽象

unsigned long bi_flags; /* status, command, etc */

unsigned long bi_rw; /* 标示是读还是写的标志位 */

struct bvec_iter bi_iter;

unsigned int bi_phys_segments;

/*

* 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全部执行结束的回调函数

void *bi_private;

unsigned short bi_vcnt; /* how many bio_vec's */

unsigned short bi_max_vecs; /* max bvl_vecs we can hold */

atomic_t bi_cnt; /* pin count */

struct bio_vec *bi_io_vec; /* the actual vec list */

struct bio_set *bi_pool;

/*

* 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有多个bio_vec,一个bio_vec叫一个segment。由于上层提交来的bio中的bio_vec,所以bio本身也是可以合并的。但是每个queue可以有标志位QUEUE_FLAG_NO_SG_MERGE控制是否允许bio的合并。如此,bio就有了两种统计口径:bi_vcnt表示bio没有经过自身合并的bio_vec数目,bi_phys_segments表示将物理连续的bio_vec算成一个后统计出来的段总数。这里需要注意的是:bio的段总数并不是单个bio的段的数目,而因为bio天生是个链表,所以段的数目总是统计的是链表中段的总数。

BIO标志
BIO_SEG_VALID:bi_phys_segments有了有效值后置这个标志位。

BIO操作
bio_flagged(bio,flag)用于检测bio的bi_flags域是否与flag相等。

request
request中包含了bio和其他参数,例如表明携带数据总大小的__data_len。用双下划线的域一般是不直接使用,而是要使用辅助函数调用,典型的是blk_rq_bytes(const struct request *rq)函数返回这个值,而

static inline unsigned intblk_rq_sectors(const struct request *rq)

{

returnblk_rq_bytes(rq) >> 9;

}

又可以返回这个request携带的sector的数目。

struct request {

struct list_head queuelist;

union {

struct call_single_data csd;

unsigned long fifo_time;

};

...}

结构体的定义都是和功能相关的。由于bio可以被合并进一个request,所以request要为这种功能提供支持。bio合并进request可以在原bio的前面合并也可能在后面。如果在前面,那么肯定是在最前面,此时直接利用bio本身的链表结构插入到最前面即可。如果在后面,也肯定是在最后面,但是此时没有使用bio本身的链表结构,而是使用了一个额外的域,叫biotail来盛放要合并进入的bio。因为这个域本身的定义就是用来放最后一个bio。向前合并最后一个bio不变,而向后合并最后一个bio要变化。

request中的域分为3类,分别用在3个不同的地方:驱动、通用块层、IO调度。

request标志
REQ_FLUSH:表示执行bio前进行fluash。REQ_FUA表示执行bio后进行flush。

QUEUE_FLAG_NO_SG_MERGE:表示是否允许bio本身的bio_vec进行物理合并。

request_queue
这是通用块层的请求队列,这个队列一个cpu一个。上层的数据请求首先生成bio,然后由bio生成request,然后添加到request_queue,然后request_queue会被执行。这个执行包括很多步骤,最重要的是电梯算法。每个算法都会在全局的request_queue之外生成自己的队列结构体elevator_queue。

Queue属性
Queue的标志

#define QUEUE_FLAG_QUEUED 1 /*uses generic tag queueing */

#define QUEUE_FLAG_STOPPED 2 /*queue is stopped */

#define QUEUE_FLAG_SYNCFULL 3 /*read queue has been filled */

#define QUEUE_FLAG_ASYNCFULL 4 /*write queue has been filled */

#define QUEUE_FLAG_DYING 5 /*queue being torn down */

#define QUEUE_FLAG_BYPASS 6 /*act as dumb FIFO queue */

#define QUEUE_FLAG_BIDI 7 /*queue supports bidi requests */

QUEUE_FLAG_NOMERGES:直接不允许对队列的request进行merge操作

#define QUEUE_FLAG_SAME_COMP 9 /*complete on same CPU-group */

#define QUEUE_FLAG_FAIL_IO 10 /*fake timeout */

#define QUEUE_FLAG_STACKABLE 11 /*supports request stacking */

#define QUEUE_FLAG_NONROT 12 /*non-rotational device (SSD) */

#define QUEUE_FLAG_VIRT QUEUE_FLAG_NONROT /* paravirt device */

#define QUEUE_FLAG_IO_STAT 13 /*do IO stats */

#define QUEUE_FLAG_DISCARD 14 /*supports DISCARD */

#define QUEUE_FLAG_NOXMERGES 15 /*No extended merges */

#define QUEUE_FLAG_ADD_RANDOM 16 /*Contributes to random pool */

#define QUEUE_FLAG_SECDISCARD 17 /*supports SECDISCARD */

#define QUEUE_FLAG_SAME_FORCE 18 /*force complete on same CPU */

#define QUEUE_FLAG_DEAD 19 /*queue tear-down finished */

#define QUEUE_FLAG_INIT_DONE 20 /*queue is initialized */

#define QUEUE_FLAG_NO_SG_MERGE 21 /* don't attempt to merge SG segments*/

#define QUEUE_FLAG_SG_GAPS 22 /*queue doesn't support SG gaps */

#define QUEUE_FLAG_DEFAULT ((1 << QUEUE_FLAG_IO_STAT) | \

(1 << QUEUE_FLAG_STACKABLE) | \

(1 << QUEUE_FLAG_SAME_COMP) | \

(1 << QUEUE_FLAG_ADD_RANDOM))

#define QUEUE_FLAG_MQ_DEFAULT ((1 << QUEUE_FLAG_IO_STAT) | \

(1 << QUEUE_FLAG_SAME_COMP))

queue的极限
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: