普通文件读操作
应用程序:
read(fd, user_buffer, size);
1. 进入内核态
用户程序调用 read()
↓
系统调用进入内核态
这是一次用户态到内核态的模式切换,但进程通常还是同一个进程。
2. 内核检查 Page Cache
内核检查文件对应的数据页是否已经在 Page Cache
如果命中:
Page Cache
↓ copy_to_user
用户缓冲区
不需要访问磁盘。
3. Page Cache 未命中
内核会:
为文件分配或找到 Page Cache 页面
↓
构造块设备 I/O 请求
↓
存储控制器通过 DMA 把数据写入 Page Cache 页面
典型路径:
磁盘/存储设备
↓ DMA
Page Cache
通常不是:
磁盘缓冲区
↓ CPU复制
内核缓冲区
↓ CPU复制
Page Cache
块设备层通常直接让 DMA 将数据放入内核准备好的页面。
4. 复制到用户缓冲区
数据进入 Page Cache 后:
Page Cache
↓ copy_to_user
用户缓冲区
然后 read() 返回:
内核态 → 用户态
所以普通文件读取可以总结为:
Page Cache未命中:
1. 用户态 → 内核态
2. 存储设备通过DMA写入Page Cache
3. CPU将Page Cache复制到用户缓冲区
4. 内核态 → 用户态
如果 Page Cache 命中:
1. 用户态 → 内核态
2. CPU将Page Cache复制到用户缓冲区
3. 内核态 → 用户态
普通文件写操作
应用程序:
write(fd, user_buffer, size);
典型流程如下。
1. 进入内核态
用户程序调用 write()
↓
用户态 → 内核态
2. 数据复制到 Page Cache
用户缓冲区
↓ copy_from_user
Page Cache
对应 Page Cache 页面会被标记为脏页。
通常不是同时复制两份:
用户缓冲区 → Page Cache
用户缓冲区 → 另一个内核缓冲区
Page Cache 本身就是普通文件写入的主要内核缓存。
3. write() 返回
很多情况下,数据进入 Page Cache 后,write() 就可以返回:
内核态 → 用户态
此时数据不一定已经到达磁盘。
4. 后台回写
稍后由内核回写线程,或者由 fsync() 主动触发:
Page Cache
↓ DMA
存储设备
因此普通文件写入可以总结为:
1. 用户态 → 内核态
2. CPU将用户缓冲区复制到Page Cache
3. 将Page Cache页面标记为脏
4. 内核态 → 用户态
5. 内核稍后通过DMA将脏页写入存储设备