第六十七章:交换之术
元婴后期涉及内核源码:
一
林小源在内景中走了很久,走到了一片陌生的区域。
这里的地面和别处不同——不是坚实的土地,而是一层薄薄的冰。冰面下面隐约能看到幽暗的深处,像是有什么东西被封印在下面。他趴在冰面上往下看,看到了一些模糊的光点,在很深很深的地方缓慢移动。
"那些是什么?"
"被换出的页面。"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 不是救命的仙丹——它是毒药和解药的混合物。用对了,能救命;用多了,会要命。
/*
* 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");#include <stdio.h>
/*
* 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;
}
int main() {
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");
return 0;
}道藏笔记
内核启示
swap 是内存的"后援"机制。
swap 的工作原理:
- 页面被选中回收
- 匿名页写入 swap 分区
- PTE 更新为 swap 条目
- 物理页面被释放
- 访问时触发 page fault,从 swap 读取
swap cache:
- 页面写入 swap 时,先存入 swap cache
- 如果页面在写入期间被访问,直接从 swap cache 返回
- 避免重复的磁盘 I/O
swap 的类型:
- swap 分区 — 独立磁盘分区,性能最好
- swap 文件 — 文件系统中的文件,灵活
thrashing(颠簸):
- 频繁的 swap 换入换出
- CPU 大部分时间花在 page fault 处理
- 系统变得极慢
swap 是"最后的防线"——不是扩展内存,是安全网。
交换之术之试
内存压力过大时,匿名页可以被换出到磁盘;本章讲的这套交换机制叫什么?