[IO系统]12 通用块层-结构体

        通用块设备层( Generic Block Layer)是内核的一个组成部分,它处理系统所有对块设备的请求。有通用块设备层后,内核可以方便地:

        • 将数据存放在高端内存—当CPU访问高端内存的数据时,页就被临时映射到内核的线性地址空间,然后解除映射。

        • 实现零拷贝( zero-copy),即磁盘数据直接拷贝到用户地址空间,而不需要先拷贝到内核地址空间。实际上就是,内核进行I/O数据传输使用的页面被映射到用户进程的地址空间中。

        • 管理逻辑卷,如LVM( Logical Volume Manager)和RAID( Redundant Array ofInexpensive Disks)。

        • 使用现在的磁盘控制器新特性,如大的磁盘缓存、增强的DMA功能、板上I/O

 

1.1 缓冲区和缓冲区头

        当一个块被调入内存时(也就是,在读入或等待写出时),它要存储在一个缓冲区中。每个缓冲区都有一个块对应,它相当于是磁盘块在内存中的表示。前面提到过,块包含一个或多个扇区,但大小不能超过一个页面,所以一个页面可以容纳一个或多个内存中的块。由于内核在处理数据时,需要一些相关的控制信息(比如块属于哪一个块设备,块对应于哪一个缓冲区等),所以每个缓冲区都有一个对应的描述符。

        每个块都要有自己的块缓冲区( block buffer),内核使用内存上的这块缓冲区来保存块数据。当内核从块设备上读取一个块时,就用从硬件上读取的数据填充块缓冲区;同理,当内核向块设备写数据时,就用块缓冲区中的数据写到块设备上。每个缓冲区都有一个缓冲区头( buffer head),描述类型为buffer_head,这个数据结构里包含了内核处理缓冲区所需要的信息;于是,在每个缓冲区上操作之前,内核都要先检查它的缓冲区头。的定义在文件include/linux/buffer_head.h中。

struct buffer_head {
    unsignedlong b_state;              /* 表示缓冲区状态 */
    structbuffer_head *b_this_page;   /* 当前页中缓冲区 */
    structpage *b_page;                /* 当前缓冲区所在内存页 */
    sector_tb_blocknr;                 /* 起始块号 */
    size_t b_size;                      /* buffer在内存中的大小 */
    char*b_data;                       /* 块映射在内存页中的数据 */
 
    structblock_device *b_bdev;      /* 关联的块设备 */
   bh_end_io_t *b_end_io;             /* I/O完成方法 */
     void*b_private;                   /* 保留的 I/O 完成方法 */
    structlist_head b_assoc_buffers; /* 关联的其他缓冲区 */
    structaddress_space *b_assoc_map;/* 相关的地址空间 */
    atomic_tb_count;                   /* 引用计数 */
};

        缓冲区与缓冲区头部的关系:


        b_state域表示缓冲区的状态,可以是中一种标志或多种标志的组合。合法的标志存放在bh_state_bits枚举中,其定义也在文件include/linux/buffer_head.h中。

enum bh_state_bits {
    BH_Uptodate,    /* Contains valid data */
    BH_Dirty,   /* Is dirty */
    BH_Lock,    /* Is locked */
    BH_Req,     /* Has been submitted for I/O */
    BH_Uptodate_Lock,/*Used by the first bh in a page, to serialise
              * IO completion of other buffers in the page
              */
 
    BH_Mapped,  /* Has a disk mapping */
    BH_New,     /* Disk mapping was newly created byget_block */
    BH_Async_Read,  /* Is under end_buffer_async_read I/O */
    BH_Async_Write, /* Is under end_buffer_async_write I/O */
    BH_Delay,   /* Buffer is not yet allocated on disk */
    BH_Boundary,    /* Block is followed by a discontiguity */
    BH_Write_EIO,   /* I/O error on write */
    BH_Unwritten,   /* Buffer is allocated on disk but notwritten */
    BH_Quiet,   /* Buffer Error Prinks to be quiet */
    BH_Meta,    /* Buffer contains metadata */
    BH_Prio,    /* Buffer should be submitted with REQ_PRIO*/
    BH_Defer_Completion,/* Defer AIO completion to workqueue */
 
    BH_PrivateStart,/*not a state bit, but the first bit available
             * for private allocation by other entities
             */
};

        其标识位含义:


        bh_state_bits列表还包含了一个特殊标志—BH_PrivateStart,该标志不是可用状态标志,使用它是为了指明可被其他代码使用的起始位。块I/O层不会使用BH_PrivateStart或更高的位,那么某个驱动程序希望通过b_state域存储信息时就可以安全地使用这些位。驱动程序可以在这些位中定义自己的状态标志,只要保证自定义的状态标志不与块I/O层的专用位发生冲突就可以了。

        b_count域表示缓冲区的使用计数,可以通过内联函数get_bh() 和put_bh() 来此域增减,其定义也在文件include/linux/buffer_head.h中

static inline void get_bh(struct buffer_head *bh)
{
       atomic_inc(&bh->b_count);
}
 
static inline void put_bh(struct buffer_head *bh)
{
       smp_mb__before_atomic();
       atomic_dec(&bh->b_count);
}

        在操作缓冲区头之前,应该先使用get_bh()函数增加缓冲区头的引用计数,确保该缓冲区头不会再被释放;当完成对缓冲区头的操作之后,还必须使用put_bh()函数减少引用计数。

        与缓冲区对应的磁盘物理块由b_blocknr域索引,该值是b_bdev域指明的块设备中的逻辑块号。与缓冲区所对应的内存物理页由b_page域表示,另外, b_data域直接指向相应的块(它位于b_page域所指明的页面中某个位置上),块的大小由b_size域表示,所以块在内存中的起始位置在b_data处,结束位置在( b_data+b_size)处。

        缓冲区头的目的在于描述磁盘块和物理内存缓冲区(在特定页面上的字节序列)之间的映射关系。这个结构体在内核中只扮演一个描述符的角色,说明从缓冲区到块的映射关系。

 

1.2 bio结构体

        I/O操作的基本容器由bio结构体表示,它定义在文件include/linux/bio.h中。该结构体代表了正在活动的以段( segment)链表形式组织的块I/O操作。一个段是一小块连续的内存缓冲区。这样,单个缓冲区就不一定要连续。所以使用段来描述缓冲区,即使一个缓冲区分散在内存的多个位置上, bio结构体也能对内核保证I/O操作的执行。这样的向量I/O称为分散-聚合I/O。

        bio结构体的定义如下:

structbio {
    struct bio           *bi_next;        //请求链表
    struct block_device   *bi_bdev;       //block设备
    unsigned long       bi_flags;        //状态和命令标志
    unsigned long       bi_rw;            //读写
    struct bvec_iter   bi_iter;        //
    unsigned int       bi_phys_segments;//结合后的片段数
    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;    //所有数据
#ifdef CONFIG_BLK_CGROUP
    struct io_context   *bi_ioc;
    struct cgroup_subsys_state *bi_css;
#endif
    union {
#ifdefined(CONFIG_BLK_DEV_INTEGRITY)
        struct bio_integrity_payload *bi_integrity; /* data integrity */
#endif
    };
 
    unsigned short       bi_vcnt;        //bio数目
    unsigned short       bi_max_vecs;    //最大数
   atomic_t        bi_cnt;                //使用计数
    struct bio_vec       *bi_io_vec;        //bio_vec链表
    struct bio_set       *bi_pool;
    struct bio_vec       bi_inline_vecs[0];  //内嵌在结构体末尾的 bio 向量,主要为了防止出现二次申请少量的 bio_vecs
}

        使用bio结构体的目的主要是代表正在现场执行的I/O操作,所以该结构体中的主要成员变量都是用来管理相关信息的,其中最重要的几个成员变量是bi_io_vecs、 bi_vcnt和bi_idx。下图显示了bio结构体及相关结构体之间的关系。


        bi_io_vecs指向一个bio_vec结构体数组,该结构体链表包含了一个特定I/O操作所需要使用到的所有段( segment)。每个bio_vec结构都是一个形式为<page, offset, len>的向量,它描述的是一个特定的段:段所在的物理页、块在物理页中的偏移量、从给定偏移量开始的块长度。整个bio_io_vec结构体数组表示了一个完整的缓冲区。 bio_vec结构体定义在文件include/linux/bio.h中。

struct bio_vec {
    struct page *bv_page;  /* 指向这个缓冲区所驻留的物理页面 */
    unsigned int    bv_len;   /* 这个缓冲区以字节为单位的大小 */
    unsigned int    bv_offset; /* 缓冲区所驻留的页中以字节为单位的偏移量 */
};

        在每个给定的块I/O操作中, bi_vcnt域用来描述bi_io_vec所指向的bio_vec数组中的向量数目。当块I/O操作执行完毕后, bi_idx指向数组的当前索引。

        总之,每个块I/O请求都通过一个bio结构体表示。每个请求包含一个或多个块,这些块存储在bio_vec结构体数组中。这些结构体描述了每个片段在物理页中的实际位置,并且像向量一样被组织在一起。 I/O操作的第一个段( segment)由bi_io_vec结构体所指向,其他的段在其后依次放置,共有bi_vcnt个段。当块I/O层开始执行请求,需要使用各个片段时,bi_idx就会不断更新,从而总指向当前片段。bi_idx指向数组的当前bio_vec段,块I/O层通过它可以跟踪块I/O操作的完成进度。但该成员变量更重要的作用在于分割bio结构体。像RAID这样的驱动器可以把单独的bio结构体(原本是为单个设备使用准备的) 分割到RAID阵列中的各个硬盘上去。RAID设备驱动只需拷贝这个bio结构体,再把bi_idx域设置为每个独立硬盘操作时需要的位置就可以了。

void bio_get(structbio *bio)

void bio_put(structbio *bio)

        前者增加使用计数,后者减少使用计数(如果减到0,则销毁bio结构体)。在操作正在活动的bio结构体时,一定要首先增加它的使用计数,以免在操作过程中bio结构体被释放;相反,在操作完毕后,要减少使用计数。最后要说明的是bi_private域,这是一个拥有者(也就是创建者)的私有域,只有创建了bio结构体的拥有者可以读写该域。

 

1.3 缓冲区头和bio结构体比较

        缓冲区头( buffer head)和bio结构体之间存在明显差别。 bio结构体代表的是I/O操作,它可以包括内存中的一个或多个页;而另一方面, buffer_head结构体代表的是一个缓冲区,它描述的仅仅是磁盘中的一个块。因为缓冲区头关联的是单独页中的单独磁盘块,所以它可能会引起不必要的分割,将请求按块为单位划分,只能靠以后才能重新组合。由于bio结构是轻量级的,它描述的块可以不需要连续存储区,并且不需要分割I/O操作。利用bio结构体代替buffer_head结构体有以下好处:        

        • bio结构体很容易处理高端内存,因为它处理的是物理页而不是直接指针。

        • bio结构体既可以代表普通页I/O,同时也可以代表直接I/O。

        • bio结构体便于执行分散-聚合I/O操作,操作中的数据可以来自多个物理页面。

        • bio结构体相比缓存区头属于轻量级的结构体,因为它只需要包含块I/O操作所需的信息就行了,不用包含与缓冲区本身相关的不必要信息。

        但还是需要缓冲区头这个概念,因为它还负责描述磁盘块到页面的映射。 bio结构体不包含任何和缓冲区相关的状态信息—它仅仅是一个向量数组,描述一个或多个块I/O操作的数据段和相关信息。在当前设置中,当bio结构体描述当前正在使用的I/O操作时,buffer_head结构体仍然需要保护缓冲区信息。内核通过两种结构分别保存各自的信息,可以保证每种结构所包含的信息量尽可能地少。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YoungerChina

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值