块IO层[1]

 

   系统中能够随机(无序的)访问固定大小数据片(chunk)的设备被称作块设备,这些数据片就称为块。常见的块设备是磁盘,软盘驱动器,CD_ROM驱动器和闪存等。注意,它们都是以安装文件系统的方式使用的---这也是块设备通常的访问方式。

  另一种基本的设备类型是字符设备。字符设备按照字符流的方式被有序访问。像串口和键盘都属于字符设备。

  这两种类型的设备的根本区别在于它们是否可以被随机访问。

  内核管理块设备比管理字符设备要细致得多。因为字符设备仅仅需要控制一个位置(当前位置),而块设备访问的位置必须能够在介质的不同区间前后移动。,因此对块设备的管理需要一个专门的提供服务的子系统。

  • 解剖一个块设备

  块设备中最小的可寻址单元是扇区。扇区大小一般是2的整数倍,最常见的大小是512个字节(很多CD_ROM盘的扇区都是2K大小)。扇区的大小是设备的物理属性,扇区是所有块设备的基本单元,块设备无法对比它还小的单元进行寻址和操作。

  虽然各种软件的用途不同,但是它们都会用到自己的最小逻辑可寻址单元---块。块是文件系统的一种抽象,只能基于块来访问文件系统。虽然物理磁盘寻址是按照扇区来级进行的,但是内核执行的所有磁盘操作都是按照块进行的。由于扇区是设备的最小可寻址单元,所以块不能比扇区还小,只能数倍于扇区大小。对有扇区的硬件设备,内核还要求块大小是2的整数倍,且不能超过一个页的长度。

  对块大小的要求最终如下:必须是扇区大小的2的整数倍,并且要小于页面大小。

  扇区,设备的最小寻址单元,或称为“硬扇区”“设备块”。块,文件系统的最小寻址单元,或称为“文件块”“I/O块”。

  和硬盘相关的常见术语有:簇,柱面,磁头等,这些都和具体的块设备邮箱,用户空间的软件一般用不到这些概念。

  扇区对内核的重要性在与所有设备的I/O操作都必须基于扇区来进行。

  • 缓冲区和缓冲区头

  当一个块被调入内存时,它要存储在一个缓冲区中。每个缓冲区与一个块对应,它相当于是磁盘块在内存中的表示。一个块小于一个页,所以一页可以容纳一个或多个内存中的块。

  由于内核在处理数据时需要一些相关的控制信息,每个缓冲区都有一个对应的描述符。该描述符用buffer_head结构体表示,被称作缓冲区头,它包含了内核操作缓冲区所需的全部信息:

 

  1. 在<Buffer_head.h(include/linux)>中
  2. struct page;
  3. struct buffer_head;
  4. struct address_space;
  5. typedef void (bh_end_io_t)(struct buffer_head *bh, int uptodate);
  6. /*
  7.  * Historically, a buffer_head was used to map a single block
  8.  * within a page, and of course as the unit of I/O through the
  9.  * filesystem and block layers.  Nowadays the basic I/O unit
  10.  * is the bio, and buffer_heads are used for extracting block
  11.  * mappings (via a get_block_t call), for tracking state within
  12.  * a page (via a page_mapping) and for wrapping bio submission
  13.  * for backward compatibility reasons (e.g. submit_bh).
  14.  */
  15. struct buffer_head {
  16.     unsigned long b_state;      /* buffer state bitmap (see above) */
  17.     struct buffer_head *b_this_page;/* circular list of page's buffers */
  18.     struct page *b_page;        /* the page this bh is mapped to */表示与缓冲区对应的内存物理页
  19.     sector_t b_blocknr;     /* start block number */与缓冲区对应的磁盘物理块由该域索引,该值是b_bdev指明的块设备中的逻辑块号
  20.     size_t b_size;          /* size of mapping 以字节为单位*/
  21.     char *b_data;           /* pointer to data within the page */直接指向相应的块(它位于b_page域所指的页面中的某个位置),块大小为b_size,块在内存中的起始位置在b_data处,结束位置在b_data+b_size
  22.     struct block_device *b_bdev;  /*块设备*/
  23.     bh_end_io_t *b_end_io;      /* I/O completion; io完成方法*/
  24.     void *b_private;        /* reserved for b_end_io */
  25.     struct list_head b_assoc_buffers; /* associated with another mapping */
  26.     struct address_space *b_assoc_map;  /* mapping this buffer is
  27.                            associated with */
  28.     atomic_t b_count;       /* users using this buffer_head */
  29. };

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

  b_state域中表示缓冲区的状态。合法标志存放在bh_state_bits枚举中:

  1. enum bh_state_bits {
  2.     BH_Uptodate,    /* Contains valid data */该缓冲区包含可用数据
  3.     BH_Dirty,   /* Is dirty */该缓冲区是脏的(缓存中的内容比磁盘中的块内容新所以缓冲区内容必须被写回磁盘)
  4.     BH_Lock,    /* Is locked */该缓冲区正在被IO操作使用,被锁定以防被并发访问
  5.     BH_Req,     /* Has been submitted for I/O */该缓冲区有IO请求操作
  6.     BH_Uptodate_Lock,/* Used by the first bh in a page, to serialise
  7.               * IO completion of other buffers in the page
  8.               */
  9.     BH_Mapped,  /* Has a disk mapping */该缓冲区是映射磁盘块的可用缓冲区
  10.     BH_New,     /* Disk mapping was newly created by get_block */缓冲区是通过get_block()刚刚映射的,尚且不能访问
  11.     BH_Async_Read,  /* Is under end_buffer_async_read I/O */该缓冲区正通过end_buffer_async_read()被异步IO读操作使用
  12.     BH_Async_Write, /* Is under end_buffer_async_write I/O */该缓冲区正通过end_buffer_async_write()被异步IO写操作使用
  13.     BH_Delay,   /* Buffer is not yet allocated on disk */该缓冲区尚未和磁盘块关联
  14.     BH_Boundary,    /* Block is followed by a discontiguity */该缓冲区处于连续块去的边界,下一个块不在连续
  15.     BH_Write_EIO,   /* I/O error on write */
  16.     BH_Ordered, /* ordered write */
  17.     BH_Eopnotsupp,  /* operation not supported (barrier) */
  18.     BH_Unwritten,   /* Buffer is allocated on disk but not written */
  19.     BH_PrivateStart,/* not a state bit, but the first bit available
  20.              * for private allocation by other entities
  21.              */该标志不是可用状态标志,使用它是为了指明可被其他代码使用的起始位。块IO层不会使用BH_PrivateStart或更高的位。
  22. };

  某个驱动程序希望通过b_state域存储信息时就可以安全的使用这些位。驱动程序可以在这些位中定义自己的状态标志,只要保证自己的状态标志不与块IO层的专用位发生冲突就OK了。

  b_count域表示缓冲区的使用计数,可以通过下面的函数进行增减:

 

  1. static inline void get_bh(struct buffer_head *bh)
  2. {
  3.         atomic_inc(&bh->b_count);
  4. }
  5. static inline void put_bh(struct buffer_head *bh)
  6. {
  7.         smp_mb__before_atomic_dec();
  8.         atomic_dec(&bh->b_count);
  9. }

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

 在2.6之前,缓冲区头的作用很重要,缓冲区头作为内核中IO操作单元,不仅仅描述了从哦哦那个磁盘块到物理内存的映射,而且还是所有开IO操作的容器。 将缓冲区头作为IO操作单元的弊端如下:

1. 缓冲区头是一个很大且不易控制的数据结构体(2.6之前),而且缓冲区头对数据的操作既不方便也不清晰。在2.6版本中,许多IO操作都是通过内核直接对页面或地址空间进行操作来完成,不再使用缓冲区头。

2. 缓冲区头仅能描述单个缓冲区,当作为所有IO的容器使用时,缓冲区头会迫使内核打断对大块数据的IO操作,使其成为对多个buffer_head结构体进行操作。2.5版本以后的内核的主要目标就是为块IO操作引入一直新型。灵活并且轻量级的容器,bio结构体。

  • bio结构体

  目前内核中块IO操作的基本容器由bio结构体表示。该结构体代表了正在现场的(活动的)以片段(segment)链表形式组织的块IO操作。一个片段是一小块连续的内存缓冲区。这样就不需要保证单个缓冲区一定要连续。通过片段来描述缓冲区,即使一个缓冲区分散在内存的多个位置上,bio结构体也能对内核保证IO操作的执行。像这样的向量IO就是所谓的聚散IO。

  1. 在<Bio.h(include/linux)>中
  2. struct bio_set;
  3. struct bio;
  4. typedef int (bio_end_io_t) (struct bio *, unsigned intint);
  5. typedef void (bio_destructor_t) (struct bio *);
  6. /*
  7.  * main unit of I/O for the block layer and lower layers (ie drivers and
  8.  * stacking drivers)
  9.  */
  10. struct bio {
  11.     sector_t        bi_sector;  /* device address in 512 byte
  12.                            sectors */磁盘上相关的扇区
  13.     struct bio      *bi_next;   /* request queue link */请求链表
  14.     struct block_device *bi_bdev;   /* 相关的块设备信息 */
  15.     unsigned long       bi_flags;   /* status, command, etc */
  16.     unsigned long       bi_rw;      /* bottom bits READ/WRITE,
  17.                          * top bits priority
  18.                          */
  19.     unsigned short      bi_vcnt;    /* how many bio_vec's */
  20.     unsigned short      bi_idx;     /* current index into bvl_vec */
  21.     /* Number of segments in this BIO after
  22.      * physical address coalescing is performed.
  23.      */结合后的片段数目
  24.     unsigned short      bi_phys_segments;
  25.     /* Number of segments after physical and DMA remapping
  26.      * hardware coalescing is performed.
  27.      */
  28.     unsigned short      bi_hw_segments;  /* 重映射后的片断数目 */
  29.     unsigned int        bi_size;    /* residual I/O count */
  30.     /*
  31.      * To keep track of the max hw size, we account for the
  32.      * sizes of the first and last virtually mergeable segments
  33.      * in this bio
  34.      */
  35.     unsigned int        bi_hw_front_size;  /* 第一个可合并的段大小 */
  36.     unsigned int        bi_hw_back_size;  /* 最后一个可合并的段大小 */
  37.     unsigned int        bi_max_vecs;    /* max bvl_vecs we can hold */
  38.     struct bio_vec      *bi_io_vec; /* the actual vec list */
  39.     bio_end_io_t        *bi_end_io;  /* IO完成方法 */
  40.     atomic_t        bi_cnt;     /* pin count */使用计数
  41.     void            *bi_private;  /*拥有者的私有方法*/
  42.     bio_destructor_t    *bi_destructor; /* destructor */
  43. };

  使用bio结构体的目的主要是代表正在现场执行的IO操作,所以该结构体中的主要域都是用来管理相关信息的。其中最重要的几个域是bi_io_vecs,bi_vcnt和bi_idx。

  bi_io_vecs域指向一个bio_vec结构体数组,该结构体链表包含了一个特定的IO操作所需要使用到的所有片段。

  在每个给定的块IO操作中,bi_vcnt域用来描述bi_io_vec所指向的bio_vec数组中的向量数目。

  当块IO操作执行完了,bi_idx指向数组的当前索引。

  每个bio_vec结构都是一个形式为<page,offset,len>的向量,它描述的是一个特定的片断:片段所在的物理页、在物理页中的偏移位置、从给定偏移量开始的块长度。整个bio_io_vec结构体指向的bio_vec结构体数组表示了一个完整的缓冲区:

  1. /*
  2.  * was unsigned short, but we might as well be ready for > 64kB I/O pages
  3.  */
  4. struct bio_vec {
  5.     struct page *bv_page;
  6.     unsigned int    bv_len;
  7.     unsigned int    bv_offset;
  8. };

  总而言之,每一个块IO请求都通过一个bio结构体表示。每个请求包括一个或多个块,这些块存储在bio_vec结构体数组中。这些结构体描述了每个片段在物理页中的实际位置,并且像向量一样被组织在一起。IO操作的第一个片段由bio_io_vecs指针所指向,其他的片段在其后一次防止,共有bi_vcnt个片段。当块IO层开始执行请求、需要使用各个片段时,bi_idx域会不断更新,指向当前片段。块IO层通过bi_idx可以跟踪IO操作的完成进度。但该域更重要的作用在于分割bio结构体。

  bi_cnt域记录bio结构体的使用计数,如果为0就销毁该结构体,并释放内存。通过下面的函数管理使用计数:

 

  1. 在<Bio.h(include/linux)>
  2. /*
  3.  * get a reference to a bio, so it won't disappear. the intended use is
  4.  * something like:
  5.  *
  6.  * bio_get(bio);
  7.  * submit_bio(rw, bio);
  8.  * if (bio->bi_flags ...)
  9.  *  do_something
  10.  * bio_put(bio);
  11.  *
  12.  * without the bio_get(), it could potentially complete I/O before submit_bio
  13.  * returns. and then bio would be freed memory when if (bio->bi_flags ...)
  14.  * runs
  15.  */
  16. #define bio_get(bio)    atomic_inc(&(bio)->bi_cnt)
  17. 在<Bio.c(fs)>中
  18. /**
  19.  * bio_put - release a reference to a bio
  20.  * @bio:   bio to release reference to
  21.  *
  22.  * Description:
  23.  *   Put a reference to a &struct bio, either one you have gotten with
  24.  *   bio_alloc or bio_get. The last put of a bio will free it.
  25.  **/
  26. void bio_put(struct bio *bio)
  27. {
  28.     BIO_BUG_ON(!atomic_read(&bio->bi_cnt));
  29.     /*
  30.      * last put frees it
  31.      */
  32.     if (atomic_dec_and_test(&bio->bi_cnt)) {
  33.         bio->bi_next = NULL;
  34.         bio->bi_destructor(bio);
  35.     }
  36. }

  在操作正在活动的bio结构体时,一定要首先增加它的使用计数,以免在操作过程中该bio结构体被释放。

  bi_private域,这是一个属于拥有者(创建者)的私有域,只有创建了bio结构的拥有者可以读写该域。

  • 新老方法的对比

  缓冲区头和新的bio结构体之间存在明显的差别。

  bio结构体代表的是IO操作,它可以包括内存中的一个或多个页;buffer_head结构体代表的是一个缓冲区,它描述的仅仅是磁盘中的一个块。因为缓冲区头关联的是单独页中的单独磁盘块,所以它会引起不必要的分割,将请求按块为单位划分,只能靠以后重新组合。bio结构体是轻量级的,它描述的块可以不需要连续存储区,且不需要分割IO操作。

  bio代替buffer_head的好处如下:

1. bio很容易处理高度内存,因为它处理的是物理页而不是直接指针。

2. bio既可以代表普通页IO,也可以代表直接IO(那些不通过页高速缓存的IO操作)

3. bio便于执行分散--集中(向量化)块IO操作,操作中的数据可取自多个物理页面。

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

  缓冲区头负责描述磁盘块到页面的映射。bio结构体不包括任何和缓冲区相关的状态信息,它仅仅是一个向量组,描述一个或多个单独块IO操作的数据片段和相关信息。

  在当前设置中,当bio描述当前正在使用的IO操作时,buffer_head需要包含缓冲区信息。

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值