第七十四章: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 是最后手段——你应该让它永远没有出场的机会。"
林小源最后看了一眼那片废墟,转身离开。废墟中的数据结构还在消散,像一个生命在安静地死去。
杀死是最后的手段——但有时候,它是唯一的选择。
/*
* 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");#include <stdio.h>
/*
* 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;
}
int main() {
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");
return 0;
}道藏笔记
内核启示
OOM Killer 是内存管理的最后防线。
OOM 的触发条件:
- 内存分配失败
- 所有回收尝试都失败
- 内存仍然不足
OOM Killer 的选择逻辑:
- 基于进程的内存使用量计算 oom_score
- 优先杀死"性价比"最高的进程
- 占用内存多、但对系统影响小的进程
保护机制:
oom_score_adj = -1000— 永不杀死oom_score_adj = 1000— 最先杀死- 系统管理员可以调整
OOM Killer 是"残酷"的——但在极端情况下,它是必要的。
OOM 之试
系统回收失败、内存仍然不足时,负责挑选牺牲进程的 OOM 选择器叫什么?