当前位置:   article > 正文

Linux 页高速缓存之buffer head_bufferhead

bufferhead

我们都知道Linux为了加速读写速度,采用了pagecache机制,用内存缓存磁盘内容,而buffer_head正是连接page和磁盘块的关键结构.

1.buffer head的作用

1. buffer_head是磁盘块的一个抽象,一个buffer_head对应一个磁盘块,buffer_head中保存对应的磁盘号
2. buffer_head把page与磁盘块联系起来,由于page和磁盘块的大小可能不一样,所以一个page可能管理多个buffer_head
这里假设page大小4K,块大小为1K, buffer_head,page和磁盘块关系如下:

2.page与磁盘块映射的建立

这里以写文件为例说明page cache,buffer_head和磁盘块的映射
采用异步IO方式写文件时,会调用到generic_perform_write函数

2.1 文件写流程

  1. static ssize_t generic_perform_write(struct file *file,
  2. struct iov_iter *i, loff_t pos)
  3. {
  4. do {
  5. /*建立page,BH,磁盘块的映射关系 */
  6. status = a_ops->write_begin(file, mapping, pos, bytes, flags,
  7. &page, &fsdata);
  8. if (unlikely(status))
  9. break;
  10. /*复制用户数据到page */
  11. copied = iov_iter_copy_from_user_atomic(page, i, offset, bytes);
  12. /*标记缓冲区为dirty,等待异步IO完成 */
  13. status = a_ops->write_end(file, mapping, pos, bytes, copied,
  14. } while (iov_iter_count(i));
  15. return written ? written : status;
  16. }

write_bengin和write_end会调用到具体文件系统的实现,这里以ext4为例ext4_write_begin:
 

  1. static int ext4_write_begin(struct file *file, struct address_space *mapping,
  2. loff_t pos, unsigned len, unsigned flags,
  3. struct page **pagep, void **fsdata)
  4. {
  5. /*分配page cache */
  6. page = grab_cache_page_write_begin(mapping, index, flags);
  7. /*建立page cache与Buffer Head和磁盘块的联系,ext4_get_blok会分配实际的磁盘空间 */
  8. ret = __block_write_begin(page, pos, len, ext4_get_block);
  9. *pagep = page;
  10. return ret;
  11. }

每个inode都有一个address_space结构,不仅提供了文件系统层操作,还用一颗radix tree来管理inode所有page cache.
grab_cache_page_write_begin:首先会用index在mapping的radix tree中查找对应的page cache,找不到,创建新的页面.
而index表示page在文件中的偏移,单位是page_size。这里重点看__block_write_begin和ext4_get_block函数:
__block_write_begin:
1.给page分配buffer_head,由create_page_buffer完成
2.buffer_head与磁盘块的映射,由ext4_get_block完成
3.这里可能涉及到读磁盘,因为向page写入数据时,先要保证page已有的buffer数据与磁盘一致,否则会出现数据覆盖

  1. int __block_write_begin(struct page *page, loff_t pos, unsigned len,
  2. get_block_t *get_block)
  3. {
  4. /*给page创建buffer head */
  5. head = create_page_buffers(page, inode, 0);
  6. blocksize = head->b_size;
  7. bbits = block_size_bits(blocksize);
  8. /*文件索引,转换成文件内块号(这个不是磁盘块号) */
  9. block = (sector_t)page->index << (PAGE_CACHE_SHIFT - bbits);
  10. for(bh = head, block_start = 0; bh != head || !block_start;
  11. block++, block_start=block_end, bh = bh->b_this_page) {
  12. block_end = block_start + blocksize;
  13. /*如果要写的区间[from,to]没有落到当前的bh范围,直接不处理 */
  14. if (block_end <= from || block_start >= to) {
  15. if (PageUptodate(page)) {
  16. if (!buffer_uptodate(bh))
  17. set_buffer_uptodate(bh);
  18. }
  19. continue;
  20. }
  21. if (buffer_new(bh))
  22. clear_buffer_new(bh);
  23. /*给对应的bh分配磁盘空间 */
  24. if (!buffer_mapped(bh)) {
  25. WARN_ON(bh->b_size != blocksize);
  26. err = get_block(inode, block, bh, 1);
  27. if (err)
  28. break;
  29. }
  30. /*待写的page已经与磁盘内容一致,直接不处理 */
  31. if (PageUptodate(page)) {
  32. if (!buffer_uptodate(bh))
  33. set_buffer_uptodate(bh);
  34. continue;
  35. }
  36. /*如果要写得区间[from,to]与磁盘不一致,需要从磁盘读数据 */
  37. if (!buffer_uptodate(bh) && !buffer_delay(bh) &&
  38. !buffer_unwritten(bh) &&
  39. (block_start < from || block_end > to)) {
  40. ll_rw_block(READ, 1, &bh);/*更新pagecache内容,如果不更新,会存在数据覆盖 */
  41. *wait_bh++=bh;
  42. }
  43. }
  44. /*等待读完成 */
  45. while(wait_bh > wait) {
  46. wait_on_buffer(*--wait_bh);
  47. if (!buffer_uptodate(*wait_bh))
  48. err = -EIO;
  49. }
  50. return err;
  51. }

 

2.2 buffer head分配

create_page_buffers判断当前page是否已分配buffer_head,否则调用create_empty_buffer创建buffer_head
此时创建完成的buffer_head并没有映射到具体的磁盘块

  1. void create_empty_buffers(struct page *page,
  2. unsigned long blocksize, unsigned long b_state)
  3. {
  4. struct buffer_head *bh, *head, *tail;
  5. /*分配buffer_head */
  6. head = alloc_page_buffers(page, blocksize, 1);
  7. bh = head;
  8. /*建立page下的buffer head为循环链表 */
  9. do {
  10. bh->b_state |= b_state;
  11. tail = bh;
  12. bh = bh->b_this_page;
  13. } while (bh);
  14. tail->b_this_page = head;
  15. /* page与buffer_head 关联 */
  16. attach_page_buffers(page, head);
  17. }

2.3 buffer head 磁盘空间分配


ext4_get_block主要分配磁盘空间,并调用map_bh建立buffer_head与磁盘块的映射

  1. static inline void
  2. map_bh(struct buffer_head *bh, struct super_block *sb, sector_t block)
  3. {
  4. set_buffer_mapped(bh);
  5. bh->b_bdev = sb->s_bdev;
  6. bh->b_blocknr = block;
  7. bh->b_size = sb->s_blocksize;
  8. }

到这里,page,buffer_head和磁盘块的映射关系建立完成,之后的流程就是等待write数据异步写到磁盘

3 BIO提交

 当buffer_head建立好后,就可以直接发起bio操作,这里以读流程来说明:
bh_submit_read是同步读函数,会等待buffer_head为unlock状态,

  1. int bh_submit_read(struct buffer_head *bh)
  2. {
  3. BUG_ON(!buffer_locked(bh));
  4. /*如果BH已经跟磁盘内容一致,则不需要发起BIO */
  5. if (buffer_uptodate(bh)) {
  6. unlock_buffer(bh);
  7. return 0;
  8. }
  9. get_bh(bh);
  10. /*设置回调函数 */
  11. bh->b_end_io = end_buffer_read_sync;
  12. submit_bh(READ, bh);
  13. /*等待BH为unlock状态 */
  14. wait_on_buffer(bh);
  15. if (buffer_uptodate(bh))
  16. return 0;
  17. return -EIO;
  18. }

submit_bh直接调用submit_bh_wbc函数发起bio
可以看到,到了bio层,就没有buffer head这个概念了,直接用page和bi_sector来操作对应的块

  1. /*提交bio */
  2. static int submit_bh_wbc(int rw, struct buffer_head *bh,
  3. unsigned long bio_flags, struct writeback_control *wbc)
  4. {
  5. struct bio *bio;
  6. int ret = 0;
  7. /*分配BIO */
  8. bio = bio_alloc(GFP_NOIO, 1);
  9. if (wbc) {
  10. wbc_init_bio(wbc, bio);
  11. wbc_account_io(wbc, bh->b_page, bh->b_size);
  12. }
  13. /*逻辑块号转换成扇区号 */
  14. bio->bi_sector = bh->b_blocknr * (bh->b_size >> 9);
  15. bio_set_dev(bio, bh->b_bdev);
  16. bio->bi_io_vec[0].bv_page = bh->b_page;
  17. bio->bi_io_vec[0].bv_len = bh->b_size;
  18. /*当前bh的页内偏移*/
  19. bio->bi_io_vec[0].bv_offset = bh_offset(bh);
  20. bio->bi_vcnt = 1;
  21. bio->bi_size = bh->b_size;
  22. bio->bi_end_io = end_bio_bh_io_sync;
  23. bio->bi_private = bh;
  24. bio->bi_flags |= bio_flags;
  25. /*对读写进行完全检查 */
  26. guard_bh_eod(rw, bio, bh);
  27. if (buffer_meta(bh))
  28. rw |= REQ_META;
  29. if (buffer_prio(bh))
  30. rw |= REQ_PRIO;
  31. bio_get(bio);
  32. /*提交一个bio */
  33. submit_bio(rw, bio);
  34. bio_put(bio);
  35. return ret;
  36. 3099,1 88%

4.buffer_head的状态
BH_Uptodate:表示BH的数据是最新的,甚至比磁盘还新(uptodate|dirtry)
BH_Dirty: BH数据是脏的,需要回刷到磁盘块
BH_Lock: BH正在进行IO操作
BH_Mapped: BH建立了磁盘映射
 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/601618
推荐阅读
相关标签
  

闽ICP备14008679号