Skip to content

第七十四章:OOM

元婴后期

涉及内核源码:

内景中的天空变红了。

不是夕阳的红——是警告的红。像火,像血,像某种即将爆发的灾难。林小源抬头看着那片猩红的天穹,心跳加速。

"怎么了?"

"内存水位线跌破了 min 。" mm_struct 的声音前所未有地紧绷,"kswapd 已经跑了一百二十圈,直接回收也试过了。没用。内存还是不够。"

林小源环顾四周。他脚下的 VMA 大陆在颤抖——不是地震,而是某种更深层的崩溃。远处有几块大陆正在缩小,边缘在碎裂,像冰川在融化。

"那些是什么?"

"被回收的页面。" mm_struct 说,"匿名页被换出到 swap,文件页被丢弃。但还是不够——总有进程在不停地申请内存,而物理内存已经见底了。"

天空中传来一声低沉的轰鸣。林小源看到一个巨大的影子在红云中盘旋——那是一个他从未见过的结构体。它没有固定的形状,像一团黑色的雾气,雾气中隐约闪烁着冷酷的光芒。

"那是什么?"林小源的声音不自觉地压低了。

" 。" mm_struct 说,"OOM Killer 的选择器。"

林小源的血凉了半截。"OOM Killer……"

"内存管理的最后防线。" mm_struct 的声音变得异常平静,"当所有回收尝试都失败,内存仍然不足时——内核必须杀死一个进程来释放内存。没有别的办法。"

黑色的雾气在天空中盘旋得更低了。林小源能看到雾气中有一些光点在闪烁——每个光点都代表一个进程。雾气在逐一扫描它们,像一个死神在挑选猎物。

"它在选什么?"

" 。" mm_struct 说,"基于进程的内存使用量计算的评分。占用内存越多,评分越高——越可能被杀死。"

林小源看到一个光点突然变亮了——那是一个占用大量内存的进程。雾气在它周围聚拢,像蛇一样缠绕上去。

"等等——"林小源下意识地伸出手。

"没用的。" mm_struct 的声音里没有感情,"OOM Killer 不接受抗议。它必须做出选择——杀死一个进程,拯救整个系统。"

一声尖锐的啸叫。那个光点碎裂了,化作无数碎片散落。碎片落回地面,变成了一块块自由的物理页——系统得救了。

林小源看着那些碎片,喉咙发紧。

天空的红色慢慢消退,但林小源的心情没有平复。

他坐在一块岩石上,看着远处那团黑色的雾气慢慢散去。雾气散尽后,他看到了一些残留的痕迹——被杀死进程的 VMA 大陆正在崩塌,页表在瓦解,数据在消散。

"为什么是它?"他问。

"因为它最'贵'。"一个低沉的声音从旁边传来。

林小源转头,看到一个浑身漆黑的结构体蹲在不远处。它没有面孔,但林小源能感觉到它在"看"着自己。

" 。"它自我介绍,声音像金属摩擦,"我执行选择。 做决定,我动手。"

"为什么选那个进程?"林小源追问。

"评分。" oom_kill_process 说,"每个进程都有一个 ,范围 0 到 1000。评分基于进程的 RSS——驻留内存大小。占用内存越多,评分越高。"

"就这样?"

"就这样。" oom_kill_process 说,"OOM Killer 没有时间做复杂的分析。系统快崩溃了——它必须在最短的时间内做出决定。占用内存最多的进程,就是牺牲品。"

林小源想起了刚才那个碎裂的光点。那是一个数据库进程——它需要大量内存来缓存数据和索引。对它自己来说,那些内存是必要的;但对系统来说,它是最危险的。

"大在 OOM 面前是弱点。"他喃喃道。

"不。" oom_kill_process 纠正他,"大是责任。你占用的内存越多,你对系统的影响就越大。OOM Killer 不关心你的内存用来做什么——它只关心你能释放多少。"

林小源站起来,走向那片崩塌的 VMA 大陆废墟。

废墟中有一些残留的数据结构——进程的 mm_struct、VMA 链表、页表。它们正在慢慢消散,像晨雾一样。

他蹲下来,捡起一个碎片。碎片上刻着一行字:oom_score_adj = -500

"这是什么?"

"用户调整。" mm_struct 的声音从远处传来,"系统管理员可以通过 来影响 OOM Killer 的选择。范围从 -1000 到 1000。设为 -1000——永不杀死。设为 1000——最先杀死。"

林小源握着那个碎片,若有所思。"如果我把所有关键进程都设成 -1000……"

"那其他进程就更危险了。" mm_struct 说,"保护一个进程,意味着把风险转嫁给其他进程。OOM Killer 总要杀一个——你保护的越多,剩下的选择就越少。"

"这是零和游戏。"

"这就是资源有限的世界。" mm_struct 说,"内存不是无限的。当内存耗尽时,总要有人付出代价。OOM Killer 只是让这个代价变得可预测——而不是让整个系统崩溃。"

林小源站起来,看着远处的天空。红色已经完全消退了,天空恢复了正常的颜色。但他知道——下一次内存耗尽,天空还会变红。

"有征兆吗?"他问,"在 OOM 触发之前?"

"有。" mm_struct 说,"kswapd 频繁运行、直接回收变多、swap 使用量飙升——这些都是征兆。如果你看到这些,说明系统已经在悬崖边上了。"

"怎么避免?"

"监控。限制。规划。" mm_struct 说,"监控内存使用,限制进程的内存配额,规划好系统的内存容量。OOM Killer 是最后手段——你应该让它永远没有出场的机会。"

林小源最后看了一眼那片废墟,转身离开。废墟中的数据结构还在消散,像一个生命在安静地死去。

杀死是最后的手段——但有时候,它是唯一的选择。


c
/*
 * OOM Killer 的工作流程:
 *
 * 1. 内存分配失败
 *    __alloc_pages() 无法分配页面
 *
 * 2. 尝试回收
 *    kswapd 回收页面
 *    直接回收 (direct reclaim)
 *
 * 3. 回收失败
 *    内存仍然不足
 *
 * 4. 触发 OOM Killer
 *    select_bad_process() 选择目标
 *    oom_kill_process() 杀死目标
 *
 * OOM 评分 (oom_score):
 *   - 基于进程的内存使用量
 *   - 范围: 0-1000
 *   - 越高越可能被杀死
 *
 * oom_score_adj:
 *   - 用户可调整的偏移量
 *   - 范围: -1000 到 1000
 *   - -1000 表示永不杀死
 */

struct task_struct {
    char name[16];
    unsigned long rss;       /* 驻留内存 (KB) */
    int oom_score;           /* OOM 评分 */
    int oom_score_adj;       /* 用户调整 */
};

int calculate_oom_score(struct task_struct *task) {
    /* 简化的 OOM 评分计算 */
    int score = task->rss / 1024;  /* 基于内存使用 */
    if (score > 1000) score = 1000;
    return score + task->oom_score_adj;
}

printf("=== OOM — 内存耗尽时的选择 ===\n\n");

/* 模拟几个进程 */
struct task_struct tasks[] = {
    {"bash",      1024,   0,    0},
    {"nginx",     8192,   0,    0},
    {"mysql",    65536,   0,    0},
    {"java",    131072,   0, -500},
    {"sshd",      2048,   0,    0},
};
int nr_tasks = sizeof(tasks) / sizeof(tasks[0]);

printf("系统内存状态: 几乎耗尽\n\n");

printf("进程列表:\n");
printf("%-10s %-12s %-10s %-10s %-10s\n",
       "进程", "RSS (KB)", "基础分", "调整", "最终分");
printf("%-10s %-12s %-10s %-10s %-10s\n",
       "---", "---", "---", "---", "---");

int target = -1;
int max_score = 0;

for (int i = 0; i < nr_tasks; i++) {
    tasks[i].oom_score = calculate_oom_score(&tasks[i]);
    int final_score = tasks[i].oom_score;

    printf("%-10s %-12ld %-10d %-10d %-10d\n",
           tasks[i].name, tasks[i].rss,
           tasks[i].oom_score, tasks[i].oom_score_adj,
           final_score);

    if (final_score > max_score) {
        max_score = final_score;
        target = i;
    }
}

printf("\n--- OOM Killer 选择 ---\n");
printf("选择目标: %s (评分: %d)\n",
       tasks[target].name, max_score);
printf("原因: 评分最高,占用内存最多\n\n");

printf("--- 保护机制 ---\n");
printf("oom_score_adj = -1000: 永不杀死\n");
printf("oom_score_adj = 0: 默认\n");
printf("oom_score_adj = 1000: 最先杀死\n\n");

printf("--- 内存耗尽的征兆 ---\n");
printf("1. 内存水位线低于 min\n");
printf("2. kswapd 频繁运行\n");
printf("3. 直接回收频繁\n");
printf("4. 大量 swap 使用\n");

道藏笔记

内核启示

OOM Killer 是内存管理的最后防线。

OOM 的触发条件:

  • 内存分配失败
  • 所有回收尝试都失败
  • 内存仍然不足

OOM Killer 的选择逻辑:

  • 基于进程的内存使用量计算 oom_score
  • 优先杀死"性价比"最高的进程
  • 占用内存多、但对系统影响小的进程

保护机制:

  • oom_score_adj = -1000 — 永不杀死
  • oom_score_adj = 1000 — 最先杀死
  • 系统管理员可以调整

OOM Killer 是"残酷"的——但在极端情况下,它是必要的。


破关试炼

OOM 之试

系统回收失败、内存仍然不足时,负责挑选牺牲进程的 OOM 选择器叫什么?

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

以修仙之名,悟内核之道