一、page和buffer_head的关系
1、页中的块在磁盘上连续
如果page中的块在磁盘上连续,那么page的PG_private不会被置位,private字段也不会指向buffer_head的链表。
但是page还是得用到buffer_head结构,因为它需要通过get_block()函数来获得磁盘上的逻辑块号。
虽然ext2_getblock()函数的代码我暂时还没有看,但是通过do_mpage_readpage()函数代码的阅读,可以对get_block()系列函数的功能进行如下猜想:
typedef int (get_block_t)(struct inode *inode, sector_t iblock,
struct buffer_head *bh_result, int create);
这类函数会得到在文件中块号iblock在磁盘上的逻辑块号,然后赋给bh中的b_blocknr字段。在调用get_block()函数前,bh中的b_size被赋为期望的连续的块数的总大小,返回前,get_block()函数被设置为以iblock块为第一块(什么意思?),且在磁盘上连续的实际的块数(如果实际连续的比期望的小)。
在do_mpage_readpage()函数中,得到了块在磁盘上的逻辑块号后,buffer_head结构就没有什么用了,将其中的b_blocknr赋给了blocks数组后,生成bio的函数mapge_alloc()使用blocks[0]就行了。
Blocks[0]是否表示磁盘上连续的实际的块数的总的大小(以位为单位)?
2、页中的块在磁盘上不连续
页一开始和buffer_head是没有关系的,但是通过get_block()发现页中的块在磁盘上不连续等现象后,就需要调用create_empty_buffers()函数来为page创建buffer_head链表了。create_empty_buffers的结构很简单,它先调用alloc_page_buffers()来为page创建一个buffer_head的链表,之后为链表中每个buffer_head的b_state赋值,并顺便将该链表构造成循环链表,然后看情况设置buffer_head的BH_dirty和BH_uptodate标志,最后调用attach_page_buffers()来将page的PG_private置位。
链表建成后,page和buffer_head的关系就如下图所示了:
Struct buffer_head *b_this_page 是否指向下一个Buffer head?确定
二、buffer_head和bio的关系
个人认为,buffer_head和bio关系在submit_bh()函数中可以充分体现:(也就是说只有在page中的块不连续时,buffer_head和bio才建立关系?)
bio = bio_alloc(GFP_NOIO, 1);
bio->bi_sector = bh->b_blocknr * (bh->b_size >> 9);//表示磁盘上相关的扇区号
bio->bi_bdev = bh->b_bdev;
bio->bi_io_vec[0].bv_page = bh->b_page;
bio->bi_io_vec[0].bv_len = bh->b_size; //应该不一定对应一个block的大小
bio->bi_io_vec[0].bv_offset = bh_offset(bh);
bio->bi_vcnt = 1;
bio->bi_idx = 0;
bio->bi_size = bh->b_size;
上述代码已经把buffer_head和bio关系说的差不多了,就不多说了。
稍微注意一点的话,可以发现io_vec中的bv_page指向了buffer_head中的b_page,即bv_page指向了也描述符,而bv_offset则是在页中的偏移,为len则为要传输的数据的(在这里就是块的大小)长度。
三、page和bio的关系
page和bio的关系在上面一段中稍微说了一下,即io_vec中的bv_page字段会指向page。
将一个整页加到bio中,可以看看_add_page函数中的如下几行(do_mpage_readpage()函数调用bio_add_page()时,offset参数是0):
bvec = &bio->bi_io_vec[bio->bi_vcnt];
bvec->bv_page = page;
bvec->bv_len = len;
bvec->bv_offset = offset;
……
bio->bi_vcnt++;
这几行代码将page、len等赋给一个新的io_vec,然后增加bi_vcnt的值。
当页中的块连续时: 一个bio包含许多blocks
Mpage_allock
Bio_add_page
Mpage_bio_submit
Submit_bio
当页中的块不连续时:一个bio只包含一个block,然后在IO层可能进行重组
(这里每个bio应该只对应一个block,所以不需要类似Bio_add_page的函数)
b lock_read_full_page
submit_bh(bh和bio建立联系)
submit_bio
buffer_head完全按照设备块来进行io,块大小取决于设备但是普遍比页面小,bh的元数据比率开销过大,bio则按照页面大小进行io,然而一个bio中可以包含多个页面,因此聚集的基于page的io吞吐量更大些,这就好比用桶提水比用汤勺舀水效率高一样。因此2.6内核普遍使用bio代替了bh,然而传统的bh并没有消失,只是它完全用bio来实现了,在读取小数据的时候,基于设备块来读取还是很可取的,因此内核干脆设计出一个 bh_lru缓存结构体来缓存8个最近使用并且猜测还将被使用的bh,这可能是一些块设备的元数据。在2.6内核中,bh退化成了一个接口层,虽然2.6内核完全使用了基于page的块io,但是却并没有丧失基于块映射io的高效性,只是这个工作交给了更底层的块设备驱动程序来做了,最终驱动程序还是成块成块地来读写数据的...早先对于bh来说,每一个块都要有一个bh结构体来描述,如果写一个很大的数据,就需要很多的bh链接成一个链,然后一个循环将它们全部写入设备,如果是bio的,使用mpage机制可以尽可能多地搜集很多的page,然后交给更底层的驱动。