Skip to content

第六十六章:kswapd

元婴中期

涉及内核源码:

林小源在内景中行走,忽然感到脚下的地面在微微震颤。

不是那种剧烈的地震——而是一种持续的、有节律的脉动,像心跳。他蹲下来,把手掌按在地面上。震动从掌心传上来,带着一种奇怪的"压力感"。

"你在做什么?"mm_struct 的声音从远处飘来。

"地面在抖。"

"那不是抖。"mm_struct 说,"那是 kswapd 在工作。"

林小源站起来,循着震动的方向望去。远处的地平线上,有一道模糊的身影在移动。那身影不高,但极其宽——像一道横亘天地的墙,在缓慢地、不可阻挡地向前推进。

他走近了一些,看清了那道身影。那是一个穿着灰色长袍的修行者,面容模糊,双眼却亮得刺眼。他每走一步,脚下就会有光点从地面浮起来,像萤火虫一样飘向天空,然后消散。

"那是——"

"页面。"mm_struct 说,"他在回收页面。"

灰袍修行者似乎听到了他们的对话,转过头来。他的眼睛不是正常的眼睛——瞳孔里有三个光圈,从外到内依次是金色、银色、铜色,像三道水位线。

"你就是 kswapd?"林小源问。

灰袍修行者没有回答。他只是伸出一只手,手掌朝下,悬在地面上方三寸的位置。林小源看到他手掌下方的地面变得透明,露出里面密密麻麻的页面——有的在发光,有的是暗的。

"他在检查页面的 Accessed 位。"mm_struct 低声道,"发光的是最近被访问过的,暗的是很久没用过的。他只回收那些暗的。"

kswapd 收回了手。他弯腰,从地面拔起一个暗淡的光点,轻轻一捏,光点碎成粉末,消散在空气中。那块地面变得空荡荡的——一个页面被回收了。

"他一直在做这件事?"林小源问。

"一直。"mm_struct 说,"从系统启动开始,直到系统关机。他不睡觉,不休息,不停顿。他是内核的守护者——专门负责在后台回收页面。"

kswapd 又拔起了一个光点。他的动作很轻,很稳,像在收割庄稼。

"但是——"mm_struct 的声音突然变得严肃,"他有自己的规矩。"

林小源跟着 kswapd 走了一段路,渐渐注意到了一个规律。

kswapd 并不是每时每刻都在回收。他走一段路,就停下来,仰头看看天空。天空中有什么东西在变化——三道光线,从上到下排列,像三条水平线悬在空中。

"那是什么?"

"水位线。"mm_struct 说,"内存水位。最上面那条金色的是 high,中间银色的是 low,最下面铜色的是 min。"

林小源看着那三条线。金色的线最高,几乎在天穹的顶端。银色的线在中间,铜色的线很低,快要贴到地面了。

"kswapd 看的是哪条线?"

"他看的是 low。"mm_struct 说,"当地面的空闲页面——也就是他脚下那些还亮着的光点——低于银色线的时候,他就会被唤醒,开始回收。"

林小源低头看地面。现在地面上的光点还很多,密密麻麻的,远远高于银色线的位置。kswapd 站在那里,双手垂在身侧,眼睛半闭,像是在打盹。

"他在等。"mm_struct 说,"等水位降下来。"

话音刚落,远处传来一阵嘈杂声。林小源转头望去——天边出现了一群飞行的生物,像蝗虫一样密密麻麻。它们扑向地面,每一只都叼走一个光点。

"那是——"

"进程。"mm_struct 说,"它们在分配内存。每分配一个页面,地面上就少一个光点。"

光点在飞速减少。林小源看到水位线在下降——从金色线以上,降到了金色和银色之间,继续下降。

kswapd 睁开了眼睛。他的三色瞳孔猛地亮起来。

"水位低于 low 了。"mm_struct 说,"他醒了。"

kswapd 动了。他的速度突然变得极快——不是走,是收割。双手在地面上掠过,每掠过一次,就有成片的暗淡光点被拔起、捏碎、消散。他的动作精准而高效,只回收那些没有被访问过的页面,跳过所有还在发光的。

林小源看着水位线在回升。从银色以下,慢慢回到银色,继续上升,直到接近金色线。

kswapd 停了下来。他的双手垂回身侧,眼睛重新半闭。

"回收到 high 水位,他就停了。"mm_struct 说,"然后继续等。等下一次水位低于 low。"

林小源看着那道灰色的身影。他想起了一句话——最好的守护者,不是最强大的,而是最有耐心的。

"如果水位降到铜色线以下呢?"林小源问。

mm_struct 没有立刻回答。

远处又来了一波进程,比刚才更多。它们疯狂地扑向地面,叼走光点的速度比 kswapd 回收的速度还快。水位线在暴跌——银色线以下,继续往下,逼近铜色线。

kswapd 在拼命回收,但他的速度跟不上。他的双手在地面上疯狂掠过,但光点减少的速度更快。

水位跌破了铜色线。

"现在怎么办?"林小源喊道。

"现在——"mm_struct 的声音变得冰冷,"kswapd 救不了了。"

林小源看到一个进程——一只特别大的飞行生物——突然停在了半空中。它想叼走一个光点,但地面上已经没有空闲的了。它的动作僵住了,像被定身术定住。

"那个进程怎么了?"

"它触发了直接回收。"mm_struct 说,"当水位低于 min,进程不能再等 kswapd 了。它必须自己回收页面——在自己的执行上下文中,同步地回收。"

林小源看到那只大鸟放下了嘴里的猎物,开始用爪子刨地面。它的动作笨拙而缓慢,和 kswapd 的精准收割完全不同。

"直接回收的代价是什么?"

"阻塞。"mm_struct 说,"进程在回收完成之前不能做任何事。它的整个执行流被卡住了。而且——"它停了一下,"如果回收不到足够的页面,进程可能会被 OOM 杀手杀死。"

林小源看着那只大鸟在拼命刨地。它的身体在抖,像是在承受巨大的压力。

"kswapd 是守护者。"mm_struct 说,"但守护者也有极限。当内存压力太大,连守护者都忙不过来的时候——进程只能靠自己。"

林小源沉默了。他看着 kswapd 和那只大鸟同时在回收页面,看着水位线艰难地回升。

"这就是内存管理的残酷之处。"mm_struct 说,"资源是有限的。不管你有多少守护者,当需求超过供给的时候——总有人要付出代价。"


c
/*
 * kswapd 的工作原理:
 *
 * 内存水位:
 *   high  — 内存充足
 *   low   — 内存紧张,唤醒 kswapd
 *   min   — 内存严重不足,直接回收
 *
 * kswapd 的工作流程:
 *   1. 等待内存水位低于 low
 *   2. 被唤醒
 *   3. 回收页面直到水位回到 high
 *   4. 继续等待
 *
 * 直接回收 (direct reclaim):
 *   当内存严重不足时,进程自己回收页面
 *   不等待 kswapd
 *   可能导致进程阻塞
 */

enum watermarks {
    WMARK_MIN,
    WMARK_LOW,
    WMARK_HIGH,
};

struct zone {
    const char *name;
    int free_pages;
    int watermarks[3];
};

int should_wakeup(struct zone *z) {
    return z->free_pages <= z->watermarks[WMARK_LOW];
}

int reclaim_pages(struct zone *z, int nr) {
    z->free_pages += nr;
    return nr;
}

printf("=== kswapd — 内存回收的守护者 ===\n\n");

struct zone z = {
    .name = "Normal",
    .free_pages = 50,
    .watermarks = { 20, 50, 100 },  /* min, low, high */
};

printf("内存水位:\n");
printf("  high: %d\n", z.watermarks[WMARK_HIGH]);
printf("  low:  %d\n", z.watermarks[WMARK_LOW]);
printf("  min:  %d\n", z.watermarks[WMARK_MIN]);
printf("  当前: %d\n\n", z.free_pages);

/* 模拟内存压力 */
printf("--- 内存压力增加 ---\n");
z.free_pages = 45;
printf("  当前: %d\n", z.free_pages);
printf("  低于 low 阈值? %s\n\n",
       should_wakeup(&z) ? "是 → 唤醒 kswapd" : "否");

/* kswapd 回收页面 */
if (should_wakeup(&z)) {
    printf("[kswapd] 被唤醒,开始回收页面\n");
    int reclaimed = reclaim_pages(&z, 30);
    printf("[kswapd] 回收了 %d\n", reclaimed);
    printf("[kswapd] 当前: %d\n", z.free_pages);
    printf("[kswapd] 达到 high 阈值? %s\n\n",
           z.free_pages >= z.watermarks[WMARK_HIGH] ? "是 → 继续等待" : "否 → 继续回收");
}

/* 直接回收 */
printf("--- 直接回收 ---\n");
z.free_pages = 15;
printf("  当前: %d\n", z.free_pages);
printf("  低于 min 阈值? %s\n",
       z.free_pages <= z.watermarks[WMARK_MIN] ? "是 → 直接回收" : "否");
if (z.free_pages <= z.watermarks[WMARK_MIN]) {
    printf("  [direct reclaim] 进程自己回收页面\n");
    printf("  可能导致进程阻塞\n");
}

printf("\n--- kswapd vs 直接回收 ---\n");
printf("kswapd:\n");
printf("  - 后台运行,不阻塞进程\n");
printf("  - 在内存水位低于 low 时唤醒\n");
printf("  - 回收到 high 水位后继续等待\n\n");
printf("直接回收:\n");
printf("  - 前台运行,阻塞进程\n");
printf("  - 在内存水位低于 min 时触发\n");
printf("  - 紧急情况下的回收\n");

道藏笔记

内核启示

kswapd 是内核的交换守护进程。

内存水位:

  • high — 内存充足
  • low — 内存紧张,唤醒 kswapd
  • min — 内存严重不足,直接回收

kswapd 的工作流程:

  1. 等待内存水位低于 low
  2. 被唤醒
  3. 回收页面直到水位回到 high
  4. 继续等待

直接回收(direct reclaim):

  • 当内存水位低于 min 时触发
  • 进程自己回收页面
  • 可能导致进程阻塞

kswapd 是内存的"守护者"——在后台默默回收页面,避免直接回收的阻塞。


破关试炼

kswapd 之试

空闲页面低于 low 水位时,本章中被唤醒去后台回收页面的守护者是谁?

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

以修仙之名,悟内核之道