前言
偶然在网上看到一个问题:OOM 是按照虚拟内存还是实际内存来打分?这里“实际内存”表达的意思应该是物理内存,而“打分”想表达的意思应该是 OOM killer 机制里面的 badness score。当内存吃紧时,假如开启了 OOM killer,OOM killer 会计算进程的 badness score, badness score 越高,就越优先被 OOM killer 杀死。
实验
当内存吃紧,页分配器尝试回收物理Page失败后,会调用 OOM killer,选择 badness score 最高的进程杀死,释放内存。badness score 的分数范围是0-1000, 0表示不杀死, 1000表示总是杀死,可以直接通过 cat /proc/<pid>/oom_score 查看进程的 badness score.
这个问题的本质是 OOM 机制是基于虚拟内存还是物理内存,我们可以先通过一个实验验证这个问题:
机器内存:
通过 free -h 可以查看机器的内存情况,物理内存为16GB

实验代码
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <sys/mman.h>
#include <vector>
#include <thread>
#include <sys/stat.h>
#include <fcntl.h>
#include <chrono>
int main() {
std::string fname = "oom_killer_file";
long long map_size = 4096UL * 1024 * 1024 * 4;
int file_fd = ::open(fname.c_str(), O_RDWR | O_CREAT, 0645);
posix_fallocate(file_fd, 0, map_size);
void* base_ptr = mmap(nullptr, map_size, PROT_READ | PROT_WRITE,
MAP_SHARED,
file_fd,
0);
if (base_ptr == nullptr) {
close(file_fd);
return 0;
}
std::this_thread::sleep_for (std::chrono::seconds(600));
close(file_fd);
return 0;
}这段代码新建了一个名为 oom_killer_file 的文件,先使用 posix_fallocate() 预分配16GB的大小,然后利用 mmap() 分配16GB的虚拟空间,这段代码会 sleep 600s,假如 OOM killer 是基于虚拟内存的,这段代码会被 kill. mmap() 的原理可以查看文章mmap源码分析,调用 mmap() 会为进程分配虚拟内存,当真正写入触发缺页中断时才分配物理内存页。
编译运行:
g++ -std=c++11 -O2 oom_killer.cc -o oom_killer
./oom_killer实验结果:
我们通过 htop 观察发现进程确实已经分配了16GB的虚拟内存,但物理内存只有1768 bytes,而经过600s的运行,程序并没有被 kill,所以可以断定 OOM killer 不是基于虚拟内存而应该是物理内存计算 badness score.

OOM killer源码分析
OOM killer的核心函数是 out_of_memory(),执行流程如下:
- 调用
check_panic_on_oom()检查是否允许执行内核恐慌,假如允许,需要重启系统。 - 假如打开了
/proc/sys/vm/oom_kill_allocating_task即允许 kill 掉当前正在申请分配物理内存的进程,那么杀死当前进程。 - 调用
select_bad_process,选择badness score最高的进程。 - 调用
oom_kill_process,杀死选择的进程。
我们分析 badness score 的计算函数来理解 OOM killer如何选择需要被 kill 掉的进程:
unsigned long oom_badness(struct task_struct *p, struct mem_cgroup *memcg,
const nodemask_t *nodemask, unsigned long totalpages)
{
long points;
long adj;
/* 假如该进程不能被kill, 则分数返回0. */
if (oom_unkillable_task(p, memcg, nodemask))
return 0;
p = find_lock_task_mm(p);
if (!p)
return 0;
/* 获取该进程的 oom_score_adj, 这个是用户为进程设置的 badness score
* 调整值,假如这个值为-1000或者进程被标记为不可被kill,或者进程处于
* vfork()过程,badness score返回0. */
adj = (long)p->signal->oom_score_adj;
if (adj == OOM_SCORE_ADJ_MIN ||
test_bit(MMF_OOM_SKIP, &p->mm->flags) ||
in_vfork(p)) {
task_unlock(p);
return 0;
}
/* badness score分数 = 物理内存页数 + 交换区页数 + 页表Page Table数量. */
points = get_mm_rss(p->mm) + get_mm_counter(p->mm, MM_SWAPENTS) +
mm_pgtables_bytes(p->mm) / PAGE_SIZE;
task_unlock(p);
/* 利用以下公式对 badness score 值进行调整. */
adj *= totalpages / 1000;
points += adj;
/* 返回 badness score, 假如等于0, 则返回 1. */
return points > 0 ? points : 1;
}总结:
通过分析 badness score 的计算函数,我们可以发现 OOM killer 是基于RSS即常驻的物理内存来选择进程进行kill,从而释放内存。Linux内核内存管理部分最主要的一个逻辑就是延迟分配。