第九十六章:读写路径
斩灵期涉及内核源码:
一
林小源站在一条长长的传送带上,传送带从远方延伸而来,穿过层层关卡,最终消失在磁盘山脉的方向。
"这是 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 说,"每个进程的每次文件操作都要经过我。open、read、write、close、、、——所有这些操作都走我这条路。"
"你不累吗?"
VFS 笑了:"我是内核的一部分,不会累。但我必须高效——任何一个操作慢了,整个系统都会受影响。所以我做了很多优化:dentry 缓存加速路径查找,inode 缓存加速元数据访问,页缓存加速数据读取。"
"缓存无处不在。"林小源感叹。
"这是内核的设计哲学。"VFS 说,"磁盘太慢了,内存快得多。只要能把磁盘访问变成内存访问,性能就能提升几十倍。页缓存、dentry 缓存、inode 缓存——都是为了这个目标。"
林小源看着传送带上的光球,忽然明白了 read() 和 write() 的本质:它们是一条长长的旅程,从用户空间出发,经过系统调用、VFS、文件系统、页缓存,最终到达磁盘。而缓存的作用,就是让这条旅程尽可能短。
/*
* 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");#include <stdio.h>
/*
* 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: 后续写入磁盘
*/
int main() {
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");
return 0;
}道藏笔记
内核启示
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 缓存加速元数据访问,页缓存加速数据读取。磁盘太慢了,只要能把磁盘访问变成内存访问,性能就能提升几十倍。
读写路径之试
读写路径从系统调用一路下沉前,首先经过的统一文件系统抽象层是什么?