read(fd, buf, 4096) 看似简单,内核旅程却穿过
VFS → 文件系统 → 页缓存 → 块层 →
设备驱动至少五层。理解全路径是性能调优的前提。
一、先看图
flowchart TD
SYSCALL[read / write 系统调用] --> FD[file 查找<br/>fdget]
FD --> FOP[f_op->read_iter<br/>/ write_iter]
FOP --> CACHE{页缓存<br/>命中?}
CACHE -- 命中 --> COPY[copy_to_user]
CACHE -- miss --> FS[文件系统<br/>iomap / readpage]
FS --> BIO[bio 提交<br/>块层]
BIO --> DISK[设备驱动<br/>NVMe / SCSI]
DISK --> IRQ[完成中断]
IRQ --> WAKEUP[唤醒进程]
FOP -->|O_DIRECT| DIO[直接 I/O<br/>跳过页缓存]
DIO --> BIO
classDef hot fill:#f0883e22,stroke:#f0883e,color:#adbac7;
classDef cache fill:#3fb95022,stroke:#3fb950,color:#adbac7;
classDef hw fill:#388bfd22,stroke:#388bfd,color:#adbac7;
class SYSCALL,FD,FOP hot
class CACHE,COPY,FS cache
class BIO,DISK,IRQ,WAKEUP,DIO hw
二、系统调用入口
// fs/read_write.c
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
// fdget → file → vfs_read → f_op->read_iter
}核心路径:ksys_read() →
vfs_read() → new_sync_read() →
call_read_iter()。
read_iter / write_iter 是现代
VFS 接口,使用 struct iov_iter
描述用户缓冲区。
三、iov_iter 与向量化 I/O
struct iov_iter {
u8 iter_type; // ITER_IOVEC, ITER_BVEC, ITER_PIPE, ITER_UBUF
size_t count;
// ...
};统一了 readv/writev、splice、sendfile、io_uring 等不同缓冲区类型。
四、页缓存命中路径
4.1 generic_file_read_iter
// mm/filemap.c
ssize_t generic_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
{
if (iocb->ki_flags & IOCB_DIRECT)
return mapping->a_ops->direct_IO(iocb, iter);
return filemap_read(iocb, iter, 0);
}4.2 filemap_read
查找页缓存(filemap_get_pages)→ 命中则
copy_page_to_iter() → 零 I/O。
miss → 调用 readahead 或
readpage → 向块层提交 bio。
五、直接 I/O(O_DIRECT)
跳过页缓存,直接构造 bio → 块层 → 设备。
条件:
- 用户缓冲区对齐(通常 512 字节或文件系统块大小)
- 文件偏移对齐
- 长度对齐
iomap 框架(fs/iomap/direct-io.c)统一了
ext4/XFS/btrfs 的直接 I/O 路径。
六、iomap 框架
取代了旧的 buffer_head 路径:
// include/linux/iomap.h
struct iomap_ops {
int (*iomap_begin)(struct inode *, loff_t, loff_t, unsigned, struct iomap *, struct iomap *);
int (*iomap_end)(struct inode *, loff_t, loff_t, ssize_t, unsigned, struct iomap *);
};文件系统只需实现 iomap_begin/iomap_end → iomap 框架处理页缓存、直接 I/O、DAX。
七、写路径
7.1 缓冲写
generic_file_write_iter() →
generic_perform_write() → 写到页缓存 → 标记
dirty → 后台 writeback。
7.2 fsync
vfs_fsync() →
file->f_op->fsync() → 文件系统 flush
dirty pages + journal → 返回。
7.3 writeback
内核 flusher 线程定期把 dirty page
写回:
dirty_expire_centisecs(3000 = 30 秒)dirty_writeback_centisecs(500 = 5 秒)- 内存压力触发
八、块层提交
页缓存 miss 或 writeback → 构造 struct bio →
submit_bio() → I/O 调度器 → 设备驱动。
flowchart LR
BIO[bio] --> SCHED[I/O 调度器<br/>mq-deadline / bfq / none]
SCHED --> DISPATCH[dispatch queue]
DISPATCH --> DRIVER[NVMe / SCSI<br/>设备驱动]
classDef blk fill:#a371f722,stroke:#a371f7,color:#adbac7;
class BIO,SCHED,DISPATCH,DRIVER blk
九、fsnotify 插桩
VFS 在关键操作点插入 fsnotify hook:
// 写完成后
fsnotify_modify(file);
// 打开时
fsnotify_open(file);inotify / fanotify 通过这些 hook 收到通知。
十、小结
- read/write 穿过 VFS → 文件系统 → 页缓存 → 块层 → 驱动五层
read_iter/write_iter+iov_iter是现代统一接口- 页缓存命中 → 纯内存操作;miss → bio 提交到块层
- O_DIRECT 旁路页缓存;iomap 统一了文件系统实现
- 理解全路径是定位 I/O 性能问题的基础
参考文献
mm/filemap.c(filemap_read)fs/iomap/(iomap 框架)fs/read_write.c(VFS 入口)- Bovet & Cesati, “Understanding the Linux Kernel.” ch14-16
- 存储百科-07-linux-io-stack
工具
strace -e read,write,pread64perf trace -e block:*bpftrace -e 'kprobe:vfs_read { @[kstack] = count(); }'blktrace/blkparse
上一篇:FUSE 下一篇:POSIX AIO 与 libaio
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【操作系统百科】VFS 四层抽象
Linux 的一切皆文件靠 VFS 实现——superblock、inode、dentry、file 四层抽象加 ops 表。本文讲 VFS 核心数据结构、dcache、inode cache、RCU lookup,以及文件系统如何插入 VFS。
【操作系统百科】页缓存深入(VM 视角)
页缓存是 Linux I/O 的灵魂缓冲层。本文从 VM 视角讲 address_space、radix 到 XArray 改造、folio 抽象、readahead 策略、writeback 与 dirty throttling、memcg 对页缓存的约束。
【操作系统百科】内存回收
Linux 内存回收是 VM 最复杂的子系统之一。本文讲 active/inactive LRU、kswapd 与 direct reclaim、watermark 三线、swappiness 的真实含义、MGLRU 改造、memcg 回收与 PSI。
【操作系统百科】交换
swap 还值得开吗?本文讲 swap area 基础、swap cache、zram 压缩内存、zswap 前端压缩池、swappiness 的真实含义、容器里的 swap 策略,以及为什么现代 Android 全靠 zram 不靠磁盘。