block bio queue request等

参考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的极限

 

posted @ 2015-12-05 10:36  fastwave2004  阅读(1763)  评论(0编辑  收藏  举报