页高速缓存(二)

五. 把块放在页高速缓存中
在旧的Linux内核版本中,有两种不同的磁盘高速缓存,分别是页高速缓存和缓冲区高速缓存,前者用来存放访问磁盘文件内容时生成的磁盘数据页,后者把通过VFS访问的块的内容保留在内存中。从2.4.10版本开始,缓冲区高速缓存不存在了,把它们存放在叫做”缓冲区页”的专门页中,页缓冲区页保存在页高速缓冲中。缓冲区页在形式上就是与称做”缓冲区首部”的附加描述符相关的数据页,主要目的是快速确定页中的一个块在磁盘中的地址。
(1) 块缓冲区和缓冲区首部
每个块缓冲区是由buffer_head结构体来描述的。其实数据结构如下。

struct buffer_head {
01 unsigned long b_state;  缓冲区状态标志
02 struct buffer_head *b_this_page;指向缓冲区页的链表中的下一个元素的指针
03 struct page *b_page;  指向拥有该块的缓冲区页的描述符的指针
04
05 sector_t b_blocknr;  块的开始号
06 size_t b_size;   块大小
07 char *b_data;   块在缓冲区页内的位置
08
09 struct block_device *b_bdev; 指向块设备描述符的指针
10 bh_end_io_t *b_end_io;  I/O完成方法
11  void *b_private;  指向I/O完成方法数据的指针
12 struct list_head b_assoc_buffers; 为与某个索引节点相关的间接块的链表提供的指针
13 struct address_space *b_assoc_map; /* mapping this buffer is
14         associated with */
15 atomic_t b_count;  块使用计数器
16};

b_bdev字段表示包含块的块设备,通常是磁盘或分区;b_blocknr字段存放逻辑块号,即块在磁盘或分区中的编号。b_data字段表示块缓冲区在缓冲区中的位置。如果在高端内存,b_data字段存放的是块缓冲区相对于页的起始位置的偏移量,否则,b_data存放的是块缓冲区的线性地址。
(2) 管理缓冲区首部
缓冲区首部的空间分配是slab分配器来完成的,其描述符kmem_cache_s存在变量bh_cachep中。使用alloc_buffer_head()和free_buffer_head()函数来完成缓冲区首部的空间分配和释放。
当内核控制路径访问块缓冲区时,应该先递增引用计数器,就也是缓冲区首部的b_count字段。确定块在页高速缓冲中的位置函数(__getblk())自动完成这个工作。当内核控制路径停止访问块缓冲区时,应该调用__breles()或__bforget()函数递减相应的引用计数器。
(3) 缓冲区页
只要内核必须单独访问一个块,就要涉及存放块缓冲区的缓冲区页,并要检查相应的缓冲区首部。第一,当读或写的文件页在磁盘块中不相邻时,第二、当访问一个单独的磁盘块时。都需要创建缓冲区页。
重点说第二种,因为是块设备驱动,一个缓冲区页可以包括1到8个缓冲区,一个缓冲区的的大小为1K,而页的大小为4K,所以可以包括四个缓冲区。其图如下
 

(4) 分配块设备缓冲区页
前面说分配缓冲的首部,其实原理都是一样的,都是在什么时候分配什么空间,内核发现指定块的缓冲区所在页不在页高速缓存中时,就分配一个新的块设备缓冲区页。
六. 向通用块层提交缓冲区首部
submit_bh()和ll_rw_block函数用来向通过块传递一个或多个缓冲区首部,并由此请求传输一个数据块,参数为传输的方向(读或写)

int submit_bh(int rw, struct buffer_head * bh)
{
01 struct bio *bio;
02 int ret = 0;
03
04 BUG_ON(!buffer_locked(bh));
05 BUG_ON(!buffer_mapped(bh));
06 BUG_ON(!bh->b_end_io);
07
08 if (buffer_ordered(bh) && (rw & WRITE))
09  rw |= WRITE_BARRIER;
10
11 if (test_set_buffer_req(bh) && (rw & WRITE))
12  clear_buffer_write_io_error(bh);
13
14 bio = bio_alloc(GFP_NOIO, 1);
15
16 bio->bi_sector = bh->b_blocknr * (bh->b_size >> 9);
17 bio->bi_bdev = bh->b_bdev;
18 bio->bi_io_vec[0].bv_page = bh->b_page;
19 bio->bi_io_vec[0].bv_len = bh->b_size;
20 bio->bi_io_vec[0].bv_offset = bh_offset(bh);
21
22 bio->bi_vcnt = 1;
23 bio->bi_idx = 0;
24 bio->bi_size = bh->b_size;
25
26 bio->bi_end_io = end_bio_bh_io_sync;
27 bio->bi_private = bh;
28
29 bio_get(bio);
30 submit_bio(rw, bio);
31
32 if (bio_flagged(bio, BIO_EOPNOTSUPP))
33  ret = -EOPNOTSUPP;
34
35 bio_put(bio);
36 return ret;
37}

第14行调用bio_alloc()函数分配一个新的bio描述符
第16行把块中的第一个扇区的号赋给bi_sector字段
第17行把块设备描述的地址赋给bi_bdev字段
第18-20行初始化bi_io_vec数组的第一个元素。
第22行把bi_vcnt置为1
第23行把bi_idx置为0(将要传输的当前段)
第24行把块大小赋给bi_size字段
第26行把end_bio_bh_io_sync()的地址赋给bi_end_io字段。
第27行把缓冲区首部的地址赋给bi_private字段。
第29行递增bio的引用计数器
第30行调用submit_bio函数,把bi_rw标志设置为数据传输的方向,更新每个CPU变量page_states以表示读和写的扇区数,并对bio描述符调用generic_make_request()函数。
第35行递减bio的使用计数器。
七.总结
到这里,页高速缓冲基本上完成了它的任务,把缓存区头提交到通用块层,由它处理,当然还有脏页写回磁盘,这里面不介绍了,可以查看资料,页高速缓存主是提高磁盘上的文件或块设备数据的读写效率,如果在页高速缓存中的页是最新的页,直接读取,是脏页写回磁盘。在写回磁盘过程中,可以有一些系统调用函数供用户使用。除了页高速缓存外,还有索引节点缓存,目录项缓存,交换区缓存等。
参考资料
深入理解linux内核

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值