ext4通过页缓存优先响应read(),未命中时由inode/extent树定位逻辑块,经bio提交至块层,最终由NVMe驱动转为Submission Queue命令;O_DIRECT跳过页缓存但需内存对齐,路径解析依赖dentry/inode缓存。

ext4 文件系统如何把 read() 调用变成磁盘读取
Linux 应用调用 read() 时,不会直接发命令给硬盘。它走的是内核抽象层:先查页缓存(page cache),命中就直接返回;没命中才触发底层 I/O。ext4 作为文件系统驱动,负责把“读文件第 1024 字节”翻译成“读物理块设备上某几个 block”,而这些 block 的位置由 ext4 的 inode、间接块、extent 树共同决定。
关键点在于:ext4 不管理扇区地址,只管逻辑块号(logical block number);真正映射到磁盘 LBA 是由块设备层(如 sd 驱动)和 SCSI/ATA 协议栈完成的。
- ext4 的
inode中记录文件大小、权限、以及指向数据块的指针(直接块、间接块或 extent) - 小文件常用直接块;大文件倾向用
extent(连续块范围),减少元数据开销 -
read()触发generic_file_read_iter()→ext4_readpage()→mpage_readpages()→ 最终提交 bio 到块层
从 bio 到 NVMe SSD 的实际写入路径
当 ext4 准备好要读的逻辑块列表后,会构造一个或多个 bio 结构体,交给通用块层(blk-mq)。这时还没碰硬件——bio 会被排队、合并、限速、加密(如果启用了 dm-crypt),再经由设备队列送到驱动。
对 NVMe 设备来说,最终调用的是 nvme_submit_cmd(),把命令放进 Submission Queue,由 SSD 控制器自己拉取执行。注意:NVMe 协议绕过了传统 IDE/SCSI 的中间层,所以延迟更低,但调试时也更难抓到“中间状态”。
- 同一块数据可能被
page cache、buffer cache(已弱化)、SSD 内部 DRAM 缓存、NAND 闪存的 page buffer多次缓存 -
hdparm -I /dev/nvme0n1可查控制器是否启用写缓存(Write Cache),这直接影响fsync()是否真落盘 - 使用
blktrace+btt可跟踪一个read()在块层各阶段耗时,定位卡点在调度器、驱动还是设备
为什么 open("/path/to/file", O_DIRECT) 会跳过页缓存
O_DIRECT 意味着应用告诉内核:“别管我的内存是不是对齐、别用你的 page cache,我来负责缓冲,你直通块层。” 这要求用户空间缓冲区地址和长度都按 logical_block_size 对齐(通常是 512B 或 4K),否则 read() 返回 -EINVAL。
跳过页缓存看似更快,实则风险不少:没有缓存复用、每次都是真实 I/O、且容易因对齐失败静默降级为普通读(取决于内核版本和文件系统)。
- ext4 在挂载时若指定
dioread_nolock,可避免O_DIRECT读时对 inode 加锁,提升并发性能 -
io_uring配合O_DIRECT是当前高性能 I/O 的主流组合,但需检查内核是否开启CONFIG_IO_URING - 不要在数据库日志写入等强一致性场景盲目禁用页缓存——
page cache提供了统一的脏页回写和fsync语义
用户态程序看到的路径名怎么变成磁盘地址
路径解析不是一次性动作。每次 open() 或 stat() 都要从根目录开始 walk:/proc/1234/fd/0 → 找到 dentry → 关联 inode → 确认文件类型和权限 → 最终拿到 file 结构体。这个过程大量依赖 dentry cache 和 inode cache,否则每 open 一次都要读磁盘上的目录块。
ext4 的目录项(struct ext4_dir_entry_2)是变长结构,按 hash 分桶(htree 目录),所以大目录下 ls 快,但 find . -name "*.log" 仍要遍历所有叶子块。
-
ls -l显示的inode号来自 ext4 的inode table,不是磁盘物理地址;同一个 inode 号在不同挂载点可能指向不同内容(bind mount) -
/proc/mounts中的uuid和fstype决定了内核用哪个文件系统驱动处理该设备 - 硬链接共用一个
inode,软链接则是独立inode存放路径字符串——这意味着readlink()不触发数据块读取,只读 inode 自身
page cache 回写由 writeback 内核线程控制,而 ext4 journal 提交又依赖 jbd2 线程,两者节奏错开就可能造成意外延迟。这类细节不出问题时没人注意,一出就是疑难杂症。










