跳转至

第三篇:页缓存与缓冲区 I/O — 从 folio 到 bio

源码:mm/filemap.c (4752行), fs/buffer.c (3078行) | 头文件:include/linux/fs.h (3660行), include/linux/buffer_head.h (538行)

系列目录:VFS 内核源码深度解析


1. 双重缓存架构

Linux 内核在文件 I/O 层面存在两种缓存抽象:

 用户态              内核态                       存储设备
┌────────┐    ┌─────────────────────────────┐
│ read   │    │                             │
│ write  │───►│  Page Cache (folio-based)   │  ← 现代主缓存
│ mmap   │    │    mm/filemap.c             │
└────────┘    │    xarray (i_pages)         │
              │     ┌──────────────────┐    │
              │     │  address_space   │    │
              │     │   (fs.h:473)     │    │
              │     └────────┬─────────┘    │
              │              │              │
              │     ┌────────▼─────────┐    │
              │     │  Buffer Cache    │    │  ← 传统块级缓存
              │     │   fs/buffer.c    │    │
              │     │   buffer_head ───┼────┼──► bio → block layer → disk
              │     └──────────────────┘    │
              └─────────────────────────────┘

Page Cachemm/filemap.c):以 folio 为单位的页面缓存,使用 xarray 基树管理,是主要缓存层。Buffer Cachefs/buffer.c):以 buffer_head 为单位的块级缓存,用于文件系统元数据(superblock、inode table、位图)和传统文件系统(ext*、FAT)。两者共享同一个 folio:buffer_head 通过 b_folio 指向 folio 中的页面,确保一致性。现代文件系统(XFS、btrfs)正迁移到 iomap 替代 buffer_head。


2. struct address_space (fs.h:473-491)

struct address_space {
    struct inode        *host;             // 474: 所属 inode 或块设备
    struct xarray       i_pages;           // 475: ★ folio 的主存储! xarray 基树
    struct rw_semaphore invalidate_lock;   // 476: 失效/截断的排他锁
    gfp_t               gfp_mask;          // 477: 页面分配的 GFP 标志
    atomic_t            i_mmap_writable;   // 478: VM_SHARED 写映射计数
#ifdef CONFIG_READ_ONLY_THP_FOR_FS
    atomic_t            nr_thps;           // 481: THP 大页计数
#endif
    struct rb_root_cached i_mmap;          // 483: VMA 映射的红黑树(回写用)
    unsigned long       nrpages;           // 484: 页面总数 (xa_lock 保护)
    pgoff_t             writeback_index;   // 485: 回写起始偏移(页号)
    const struct address_space_operations *a_ops; // 486: 操作方法表
    unsigned long       flags;             // 487: AS_* 标志 (错误等)
    errseq_t            wb_err;            // 488: 写回错误序号跟踪
    spinlock_t          i_private_lock;    // 489: 拥有者私有锁
    struct rw_semaphore i_mmap_rwsem;      // 490: 保护 i_mmap + writable
} __attribute__((aligned(sizeof(long)))) __randomize_layout;

核心字段

host (474):指向拥有此 address_space 的 inode。对块设备,指向 bdev->bd_inode

i_pages (475):xarray——高效的基树(radix tree 的现代替代)。每个条目是一个 folio 指针。支持: - O(log n) 查找、插入、删除 - 位标记(mark)进行脏页/回写跟踪:

// include/linux/fs.h:498-501
#define PAGECACHE_TAG_DIRTY      XA_MARK_0  // folio 脏
#define PAGECACHE_TAG_WRITEBACK  XA_MARK_1  // folio 正在回写
#define PAGECACHE_TAG_TOWRITE    XA_MARK_2  // 待回写

i_mmap (483):红黑树(rb_root_cached),存储所有映射到该文件的 vm_area_struct。回写脏页时,需要遍历 i_mmap 找到所有映射此 folio 的进程,刷新 TLB 并使缓存行失效。

nrpages (484):快速空缓存判断——mapping->nrpages == 0 表示无缓存页。

wb_err (488)errseq_t 类型。每个文件的 file->f_wb_err 记录最近见过的错误序号。通过 filemap_sample_wb_err / filemap_check_wb_err 检测自上次检查以来是否有新的写回错误。

invalidate_lock (476):在 invalidate_inode_pages2_range 等截断操作期间阻止新页插入。页缓存一致性关键——防止添加和截断之间的竞态。


3. struct address_space_operations (fs.h:401-442)

struct address_space_operations {
    int (*read_folio)(struct file *, struct folio *);              // 402
    int (*writepages)(struct address_space *, struct writeback_control *); // 405
    bool (*dirty_folio)(struct address_space *, struct folio *);   // 408
    void (*readahead)(struct readahead_control *);                 // 410
    int (*write_begin)(const struct kiocb *, struct address_space *mapping,
                loff_t pos, unsigned len,
                struct folio **foliop, void **fsdata);             // 412
    int (*write_end)(const struct kiocb *, struct address_space *mapping,
                loff_t pos, unsigned len, unsigned copied,
                struct folio *folio, void *fsdata);                // 415
    ssize_t (*direct_IO)(struct kiocb *, struct iov_iter *iter);   // 424
    int (*migrate_folio)(struct address_space *, struct folio *dst,
                struct folio *src, enum migrate_mode);              // 429
    bool (*release_folio)(struct folio *, gfp_t);                  // 422
    bool (*is_partially_uptodate)(struct folio *, size_t, size_t); // 432
    void (*is_dirty_writeback)(struct folio *, bool *, bool *);    // 434
    int (*error_remove_folio)(struct address_space *, struct folio *); // 435
};

核心操作对: - read_folio:从存储设备读取一个 folio 的内容到页缓存 - write_begin + write_end:两阶段写——begin 获取并锁定 folio,end 标记脏 - writepages:writeback 线程调用,批量将脏 folio 写入存储 - direct_IO:绕过页缓存的直接 I/O - migrate_folio:内存迁移(NUMA、compaction)


4. 读路径:从系统调用到 folio

用户 read(fd, buf, 4096)
        ▼ (SYSCALL_DEFINE3(read, ...))
    ksys_read(fd, buf, count)
        ▼ (VFS layer)
    vfs_read(file, buf, count, &pos)        [fs/read_write.c]
        ▼ (file_operations→read_iter)
    generic_file_read_iter(iocb, iter)      [mm/filemap.c:2957]
        ├── IOCB_DIRECT? → direct_IO → 块层 → 磁盘
        │   └── 短读(Btrfs压缩…)? → 回退到 filemap_read
        └── filemap_read(iocb, iter)         [mm/filemap.c:2769]
              ├── filemap_get_pages()        [内部函数]
              │     ├── xarray 查找 folio (O(log n))
              │     ├── 未命中/非uptodate → readahead + read_folio
              │     │     └── a_ops→read_folio → buffer_head → bio → 磁盘
              │     └── 收集到 folio_batch (PAGEVEC_SIZE=15)
              ├── flush_dcache_folio (架构缓存一致性)
              ├── copy_folio_to_iter() → 复制到用户空间
              └── file_accessed() → 更新 atime

generic_file_read_iter (filemap.c:2957-2999)

generic_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
{
    size_t count = iov_iter_count(iter);
    ssize_t retval = 0;
    if (!count) return 0;

    if (iocb->ki_flags & IOCB_DIRECT) {
        struct inode *inode = mapping->host;
        retval = kiocb_write_and_wait(iocb, count); // 等回写完成
        file_accessed(file);
        retval = mapping->a_ops->direct_IO(iocb, iter); // 直接读盘
        // ... Btrfs 压缩短读回退逻辑 ...
    }
    return filemap_read(iocb, iter, retval); // ← 通用页缓存读
}

DIO 路径的特殊处理:kiocb_write_and_wait 确保所有脏数据先写入磁盘,避免 DIO 读到旧数据。短读场景(Btrfs 压缩、EOF)回退到 filemap_read 走页缓存。

filemap_read — 核心批量读 (filemap.c:2769-2881)

ssize_t filemap_read(struct kiocb *iocb, struct iov_iter *iter,
        ssize_t already_read)
{
    struct address_space *mapping = filp->f_mapping;
    struct folio_batch fbatch;
    // 边界检查...

    folio_batch_init(&fbatch);
    do {
        cond_resched();

        // ★ 批量获取 folio (预读+锁+等待uptodate)
        error = filemap_get_pages(iocb, iter->count, &fbatch, false);

        isize = i_size_read(inode);
        end_offset = min_t(loff_t, isize, iocb->ki_pos + iter->count);
        writably_mapped = mapping_writably_mapped(mapping);

        for (i = 0; i < folio_batch_count(&fbatch); i++) {
            struct folio *folio = fbatch.folios[i];
            offset = iocb->ki_pos & (folio_size(folio) - 1);
            bytes = min_t(loff_t, end_offset - iocb->ki_pos,
                          folio_size(folio) - offset);

            if (writably_mapped)
                flush_dcache_folio(folio); // 架构级缓存一致性

            // ★ 复制到用户空间
            copied = copy_folio_to_iter(folio, offset, bytes, iter);
            already_read += copied;
            iocb->ki_pos += copied;
        }
        // 释放所有 folio 引用
        for (i = 0; i < folio_batch_count(&fbatch); i++)
            folio_put(fbatch.folios[i]);
    } while (iov_iter_count(iter) && iocb->ki_pos < isize && !error);

    file_accessed(filp);
    return already_read ? already_read : error;
}

filemap_get_pages 内部逻辑

  1. 在 xarray 中查找 iocb->ki_pos 对应的 folio
  2. 不存在或非 uptodate → 触发预读 (filemap_readahead) → 等待 folio 锁定 → 等待 uptodate
  3. 批量收集连续的 folio → folio_batch(一次最多 PAGEVEC_SIZE=15
  4. 返回后 filemap_read 遍历复制

folio_batch 批量收集减少 xarray 锁的获取次数和 copy_to_user 的调用开销。


5. filemap_fault — mmap 缺页中断 (filemap.c:3513)

filemap_fault 是 VMA 的 .fault 回调,处理 mmap 映射文件的缺页:

vm_fault_t filemap_fault(struct vm_fault *vmf)
{
    struct address_space *mapping = file->f_mapping;
    pgoff_t index = vmf->pgoff;
    struct folio *folio;

    // 1. 在 xarray 中查找
    folio = filemap_get_folio(mapping, index);
    if (likely(!IS_ERR(folio))) {
        // 找到了! 触发异步预读
        if (!(vmf->flags & FAULT_FLAG_TRIED))
            fpin = do_async_mmap_readahead(vmf, folio);
    } else {
        // 主缺页 (MAJOR FAULT) — 需要读磁盘
        count_vm_event(PGMAJFAULT);
        fpin = do_sync_mmap_readahead(vmf);
        // 分配并加入 folio:
        folio = __filemap_get_folio(mapping, index,
                          FGP_CREAT|FGP_FOR_MMAP, vmf->gfp_mask);
    }

    // 2. 锁 folio (可能需要释放 mmap_lock 避免死锁)
    lock_folio_maybe_drop_mmap(vmf, folio, &fpin);

    // 3. 截断检查
    if (unlikely(folio->mapping != mapping)) { retry; }

    // 4. uptodate 检查 —— 不是最新则 read_folio
    if (unlikely(!folio_test_uptodate(folio)))
        goto page_not_uptodate;  // → 可能需要 read_folio

    // 5. 插入进程页表
    // ... vmf_insert_* → set_pte ...
    return VM_FAULT_LOCKED;
}

流程: 1. xarray 查找 → 命中触发异步预读 → 未命中触发同步预读 + 分配 folio 2. lock_folio_maybe_drop_mmap:锁 folio,如果锁竞争可能释放 mmap_lock 3. 验证 folio->mapping == mapping(防止在锁竞争期间被截断) 4. 非 uptodate → read_folio → 磁盘 I/O → 等待 → 插入页表 5. 返回 VM_FAULT_LOCKED / VM_FAULT_MAJOR / VM_FAULT_OOM


6. 写路径

用户 write(fd, buf, count)
    ksys_write → vfs_write
    generic_file_write_iter(iocb, from)   [filemap.c:4459]
        ├── inode_lock(inode) — 获取 i_rwsem 写锁
        ├── generic_write_checks — 边界/权限/文件大小扩展
        └── __generic_file_write_iter     [内部循环]
              └── 循环每段数据:
                    ├── write_begin(pos, len)
                    │     ├── xarray 查找/创建 folio
                    │     ├── 确保 folio uptodate (触发 read_folio)
                    │     └── 锁定 folio
                    ├── copy_from_user → 数据复制到 folio
                    └── write_end(pos, len, copied, folio)
                          ├── folio_mark_dirty
                          ├── 更新 i_size (如果扩展了文件)
                          └── folio_unlock

generic_file_write_iter (filemap.c:4459-4474)

ssize_t generic_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
    struct inode *inode = file->f_mapping->host;
    ssize_t ret;

    inode_lock(inode);                       // i_rwsem 写锁
    ret = generic_write_checks(iocb, from);  // 杀 SUID、文件大小检查
    if (ret > 0)
        ret = __generic_file_write_iter(iocb, from);
    inode_unlock(inode);

    if (ret > 0)
        ret = generic_write_sync(iocb, ret); // O_SYNC/O_DSYNC 强制回写
    return ret;
}

write_begin / write_end 两阶段设计

分离允许 VFS 在两个调用之间做 copy_from_user:持锁时间最小化,文件系统在 write_end 中进行事务提交(如 ext4 journal)。


7. struct buffer_head (buffer_head.h:60-82)

struct buffer_head {
    unsigned long b_state;            // 61: 状态位 (BH_Uptodate, BH_Dirty…)
    struct buffer_head *b_this_page;  // 62: 页面内循环链表
    union {
        struct page *b_page;          // 64: 关联页面(legacy)
        struct folio *b_folio;        // 65: 关联 folio (modern)
    };

    sector_t b_blocknr;               // 68: 磁盘逻辑块号
    size_t b_size;                    // 69: buffer 大小(通常=blksize)
    char *b_data;                     // 70: 指向页内数据的指针

    struct block_device *b_bdev;      // 72: 块设备
    bh_end_io_t *b_end_io;            // 73: I/O 完成回调
    void *b_private;                  // 74: 回调私有数据
    struct list_head b_assoc_buffers; // 75: 关联的映射链表
    struct mapping_metadata_bhs *b_mmb; // 76: 元数据 bh 链表头
    atomic_t b_count;                 // 78: 引用计数
    spinlock_t b_uptodate_lock;       // 79: uptodate 序列化 (第一个bh)
};

buffer_head 与 folio 的关联

folio
┌──────────────────────────────────────┐
│        page (PAGE_SIZE 4096)         │
│ ┌────────┬────────┬────────┬──────┐ │
│ │ bh[0]  │ bh[1]  │ bh[2]  │bh[3] │ │  (如果 blksize=1024)
│ │ b_data │ b_data │ b_data │b_data│ │
│ │ Uptod? │ Dirty! │ Dirty! │Clean │ │
│ └───┬────┴────┬───┴────┬───┴──┬───┘ │
│     │ p_next→ │ p_next→│p_nxt→│ ↺   │  b_this_page 循环链表
└─────┼─────────┼────────┼──────┼──────┘
      │         │        │      │
      ▼         ▼        ▼      ▼
  block N   block N+1 block N+2 block N+3  (磁盘)
  • folio_buffers(folio) → 获取该 folio 的第一个 buffer_head
  • b_this_page → 页面内循环链表,遍历所有 buffer_head
  • b_data → 指向页内偏移的指针
  • b_bdev + b_blocknr → 唯一标识磁盘位置

8. buffer_head 状态标志和 API

BH_Uptodate   — buffer 数据最新
BH_Dirty      — buffer 数据脏 (需回写)
BH_Lock       — I/O 进行中 (lock_buffer 阻塞到 unlock)
BH_Req        — 已提交 I/O 请求
BH_Mapped     — 有磁盘映射 (b_blocknr 有效)
BH_New        — 新分配的 buffer
BH_Delay      — 延迟分配 (未分配磁盘块)
BH_Unwritten  — 分配但未写入
BH_Meta       — 元数据 buffer (调度器可优先)
BH_Prio       — 高优先级 I/O

宏展开(buffer_head.h:90-103)生成:

buffer_uptodate(bh), set_buffer_dirty(bh), buffer_dirty(bh)
clear_buffer_dirty(bh), lock_buffer(bh), unlock_buffer(bh)

lock_buffer (buffer.c:69-72) 使用 wait_on_bit_lock_io(&bh->b_state, BH_Lock, TASK_UNINTERRUPTIBLE) 阻塞等待。


9. __bread_gfp — 同步读块 (buffer.c:1399-1418)

struct buffer_head *__bread_gfp(struct block_device *bdev, sector_t block,
        unsigned size, gfp_t gfp)
{
    struct buffer_head *bh;
    gfp |= mapping_gfp_constraint(bdev->bd_mapping, ~__GFP_FS);
    gfp |= __GFP_NOFAIL;                     // 不失败原则

    bh = bdev_getblk(bdev, block, size, gfp); // 获取或分配 bh

    if (likely(bh) && !buffer_uptodate(bh))
        bh = __bread_slow(bh);               // 非最新 → 等待 I/O
    return bh;
}

同步读流程:

__bread_gfp(bdev, block, 1024, GFP_NOFS)
  ├── bdev_getblk(bdev, block, size, gfp)
  │     ├── __find_get_block → bh LRU 查找
  │     │     └── 命中 → b_count++, 返回
  │     └── __getblk_slow → 分配 bh + 关联 folio
  │           └── 找不到 folio → 分配新 folio + attach bh
  ├── buffer_uptodate(bh)?
  │     ├── YES → 直接返回
  │     └── NO  → __bread_slow(bh)
  │           ├── lock_buffer(bh) → 等之前 I/O 完成
  │           ├── 再查 uptodate? → unlock+return
  │           └── bh->b_end_io = end_buffer_read_sync
  │               submit_bh(REQ_OP_READ, bh)
  │               wait_on_bit_io(BH_Lock) ← 阻塞等 I/O 完成
  │               buffer_uptodate? → 返回


10. submit_bh_wbc — I/O 提交 (buffer.c:2692-2738)

static void submit_bh_wbc(blk_opf_t opf, struct buffer_head *bh,
                          enum rw_hint write_hint,
                          struct writeback_control *wbc)
{
    struct bio *bio;

    // 保证: 已锁, 已映射, 有 end_io
    BUG_ON(!buffer_locked(bh) || !buffer_mapped(bh) || !bh->b_end_io);

    // 重写时清除旧错误
    if (test_set_buffer_req(bh) && (op == REQ_OP_WRITE))
        clear_buffer_write_io_error(bh);
    if (buffer_meta(bh)) opf |= REQ_META;    // 元数据 → 调度器优待
    if (buffer_prio(bh)) opf |= REQ_PRIO;

    bio = bio_alloc(bh->b_bdev, 1, opf, GFP_NOIO);

    // ★ bh → bio 映射:
    bio->bi_iter.bi_sector = bh->b_blocknr * (bh->b_size >> 9);
    bio->bi_write_hint = write_hint;
    bio_add_folio_nofail(bio, bh->b_folio, bh->b_size, bh_offset(bh));
    bio->bi_end_io = end_bio_bh_io_sync;
    bio->bi_private = bh;

    guard_bio_eod(bio);                   // 截断到设备末尾
    if (wbc) {
        wbc_init_bio(wbc, bio);           // writeback 统计
        wbc_account_cgroup_owner(wbc, bh->b_folio, bh->b_size);
    }
    blk_crypto_submit_bio(bio);           // 提交到块层!
}

// 便捷接口 (buffer.c:2740-2744)
void submit_bh(blk_opf_t opf, struct buffer_head *bh)
{
    submit_bh_wbc(opf, bh, WRITE_LIFE_NOT_SET, NULL);
}

bh → bio 转换:块号×块大小→扇区号 → bio_add_folio_nofail 添加 folio 片段 → blk_crypto_submit_bio


11. I/O 完成回调

// buffer.c:158-163
void end_buffer_read_sync(struct buffer_head *bh, int uptodate)
{
    put_bh(bh);                              // 释放 __bread_gfp 的额外引用
    __end_buffer_read_notouch(bh, uptodate); // uptodate→set_uptodate, unlock
}

// buffer.c:165-177
void end_buffer_write_sync(struct buffer_head *bh, int uptodate)
{
    if (uptodate) set_buffer_uptodate(bh);
    else { buffer_io_error(bh, ", lost sync page write"); clear }
    unlock_buffer(bh);       // 唤醒 wait_on_buffer 的等待者
    put_bh(bh);
}
完成流:
block layer → bio->bi_end_io = end_bio_bh_io_sync(bio)
  → bh = bio->bi_private
  → bh->b_end_io(bh, !bio->bi_status)
      ├── end_buffer_read_sync → __end_buffer_read_notouch
      │     → set_buffer_uptodate + clear BH_Lock(unlock)
      └── end_buffer_write_sync → set/clear_uptodate + unlock

12. Writeback 回写路径

回写由内核 writeback 线程统一调度,而非 write 系统调用直接触发:

脏页产生:
  write → write_end → folio_mark_dirty
  mmap → PTE dirty 位 → __set_page_dirty (通过页表扫描)

回写触发:
  ├── 定期: wb_workfn (bdi_writeback 工作队列)
  ├── 内存压力: shrinker → shrink_page_list → writepages
  ├── 显式: sync() / fsync() / fdatasync()
  └── 脏页比例: /proc/sys/vm/dirty_ratio 超限

回写执行:
  wb_workfn → wb_writeback → writeback_sb_inodes
    → __writeback_single_inode(inode, wbc)
        ├── do_writepages(mapping, wbc)
        │     → mapping->a_ops->writepages(mapping, wbc)  // ext4_writepages
        │         → 收集脏 folio (通过 PAGECACHE_TAG_DIRTY)
        │         → bio_alloc + bio_add_folio(多页合并)
        │         → submit_bio → block layer → disk
        └── write_inode(inode, wbc)
              → sb->s_op->write_inode(inode, wbc)  // 回写元数据

evict 时脏页丢弃:
  evict(inode) → truncate_inode_pages_final(&inode->i_data)
    → 遍历 xarray 所有 folio
        → 脏 → cancel_dirty_page() 丢弃(文件已删)
        → 从 xarray 删除 → put_folio

13. Folio 时代的变更

旧术语 新术语 说明
struct page struct folio 可包含多个连续物理页
readpage → read_folio a_ops 读操作
writepage → writepages 批量回写 单页回写已废弃
lock_page → folio_lock 锁 folio
set_page_dirty → folio_mark_dirty 标记脏
page_mapping → folio_mapping 获取 mapping
PageUptodate → folio_test_uptodate uptodate 检查

迁移理由: 1. THP 大页:folio 可以是 2MB(512 基页),一次操作代替 512 次 2. 类型安全:编译器可区分 folio 和 page 3. 减少 cache 污染:folio 的 head struct 放最前面,tail pages 不含元数据

iomap — 现代替代

传统路径:write_begin/write_end + 每个块的 buffer_head 分配 → 锁开销大 现代路径:iomap 一次映射整个 I/O 范围:

// iomap 接口 (fs/iomap/):
iomap_file_buffered_write()    // 代替 write_begin/write_end
iomap_file_unshare()
iomap_zero_range()
iomap_truncate_page()

XFS 和 btrfs 已主要使用 iomap,减少了 buffer_head 的创建和锁开销。


14. DAX — 绕过页缓存

传统:            DAX (Direct Access):

用户 buffer       进程 mmap 地址
  copy_from_user    │ CPU load/store (直接!)
  ▼                 ▼
page cache        持久内存 (PMEM/NVDIMM)
  buffer_head       │ 内存总线
  ▼                 ▼
block layer       持久内存 DIMM
disk

fs/dax.c (~61KB) 实现 DAX 模式: - 绕过页缓存、buffer_head、bio、block layer - 持久内存的物理地址直接映射到进程页表 - dax_iomap_fault 通过 pfn_t 插入 PMEM 页到页表 - IS_DAX(inode) 判断是否在 DAX 模式

适用:数据库、虚拟机镜像等延迟敏感、自管理缓存的应用。


15. 读路径完整 ASCII 流程

read(fd, buf, 4096)
vfs_read()
generic_file_read_iter(iocb, iter)              [filemap.c:2957]
  ├── IOCB_DIRECT?
  │     └── YES → kiocb_write_and_wait() — 排空脏数据
  │              → direct_IO() — 直接读盘(PBLOCK)
  │              → 短读? → 回退到缓冲 I/O
  └── filemap_read(iocb, iter, retval)           [filemap.c:2769]
        ├── filemap_get_pages(&fbatch)            [内部函数]
        │     │
        │     ├── xa_load() — xarray 查找 folio
        │     ├── 命中 → folio_get, folio_mark_accessed
        │     └── 未命中 → filemap_readahead()
        │           └── a_ops→readahead()
        │               └── read_folio()            [a_ops:402]
        │                   ├── buffer_head: bdev_getblk→__bread_slow
        │                   │   → submit_bh(READ) → block layer   [buffer.c:2740]
        │                   ├── iomap: iomap_read_folio
        │                   │   → submit_bio → block layer
        │                   └── block layer → SCSI/NVMe → 磁盘
        │                       end_io → set_uptodate → unlock folio
        ├── for each folio in fbatch:
        │     ├── flush_dcache_folio() — 架构缓存一致性
        │     ├── copy_folio_to_iter() — copy_to_user
        │     └── folio_put() — 释放引用
        └── file_accessed() — 更新 atime (如果 atime 更新策略)

16. 总结

组件 位置 职责
address_space fs.h:473 inode 的页缓存抽象,连接 inode 和 folio
i_pages (xarray) fs.h:475 高效基树,存储 folio,支持标记
read_folio a_ops:402 从磁盘读取一个 folio 到缓存
write_begin/end a_ops:412/415 两阶段写:准备 folio + 标记脏
writepages a_ops:405 批量回写脏 folio
direct_IO a_ops:424 绕过页缓存的直接 I/O
buffer_head buffer_head.h:60 传统块级缓存,连接 folio 和块设备
__bread_gfp buffer.c:1399 同步块读取(元数据常用)
submit_bh buffer.c:2740 提交 bh 到 bio/块层
end_buffer_read_sync buffer.c:158 I/O 完成回调,unlock+set_uptodate
filemap_read filemap.c:2769 核心批量读(folio_batch 收集)
generic_file_read_iter filemap.c:2957 通用读入口(DIO + buffered)
filemap_fault filemap.c:3513 mmap 缺页处理
generic_file_write_iter filemap.c:4459 通用写入口
writeback (flusher) fs/fs-writeback.c 定期回写脏页线程
folio 现代页面单元,可多个物理页
iomap fs/iomap/ buffer_head 的现代替代

理解 address_space → xarray → folio → buffer_head → bio → block layer 这条数据通路是深入 VFS 与存储层交互的关键。


下一篇文章

➡️ 第四篇:路径名查找 — 从 open() 到 inode


💬 评论