Skip to content

第六十七章:交换之术

元婴后期

涉及内核源码:

林小源在内景中走了很久,走到了一片陌生的区域。

这里的地面和别处不同——不是坚实的土地,而是一层薄薄的冰。冰面下面隐约能看到幽暗的深处,像是有什么东西被封印在下面。他趴在冰面上往下看,看到了一些模糊的光点,在很深很深的地方缓慢移动。

"那些是什么?"

"被换出的页面。"mm_struct 说,"它们不在内存里了。它们在 swap 中。"

林小源用手敲了敲冰面。冰层很厚,很硬,敲上去发出沉闷的声响。

"swap 是什么?"

"一个遥远的地方。"mm_struct 的声音变得低沉,"磁盘上的一块区域。当内存不够用的时候,内核会把一些不活跃的页面写到那里去,腾出物理页面给更需要的进程。"

林小源继续往下看。冰面下的光点很暗,几乎看不清——不像地面上那些明亮的页面。它们像是被冻住了,失去了活力。

"那些页面还能用吗?"

"能。"mm_struct 说,"但代价很大。要取回一个被换出的页面,需要触发 page fault,分配新的物理页面,然后从磁盘上读取数据——整个过程可能要花十毫秒。"

"十毫秒听起来不长。"

mm_struct 没有说话。但林小源突然感觉到一阵眩晕——不是身体上的,而是意识上的。他感觉自己被拉进了一个极慢的时间流里。他看到一个进程在执行一条指令,然后停下来,等了"很久很久",才拿到需要的数据。在那个"很久"里,CPU 转了几百万圈,什么有用的事都没做。

"这就是访问 swap 的代价。"mm_struct 说,"在 CPU 的时间尺度上,十毫秒是永恒。"

林小源从冰面上爬起来,后退了几步。他看着脚下那片薄冰,心里升起了一股寒意。

"所以这些页面是被'流放'的。"

"你可以这么理解。"mm_struct 说,"它们曾经是内存的一部分,但现在它们在磁盘上。只有当进程再次需要它们的时候,它们才会被'召回'。"

林小源在冰面附近徘徊,忽然注意到了一个奇怪的现象。

冰面在某些地方特别薄——薄到几乎透明。他能看到下面的光点在剧烈闪烁,像是在不断地被拉上来又推下去。

"那里在发生什么?"

"Thrashing。"mm_struct 的声音里带了一丝厌恶,"颠簸。"

林小源凑近那块薄冰。他看到一个光点被从冰面下拉了出来——一个页面被换入。光点刚落地面,还没站稳,就被另一股力量推了回去——又被换出。然后又被拉上来,又被推下去。

"为什么它不停地被换来换去?"

"因为它被频繁访问。"mm_struct 说,"进程需要这个页面,所以它被换入。但内存不够,另一个页面需要被换出,于是它又被选中换出。然后进程又需要它——"

"来回拉锯。"林小源说。

"对。"mm_struct 说,"这就是 thrashing。CPU 大部分时间花在处理 page fault 上,而不是执行有用的计算。系统看起来在'运行',但实际上什么都没做。"

林小源看着那块薄冰下面的光点疯狂闪烁。他能感觉到一种焦躁——不是自己的焦躁,而是整个系统的焦躁。每个进程都在抢内存,每个进程都在等待页面换入,每个进程都在被阻塞。

"怎么解决?"

"最好的解决办法是加内存。"mm_struct 说,"但如果加不了——内核会尝试杀掉一些进程,释放内存。那就是 OOM 杀手的工作了。"

林小源打了个寒颤。他看着那些在冰面下疯狂闪烁的光点,想起了那些被 OOM 杀手杀死的进程——它们不是因为犯了错才死的,只是因为内存不够。

林小源离开了那片薄冰区域,走到一块相对坚实的地面上。

他坐下来,看着远处那片冰原。冰面下的光点依然在缓慢移动,像被冻住的萤火虫。

"mm_struct,"他说,"swap 是不是一种'好'的机制?"

mm_struct 沉默了很久。

"swap 是一种'妥协'。"它终于开口,"它不是扩展内存的手段——物理内存是固定的,swap 不能让它变多。它是一张安全网。当内存偶尔紧张的时候,swap 可以避免进程被杀死。"

"偶尔?"

"偶尔。"mm_struct 重复,"如果系统经常需要 swap——如果那些光点频繁地被拉上来又推下去——那就说明内存真的不够了。这时候 swap 不是救命稻草,而是拖慢整个系统的枷锁。"

林小源想起了 kswapd。那位灰袍守护者在地面上收割页面,而冰面下的页面是他无力触及的——它们太远了,远在磁盘上。

"kswapd 能回收 swap 里的页面吗?"

"不能。"mm_struct 说,"kswapd 只回收内存中的页面。swap 里的页面已经不在内存里了——它们是被'放逐'的。要回收它们,只能释放 swap 空间本身。"

林小源看着那片冰原。冰面很厚,很冷,很远。他知道那些光点曾经是内存的一部分,但现在它们在一个完全不同的世界里。

"所以 swap 是最后的防线。"

"对。"mm_struct 说,"最后的防线。但最后的防线不意味着它没有代价。每一次换出,都是一次 IO。每一次换入,都是一次 page fault。如果你把 swap 当作常态——"它停了一下,"你的系统会变得非常慢。"

林小源站起身来。他看着脚下的冰面,心里多了一层理解。swap 不是救命的仙丹——它是毒药和解药的混合物。用对了,能救命;用多了,会要命。


c
/*
 * swap 的工作原理:
 *
 * 1. 页面被选中回收
 * 2. 如果是匿名页,写入 swap 分区
 * 3. 更新 PTE,指向 swap 条目
 * 4. 释放物理页面
 *
 * swap 条目 (swp_entry_t):
 *   包含 swap 分区的编号和偏移
 *   存储在 PTE 中(页面不在内存时)
 *
 * swap cache:
 *   页面写入 swap 时,先存入 swap cache
 *   如果页面在写入期间被访问,可以从 swap cache 返回
 *   避免重复的磁盘 I/O
 *
 * swap 的类型:
 *   swap 分区 — 独立的磁盘分区
 *   swap 文件 — 文件系统中的文件
 */

struct swap_entry {
    int type;           /* swap 分区编号 */
    int offset;         /* 分区内的偏移 */
    int valid;
};

struct page {
    int index;
    int in_swap;
    struct swap_entry swap_loc;
    char data[32];
};

int swap_out(struct page *pg, int type, int offset) {
    printf("  [swap_out] 写入 swap: type=%d, offset=%d\n", type, offset);
    pg->in_swap = 1;
    pg->swap_loc.type = type;
    pg->swap_loc.offset = offset;
    pg->swap_loc.valid = 1;
    return 0;
}

int swap_in(struct page *pg) {
    if (pg->in_swap && pg->swap_loc.valid) {
        printf("  [swap_in] 从 swap 读取: type=%d, offset=%d\n",
               pg->swap_loc.type, pg->swap_loc.offset);
        pg->in_swap = 0;
        return 0;
    }
    return -1;
}

printf("=== swap — 内存的后援 ===\n\n");

/* 模拟一个匿名页 */
struct page anon_page = {
    .index = 0,
    .in_swap = 0,
    .swap_loc = { 0, 0, 0 },
};
sprintf(anon_page.data, "进程的匿名数据");

printf("匿名页:\n");
printf("  数据: %s\n", anon_page.data);
printf("  在 swap 中? %s\n\n", anon_page.in_swap ? "是" : "否");

/* 内存紧张,页面被换出 */
printf("--- 内存紧张,页面被换出 ---\n");
swap_out(&anon_page, 1, 1000);

printf("  PTE 更新为 swap 条目\n");
printf("  物理页面被释放\n\n");

/* 进程访问被换出的页面 */
printf("--- 进程访问被换出的页面 ---\n");
printf("  触发页 fault\n");
printf("  内核发现页面在 swap 中\n");
swap_in(&anon_page);
printf("  分配新的物理页面\n");
printf("  从 swap 读取数据\n");
printf("  更新 PTE\n\n");

printf("页面恢复:\n");
printf("  数据: %s\n", anon_page.data);
printf("  在 swap 中? %s\n", anon_page.in_swap ? "是" : "否");

printf("\n--- swap 的类型 ---\n");
printf("swap 分区:\n");
printf("  - 独立的磁盘分区\n");
printf("  - 性能最好\n");
printf("  - 大小固定\n\n");
printf("swap 文件:\n");
printf("  - 文件系统中的文件\n");
printf("  - 灵活,可以动态调整大小\n");
printf("  - 性能稍差\n");

printf("\n--- swap 的代价 ---\n");
printf("换出 (swap out): 写入磁盘,~10ms\n");
printf("换入 (swap in):  读取磁盘,~10ms\n");
printf("如果频繁换入换出 (thrashing),系统会很慢\n");

道藏笔记

内核启示

swap 是内存的"后援"机制。

swap 的工作原理:

  1. 页面被选中回收
  2. 匿名页写入 swap 分区
  3. PTE 更新为 swap 条目
  4. 物理页面被释放
  5. 访问时触发 page fault,从 swap 读取

swap cache:

  • 页面写入 swap 时,先存入 swap cache
  • 如果页面在写入期间被访问,直接从 swap cache 返回
  • 避免重复的磁盘 I/O

swap 的类型:

  • swap 分区 — 独立磁盘分区,性能最好
  • swap 文件 — 文件系统中的文件,灵活

thrashing(颠簸):

  • 频繁的 swap 换入换出
  • CPU 大部分时间花在 page fault 处理
  • 系统变得极慢

swap 是"最后的防线"——不是扩展内存,是安全网。


破关试炼

交换之术之试

内存压力过大时,匿名页可以被换出到磁盘;本章讲的这套交换机制叫什么?

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

以修仙之名,悟内核之道