1.ext 文件系统核心架构
1.1.磁盘布局(以 ext4 为例)
c
+-----------+-----------+-----------+-------------------+
| 引导块 | 块组0 | 块组1 | ... 块组N |
| (1KB/2KB) | | | |
+-----------+-----------+-----------+-------------------+
每个块组包含:
- 超级块 (Superblock):文件系统全局信息
- 组描述符 (GDT):块组元数据
- 数据块位图 / inode 位图
- inode 表
- 数据块
1.2 关键数据结构
| 结构 | 作用 |
|---|---|
struct ext4_inode |
文件元数据(模式、大小、时间戳、块指针) |
struct ext4_extent |
ext4 引入的区间树,替代 ext2/3 的直接/间接块指针,加速大文件访问 |
struct ext4_sb_info |
内存中的超级块,挂载时读入 |
struct ext4_inode_info |
内存中的 inode 私有数据 |
2.反向映射(Reverse Mapping)
反向映射解决的核心问题:给定一个物理页(struct page),找出哪些进程的哪些虚拟地址映射了它。这对页回收(page reclaim)、写时复制(COW)、内存去重(KSM)至关重要。
2.1.匿名页的 RMAP
通过 struct anon_vma 红黑树实现:
c
物理页 page
└── mapping 指向 anon_vma
└── 红黑树保存所有映射此页的 vma
└── 每个 vma 通过 rmap_item 关联
流程:
- fork() 时,子进程继承父进程的 anon_vma,加入同一个链表
- 页故障分配匿名页时,page->mapping = anon_vma
- 需要反向查找时(如 try_to_unmap()),遍历 anon_vma 红黑树,找到所有页表项(PTE),清除并回收
2.2.文件页的 RMAP
文件页(page cache)的反向映射更复杂,因为多个文件偏移可能映射到不同进程地址空间:
c
struct address_space (inode->i_mapping)
├── i_mmap: 红黑树,保存所有映射此文件区间的 vma(按文件偏移排序)
├── i_mmap_nonlinear: 非线性映射链表
└── page_tree: 基数树,管理文件页
关键结构:
- struct vm_area_struct 的 vm_file 指向文件,vm_pgoff 记录文件偏移
- struct address_space 的 i_mmap 树以 (pgoff, vma) 为键
反向查找流程(try_to_unmap_file()):
- 从 page->index 得到文件偏移
- 在 address_space->i_mmap 树中查找覆盖该偏移的所有 vma
- 对每个 vma,计算虚拟地址:addr = vma->vm_start + (page->index - vma->vm_pgoff) * PAGE_SIZE
- 找到对应 PTE,执行解除映射
2.3.RMAP 的锁与性能
- anon_vma->rwsem:保护匿名页 RMAP 树
- i_mmap_rwsem:保护文件映射树
- 锁竞争:大量进程共享文件映射(如共享库)时,i_mmap_rwsem 成为瓶颈
- 优化:Linux 4.x+ 引入 per-VMA lock,减少全局锁粒度
3.open/write/read/close 完整执行流程
3.1.open() 路径
c
用户态: open("/a/b/c", O_RDWR)
↓
VFS: sys_open() → do_sys_open()
├── getname() # 拷贝路径到内核
├── get_unused_fd() # 分配 fd
└── do_filp_open() # 核心路径
↓
path_openat()
├── link_path_walk() # 路径解析:逐层查找 dentry
│ └── 每级:d_lookup() → 命中 dcache?返回 : 调用具体文件系统 → ext4_lookup()
├── do_last()
│ └── lookup_fast() / lookup_slow()
└── vfs_open()
├── ext4_file_open() # 文件系统特定初始化
└── 分配 struct file,设置 f_op = ext4_file_operations
3.2.read() 路径(Buffered I/O)
c
用户态: read(fd, buf, count)
↓
VFS: sys_read() → vfs_read()
├── file->f_op->read_iter() # ext4 使用 generic_file_read_iter()
↓
页缓存层 (Page Cache)
├── find_get_page(mapping, index) # 查页缓存
│ └── 命中?返回 page
│ └── 未命中?readahead + 发起 I/O
└── 未命中时:
└── page_cache_sync_readahead()
├── ext4_readpages() # 文件系统层
│ └── mpage_readpages() # 多页 bio 提交
│ └── ext4_mpage_readpages()
│ ├── ext4_map_blocks() # extent 树查找逻辑→物理块
│ └── submit_bio() # 下发到块层
└── 等待 I/O 完成(lock_page())
└── 拷贝到用户空间:copy_page_to_iter()
关键机制:
- 预读(Readahead):ondemand_readahead 检测顺序访问模式,异步预读后续页
- 零拷贝优化:sendfile() / splice() 绕过用户态缓冲区,DMA 页缓存到网卡/磁盘
3.3.write() 路径(Buffered I/O)
c
用户态: write(fd, buf, count)
↓
VFS: sys_write() → vfs_write() → ext4_file_write_iter()
↓
generic_perform_write() # 通用缓冲写
├── ext4_write_begin() # 准备页
│ ├── grab_cache_page_write_begin() # 分配/查找页缓存页
│ ├── ext4_journal_start() # 日志事务开始(ordered 模式)
│ └── 若页不存在:block_read_full_page()(写前读,防止覆盖)
├── copy_page_from_iter() # 用户数据拷贝到页缓存
└── ext4_write_end() # 完成写
├── ext4_journal_stop() # 结束日志事务
└── mark_page_accessed() + set_page_dirty()
脏页回写(异步路径):
- balance_dirty_pages():脏页比例过高时,唤醒 flush-xxx 内核线程
- ext4_writepages():将脏页收集为 bio,批量提交块层
3.4.close() 路径
c
用户态: close(fd)
↓
VFS: sys_close() → __close_fd() → filp_close()
├── dput(filp->f_dentry) # 递减 dentry 引用
├── iput(filp->f_inode) # 递减 inode 引用(若到 0,释放内存 inode)
└── ext4_release_file() # ext4 清理
└── ext4_discard_preallocations() # 释放预分配块
4.性能优化全景
4.1.挂载参数优化
| 参数 | 作用 | 场景 |
|---|---|---|
noatime / relatime |
禁用/减少 atime 更新(减少随机写) | 高并发读 |
data=writeback |
日志仅保证元数据,数据写无序 | 极高性能,崩溃后需 fsck |
data=ordered |
默认,数据先刷盘再提交元数据日志 | 平衡安全与性能 |
nobarrier |
禁用写屏障(需电池备份缓存) | SSD + RAID 控制器 |
inode_readahead_blks |
inode 表预读块数 | 大量小文件 |
extents |
启用 extent(ext4 默认) | 大文件 |
4.2.应用层优化建议
| 优化点 | 具体做法 |
|---|---|
| 减少元数据操作 | 批量 open(),避免频繁 stat() |
| 顺序 I/O | 使用 posix_fadvise(POSIX_FADV_SEQUENTIAL) |
| 直接 I/O | 数据库场景用 O_DIRECT 绕过页缓存,结合自研缓存 |
| 大页支持 | 启用 HugeTLB / THP 减少 TLB miss |
| 异步 I/O | 使用 io_uring(现代最高效)或 aio |
| 避免写放大 | 对齐写入到 4KB 边界,避免 read-modify-write |
4.3.监控与调优工具
c
# 查看 ext4 挂载参数
mount | grep ext4
# 实时 I/O 统计
iostat -x 1
# 页缓存命中率
bcc-cachestat # BPF 工具
# ext4 延迟分配统计
cat /sys/fs/ext4/*/delayed_allocation_blocks
# 查看碎片
e4defrag -c /path
5.流程全景图
c
用户空间
↓ 系统调用
VFS (vfs_read/write/open)
├── 页缓存层 (Page Cache / RMAP)
│ ├── 命中:直接拷贝
│ └── 未命中:预读 → 块层
├── 文件系统层 (ext4)
│ ├── 路径解析 → dentry cache → inode → extent 树
│ ├── 延迟分配(写时)
│ └── 日志事务(JBD2)
└── 块层 (Block Layer)
├── I/O 调度(mq-deadline / none / kyber)
└── SCSI/NVMe 驱动 → 硬件