普通文件读操作

应用程序:

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将脏页写入存储设备