Skip to content

第九十六章:读写路径

斩灵期

涉及内核源码:

林小源站在一条长长的传送带上,传送带从远方延伸而来,穿过层层关卡,最终消失在磁盘山脉的方向。

"这是 read() 的完整路径。"一个威严的声音从传送带的起点传来。林小源抬头,看到一个身穿白袍的身影站在传送带的最前端——它的胸口绣着"VFS"三个大字。

"你就是 VFS?"林小源问。

"我是虚拟文件系统。"VFS 的声音平稳而有力,"所有文件操作都要经过我。你从用户空间调用 read(fd, buf, count),先到系统调用层 ,然后到我这里 。"

"你做什么?"

"检查权限、更新文件位置、做一些通用的簿记工作。"VFS 说,"然后我调用具体文件系统的实现——file->f_op->read_iter()。如果是 ext4,就调用 ;如果是 btrfs,就调用 。"

"所以你是一个分发器?"林小源问。

VFS 微微点头:"我是统一入口。用户程序不需要关心底层是什么文件系统——它们只需要调用 read()write(),剩下的事情交给我。"

传送带上有一个光球在移动——那是一个读取请求。光球从用户空间出发,经过系统调用层,到达 VFS,然后被分发到具体文件系统。

林小源跟着光球继续前行,来到了页缓存的平原。光球在平原上停了下来,页缓存的身影出现了。

"又来了一个读取请求。"页缓存说,"让我看看数据在不在这里。"

它在 xarray 中搜索了几秒钟,然后摇了摇头:"缓存未命中。需要从磁盘读取。"

光球被传递给块设备层,穿过 I/O 调度器,最终到达磁盘山脉。几毫秒后,光球带着数据回来了——一个 4KB 的页面。

"数据到了。"页缓存把页面存入 xarray,"现在缓存命中了。下次再读同样的数据,就不用去磁盘了。"

光球带着数据回到了 VFS,VFS 把数据复制到用户空间的缓冲区,然后返回。

"整个过程就是这样。"VFS 说,"从用户空间到磁盘,再从磁盘回用户空间。每一步都有特定的作用。"

林小源看着光球消失在远方,若有所思:"写入呢?写入的路径是什么?"

VFS 指了指另一条传送带:"写入的路径类似——从用户空间到 ,到我这里 ,到具体文件系统的 ,最后写入页缓存。但写入不会立即到磁盘——数据留在页缓存中,标记为脏页,由 writeback 线程后续写入。"

林小源站在 VFS 身边,看着传送带上川流不息的光球。

"你每天要处理多少请求?"他问。

"数以亿计。"VFS 说,"每个进程的每次文件操作都要经过我。openreadwriteclose——所有这些操作都走我这条路。"

"你不累吗?"

VFS 笑了:"我是内核的一部分,不会累。但我必须高效——任何一个操作慢了,整个系统都会受影响。所以我做了很多优化:dentry 缓存加速路径查找,inode 缓存加速元数据访问,页缓存加速数据读取。"

"缓存无处不在。"林小源感叹。

"这是内核的设计哲学。"VFS 说,"磁盘太慢了,内存快得多。只要能把磁盘访问变成内存访问,性能就能提升几十倍。页缓存、dentry 缓存、inode 缓存——都是为了这个目标。"

林小源看着传送带上的光球,忽然明白了 read() 和 write() 的本质:它们是一条长长的旅程,从用户空间出发,经过系统调用、VFS、文件系统、页缓存,最终到达磁盘。而缓存的作用,就是让这条旅程尽可能短。

c
/*
 * read() 的完整路径:
 *
 *   用户空间: read(fd, buf, count)
 *     ↓
 *   系统调用: sys_read()
 *     ↓
 *   VFS 层: vfs_read()
 *     ↓
 *   file->f_op->read_iter()
 *     ↓
 *   文件系统: ext4_file_read_iter()
 *     ↓
 *   页缓存: generic_file_read_iter()
 *     ↓
 *   磁盘 I/O: 如果页缓存未命中
 *
 * write() 的完整路径:
 *
 *   用户空间: write(fd, buf, count)
 *     ↓
 *   系统调用: sys_write()
 *     ↓
 *   VFS 层: vfs_write()
 *     ↓
 *   file->f_op->write_iter()
 *     ↓
 *   文件系统: ext4_file_write_iter()
 *     ↓
 *   页缓存: 写入页缓存
 *     ↓
 *   writeback: 后续写入磁盘
 */

printf("=== read/write — 从系统调用到磁盘 ===\n\n");

printf("read() 的完整路径:\n\n");
printf("  用户空间\n");
printf("    read(fd, buf, count)\n");
printf("      ↓\n");
printf("  系统调用层\n");
printf("    sys_read()\n");
printf("      ↓\n");
printf("  VFS 层\n");
printf("    vfs_read()\n");
printf("    检查权限、更新文件位置\n");
printf("      ↓\n");
printf("  文件操作\n");
printf("    file->f_op->read_iter()\n");
printf("      ↓\n");
printf("  文件系统层\n");
printf("    ext4_file_read_iter()\n");
printf("      ↓\n");
printf("  页缓存层\n");
printf("    generic_file_read_iter()\n");
printf("    检查页缓存\n");
printf("      ↓\n");
printf("  磁盘 I/O (如果缓存未命中)\n\n");

printf("write() 的完整路径:\n\n");
printf("  用户空间\n");
printf("    write(fd, buf, count)\n");
printf("      ↓\n");
printf("  VFS 层\n");
printf("    vfs_write()\n");
printf("      ↓\n");
printf("  文件系统层\n");
printf("    ext4_file_write_iter()\n");
printf("      ↓\n");
printf("  页缓存\n");
printf("    写入页缓存,标记为脏\n");
printf("      ↓\n");
printf("  writeback\n");
printf("    后续写入磁盘\n\n");

printf("--- 关键函数 ---\n");
printf("vfs_read/vfs_write:\n");
printf("  VFS 层的统一入口\n");
printf("  检查权限\n");
printf("  更新文件位置\n\n");
printf("f_op->read_iter/write_iter:\n");
printf("  文件系统特定的操作\n");
printf("  每个文件系统实现自己的版本\n\n");
printf("generic_file_read_iter:\n");
printf("  通用的读取实现\n");
printf("  使用页缓存\n");

道藏笔记

内核启示

read 和 write 的路径是一条长长的旅程,从用户空间出发,经过好几层才到达磁盘。

read 的路线是:用户空间调用 read(fd, buf, count),进入系统调用层 sys_read(),然后到 VFS 的 vfs_read()——VFS 在这里检查权限、更新文件位置,做些通用簿记。接着 VFS 调用具体文件系统的实现 f_op->read_iter(),ext4 就调 ext4_file_read_iter(),btrfs 就调 btrfs_file_read_iter()。最后到页缓存层,先查缓存,命中了直接返回;没命中才去磁盘读,读完存到缓存里。

write 路径类似,但有一个关键区别:数据写入页缓存后就返回了,不会立即写磁盘。数据留在缓存里标记为脏页,由 writeback 线程后续写入。所以 Linux 的写入性能看起来很好——真正的磁盘写入被延迟、批量、自适应地处理了。

缓存无处不在:dentry 缓存加速路径查找,inode 缓存加速元数据访问,页缓存加速数据读取。磁盘太慢了,只要能把磁盘访问变成内存访问,性能就能提升几十倍。


破关试炼

读写路径之试

读写路径从系统调用一路下沉前,首先经过的统一文件系统抽象层是什么?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道