第九十五章:写回之道
斩灵期涉及内核源码:
一
林小源站在页缓存的平原边缘,看到远处有一队身影在忙碌地搬运——它们从平原上取下发光的页面,扛着向远方的磁盘山脉走去。
"那些是写回线程。"页缓存的声音从身后传来,"它们负责把脏页写入磁盘。"
一个身穿灰色工装的身影停下脚步,擦了擦额头上的汗。它的胸口绣着"bdi_writeback"的字样。
"你就是写回线程?"林小源问。
"对。"bdi_writeback 的声音沙哑而疲惫,"每个 BDI——Backing Device Info——都有一个写回线程。我的工作是定期检查脏页,达到阈值就写入磁盘。"
"为什么要定期写入,而不是每次写入都立即写磁盘?"
bdi_writeback 放下肩上的页面,叹了口气:"你想想,如果每次 write() 都立即写磁盘,磁盘 I/O 会把系统拖垮。磁盘是整个系统最慢的部件——比内存慢几十倍。所以内核的策略是:先写入页缓存,标记为脏页,积累到一定数量后批量写回。"
"批量操作比逐个操作高效。"林小源点头。
"没错。"bdi_writeback 说,"而且写回的时机有讲究——脏页比例超过 (默认 10%)时,后台开始写回;超过 (默认 20%)时,强制写回,所有新的写入都会被阻塞。"
二
林小源跟着 bdi_writeback 走在通往磁盘山脉的路上。他注意到,有些进程在路边焦急地等待。
"那些是什么进程?"他问。
"它们调用了 。"bdi_writeback 说,"有些应用——比如数据库——需要确保数据真正写入磁盘才算完成。它们调用 ,强制把文件的所有脏页写入磁盘。在写入完成之前, 会阻塞。"
"那 呢?"
"只写数据,不写元数据。"bdi_writeback 说,"比 快一点,因为元数据的写入可以省略。还有 ,把整个系统的所有脏页都写入磁盘——那是最慢的操作。"
林小源看到一个进程调用了 ,bdi_writeback 立刻放下手中的活,优先处理那个进程的文件。几秒钟后,进程满意地离开了。
"你对 有优先级?"林小源问。
"当然。"bdi_writeback 说,"显式请求写回的进程,说明数据对它很重要。我必须优先处理。"
三
林小源注意到 bdi_writeback 的步伐有时快有时慢。
"你在调整速度?"他问。
"是的。"bdi_writeback 说,"写回的频率需要平衡——写回太频繁,磁盘 I/O 太重,系统变慢;写回太慢,脏页积累太多,占用内存,而且断电风险增大。"
"怎么平衡?"
"内核有动态调整机制。"bdi_writeback 说,"根据磁盘的写入速度和脏页的增长速度,动态调整写回频率。如果磁盘写入快,可以多积累一些脏页再写回;如果磁盘写入慢,就更频繁地写回,避免脏页堆积。"
"所以写回是一个自适应的过程。"
"对。"bdi_writeback 看了看远方的磁盘山脉,"我每 5 秒运行一次——这是 的默认值。但实际的写回频率会根据系统状态动态调整。"
林小源看着 bdi_writeback 继续扛着脏页走向磁盘,忽然理解了为什么 Linux 的写入性能看起来那么好——大部分写入操作只是写入内存中的页缓存,真正的磁盘写入被延迟、批量、自适应地处理了。
"延迟是一种艺术。"林小源低声说。
bdi_writeback 回头看了他一眼,微微点头。
/*
* 写回的触发条件:
*
* 1. 定时写回
* dirty_writeback_centisecs(默认 5 秒)
* bdi_writeback 线程定期运行
*
* 2. 脏页比例
* dirty_ratio(默认 20%)
* 脏页占总内存的比例
* 超过时强制写回
*
* 3. 脏页数量
* dirty_background_ratio(默认 10%)
* 后台开始写回的阈值
*
* 4. fsync()
* 进程显式要求写回
* 确保数据写入磁盘
*
* 写回线程:
* bdi_writeback — 每个 BDI 一个
* flusher threads — 批量写回
*/
printf("=== writeback — 脏页写入磁盘 ===\n\n");
printf("写回的触发条件:\n\n");
printf("1. 定时写回:\n");
printf(" dirty_writeback_centisecs = 500 (5 秒)\n");
printf(" bdi_writeback 线程定期运行\n\n");
printf("2. 脏页比例:\n");
printf(" dirty_ratio = 20%%\n");
printf(" 脏页超过总内存 20%% 时强制写回\n\n");
printf("3. 后台写回:\n");
printf(" dirty_background_ratio = 10%%\n");
printf(" 脏页超过 10%% 时后台开始写回\n\n");
printf("4. 显式写回:\n");
printf(" fsync(fd): 把文件数据写入磁盘\n");
printf(" fdatasync(fd): 只写数据\n");
printf(" sync(): 把所有脏页写入磁盘\n\n");
printf("--- 写回的过程 ---\n");
printf("1. 进程 write() 写入页缓存\n");
printf(" 页面标记为"脏"\n\n");
printf("2. 后台线程检查脏页\n");
printf(" 达到阈值时开始写回\n\n");
printf("3. 写回脏页\n");
printf(" 把脏页写入磁盘\n");
printf(" 清除"脏"标记\n\n");
printf("--- 查看脏页 ---\n");
printf("/proc/meminfo:\n");
printf(" Dirty: 1234 kB\n");
printf(" Writeback: 0 kB\n\n");
printf("vmstat 1:\n");
printf(" wa: I/O 等待时间\n\n");
printf("--- 调优参数 ---\n");
printf("/proc/sys/vm/dirty_ratio\n");
printf("/proc/sys/vm/dirty_background_ratio\n");
printf("/proc/sys/vm/dirty_writeback_centisecs\n");
printf("/proc/sys/vm/dirty_expire_centisecs\n");#include <stdio.h>
/*
* 写回的触发条件:
*
* 1. 定时写回
* dirty_writeback_centisecs(默认 5 秒)
* bdi_writeback 线程定期运行
*
* 2. 脏页比例
* dirty_ratio(默认 20%)
* 脏页占总内存的比例
* 超过时强制写回
*
* 3. 脏页数量
* dirty_background_ratio(默认 10%)
* 后台开始写回的阈值
*
* 4. fsync()
* 进程显式要求写回
* 确保数据写入磁盘
*
* 写回线程:
* bdi_writeback — 每个 BDI 一个
* flusher threads — 批量写回
*/
int main() {
printf("=== writeback — 脏页写入磁盘 ===\n\n");
printf("写回的触发条件:\n\n");
printf("1. 定时写回:\n");
printf(" dirty_writeback_centisecs = 500 (5 秒)\n");
printf(" bdi_writeback 线程定期运行\n\n");
printf("2. 脏页比例:\n");
printf(" dirty_ratio = 20%%\n");
printf(" 脏页超过总内存 20%% 时强制写回\n\n");
printf("3. 后台写回:\n");
printf(" dirty_background_ratio = 10%%\n");
printf(" 脏页超过 10%% 时后台开始写回\n\n");
printf("4. 显式写回:\n");
printf(" fsync(fd): 把文件数据写入磁盘\n");
printf(" fdatasync(fd): 只写数据\n");
printf(" sync(): 把所有脏页写入磁盘\n\n");
printf("--- 写回的过程 ---\n");
printf("1. 进程 write() 写入页缓存\n");
printf(" 页面标记为"脏"\n\n");
printf("2. 后台线程检查脏页\n");
printf(" 达到阈值时开始写回\n\n");
printf("3. 写回脏页\n");
printf(" 把脏页写入磁盘\n");
printf(" 清除"脏"标记\n\n");
printf("--- 查看脏页 ---\n");
printf("/proc/meminfo:\n");
printf(" Dirty: 1234 kB\n");
printf(" Writeback: 0 kB\n\n");
printf("vmstat 1:\n");
printf(" wa: I/O 等待时间\n\n");
printf("--- 调优参数 ---\n");
printf("/proc/sys/vm/dirty_ratio\n");
printf("/proc/sys/vm/dirty_background_ratio\n");
printf("/proc/sys/vm/dirty_writeback_centisecs\n");
printf("/proc/sys/vm/dirty_expire_centisecs\n");
return 0;
}道藏笔记
内核启示
写回是把脏页写入磁盘的机制。为什么不每次 write 都立即写磁盘?因为磁盘是整个系统最慢的部件,每次写入都立即落盘会把系统拖垮。内核的策略是:先写页缓存,标记为脏,积累到一定数量后批量写回。
触发条件有好几种。每 5 秒一次定时写回(dirty_writeback_centisecs 的默认值)。脏页占总内存超过 dirty_background_ratio(默认 10%)时后台开始写回。超过 dirty_ratio(默认 20%)时强制写回,新的写入会被阻塞。还有显式的 fsync()——数据库这类应用需要确保数据真正写入磁盘,会主动调用。
写回线程还会根据磁盘写入速度动态调整频率——磁盘快就多积累一些再写,磁盘慢就更频繁地写,避免脏页堆积。这是一个自适应的过程。
显式写回有三个选择:fsync() 写回文件数据和元数据,fdatasync() 只写数据不写元数据(快一点),sync() 最猛,把系统所有脏页都写入磁盘。
写回之试
本章讲后台写回频率时,对应可调的内核参数名是什么?