欢迎访问我们的Linux内核之旅:http://www.kerneltravel.net/   网站及本博客中的文章,允许在不改变内容的前提下对其以任何形式复制和重新刊登,但必须 附有版权信息,并声明出处。

块设备输入输出解读(三)—缓冲区和缓冲区头

上一篇 / 下一篇  2008-01-02 11:32:51 / 个人分类:释义Linux内核

查看( 1429 ) / 评论( 0 )

 

当一个块被调入内存时(也就是说,在读入后或等待写出时),它要存储在一个缓冲区中。每个缓冲区与一个块对应,它相当于是磁盘块在内存中的表示。前面提到过,块包含一个或多个扇区,但大小不能超过一个页面,所以一个页可以容纳一个或多个内存中的块。由于内核在处理数据时需要一些相关的控制信息(比如块属于那一个块设备,块对应于哪个缓冲区等),所以每一个缓冲区都有一个对应的描述符。该描述符用buffer_head结构体表示,被称作缓冲区头,在文件<linux/buffer_head.h>中定义,它包含了内核操作缓冲区所需要的全部信息。

下面给出缓冲区头结构体和其中各个域的说明:

struct buffer_head {

    unsigned long   b_state;    /*缓冲区状态标志*/

    atomic_t    b_count;    /*缓冲区使用计数*/

    struct buffer_head *b_this_page;   /*页面中的缓冲区*/

    struct page     *b_page;    /*存储缓冲区的页面*/

    sector_t     b_blocknr; /*逻辑块号*/

    u32      b_size;    /*块大小(以字节为单位)*/

    char    *b_data;    /*页面中的缓冲区*/

    struct block_device    *b_bdev;    /*块设备*/

    bh_end_io_t     *b_end_io;  /*I/O完成方法*/

    void    *b_private; /*完成方法数据*/

    struct list_head    b_assoc_buffers; /*相关的映射链表*/

};

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

   bh_state 标志

状态标志             

BH_Uptodate   该缓冲区包含可用数据

BH_Dirty       该缓冲区是脏的(缓存中的内容比磁盘中的块内容新,

所以缓冲区内容必须被写回磁盘)

BH_Lock        该缓冲区正在被I/O操作使用,被锁定以防被并发访问

BH_Req         该缓冲区有I/O请求操作

BH_Mapped      该缓冲区是映射磁盘块的可用缓冲区

BH_New        缓冲区是通过get_block()刚刚映射的,尚且不能访问

    BH_Async_Read 该缓冲区正通过end_buffer_async_read()被异步I/O读操作使用

  BH_Async_write  该缓冲区正通过end_buffer_async_write()被异步I/O写操作使用

BH_Delay        该缓冲区尚未和磁盘块关联

BH_Boundary     该缓冲区处于连续块区的边界——下一个块不再连续

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

b_count域表示缓冲区的使用记数,可通过两个定义在文件<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)

{

       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)处。

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

2.6内核以前,缓冲区头的作用比现在还要重要。因为缓冲区头作为内核中的I/O操作单元,不仅仅描述了从磁盘块到物理内存的映射,而且还是所有块I/O操作的容器。可是,将缓冲区头作为I/O操作单元带来了两个弊端。首先,缓冲区头是一个很大且不易控制的数据结构体(现在是缩减过的了),而且缓冲区头对数据的操作既不方便也不清晰。对内核来说,它更倾向于操作页面结构,因为页面操作起来更为简便,同时效率也高。使用一个巨大的缓冲区头表示每一个独立的缓冲区(可能比页面小)效率低下,所以在2.6版本中,许多I/O操作都是通过内核直接对页面或地址空间进行操作来完成,不再使用缓冲区头了。

缓冲区头带来的第二个弊端是:它仅能描述单个缓冲区,当作为所有I/O的容器使用时,缓冲区头会迫使内核打断对大块数据的I/O操作(比如写操作),使其成为对多个buffer_head结构体进行操作。这样做必然会造成不必要的负但和空间浪费。所以2.5开发版内核的主要目标就是为块I/O操作引入一种新型、灵活并且轻量级的容器,也就是下一节要介绍的bio结构体。


TAG:

我来说两句

(可选)

Open Toolbar