OOM(Out Of Memory)机制为Linux内核中一种自我保护机制,当系统分配不出内存时(触发条件)会触发这个机制,由系统在已有进程中挑选一个占用内存较多,回收内存收益最大的进程杀掉来释放内存。
Linux下允许程序申请比系统可用内存更多的内存(如malloc函数),这个特性叫Overcommit。这么做是出于优化系统的考虑,因为并不是所有的程序申请了内存就立刻使用,当使用的时候说不定系统已经回收了一些内存资源了。不过当需要真正使用内存资源而系统已经没有多余的内存资源可用时,OOM机制就被触发了。
含义:用来控制当内存不足时该如何做。
查看:cat /proc/sys/vm/panic_on_oom
取值:0/1/2
值为0:内存不足时,启动 OOM killer。
值为1:内存不足时,有可能会触发 kernel panic(系统重启),也有可能启动 OOM killer。
值为2:内存不足时,表示强制触发 kernel panic,内核崩溃GG(系统重启)。
含义:用来决定触发OOM时先杀掉哪种进程
查看:cat /proc/sys/vm/oom_kill_allocating_task
取值:0或者非0
值为0:会 kill 掉得分最高的进程。
值为非0:会kill 掉当前申请内存而触发OOM的进程。
当然,一些系统进程(如init)或者被用户设置了oom_score_adj的进程等可不是说杀就杀的。
含义:用来决定触发OOM时是否进行日志dump记录
查看:cat /proc/sys/vm/oom_dump_tasks
取值:0或者非0
值为0:关闭oom日志信息dump打印
值为非0:调用dump_tasks来打印系统中所有task的内存状况
含义:都是和具体进程相关的oom参数,和进程得分计算相关
查看:cat /proc/xxx/目录下(xxx是进程ID)
取值:0、正值、负值(-1000到1000之间)
0表示接受默认值,负值表示要在实际打分值上减去一个折扣,正值表示要惩罚该task
含义:允许程序申请比系统可用内存更多的内存特性
查看:cat /proc/sys/vm/overcommit_memory取值:0、1和2三个值,默认是0:
1、取值0:启发式策略,比较多的内存申请可能会被拒绝,如当前内存2G,突然申请1T的内存(一般当系统启动selinux模块时有效,其他情况等同取值1);
2、取值1:允许分配比当前内存资源多的内存;
3、取值2:系统所能分配的内存资源不能超过swap+内存资源*系数(/proc/sys/vm/overcommit_ratio,默认50%,可调整)。如果资源已经用光,再有内存申请请求时,都会返回错误。
内核检测到系统内存不足、挑选并杀掉某个进程的过程可以参考内核源代码linux/mm/oom_kill.c,当系统内存不足的时候,out_of_memory()被触发,然后调用select_bad_process()选择进程杀,再通过oom_kill_process()杀掉进程。
OOM-killer策略就是发生OOM时,系统会选择一些进程来杀掉以释放一部分缓存资源。
Linux下每个进程都有一个OOM权重,在/proc/
最终OOM-Killer是通过/proc/
总之,OOM-Killer策略是:损失最少的工作,释放最大的内存;同时不伤及无辜的用了很大内存的进程,并且杀掉的进程数尽量少。
__alloc_pages //内存分配时调用
|-->__alloc_pages_nodemask
|--> __alloc_pages_slowpath
|--> __alloc_pages_may_oom
| --> out_of_memory //触发
void check_panic_on_oom(enum oom_constraint constraint, gfp_t gfp_mask,
int order, const nodemask_t *nodemask)
{
if (likely(!sysctl_panic_on_oom))
return;
if (sysctl_panic_on_oom != 2) {
if (constraint != CONSTRAINT_NONE)
return;killer
}
dump_header(NULL, gfp_mask, order, NULL, nodemask);
panic("Out of memory: %s panic_on_oom is enabled\n",
sysctl_panic_on_oom == 2 ? "compulsory" : "system-wide");
}
/*
* Simple selection loop. We choose the process with the highest number of
* 'points'. In case scan was aborted, oc->chosen is set to -1.
*/
static void select_bad_process(struct oom_control *oc)
{
if (is_memcg_oom(oc))
mem_cgroup_scan_tasks(oc->memcg, oom_evaluate_task, oc);
else {
struct task_struct *p;rcu_read_lock();
for_each_process(p)
if (oom_evaluate_task(p, oc))
break;
rcu_read_unlock();
}oc->chosen_points = oc->chosen_points * 1000 / oc->totalpages;
static void oom_kill_process(struct oom_control *oc, const char *message)
{
struct task_struct *victim = oc->chosen;
struct mem_cgroup *oom_group;
static DEFINE_RATELIMIT_STATE(oom_rs, DEFAULT_RATELIMIT_INTERVAL,
DEFAULT_RATELIMIT_BURST);/*
* If the task is already exiting, don't alarm the sysadmin or kill
* its children or threads, just give it access to memory reserves
* so it can die quickly
*/
task_lock(victim);
if (task_will_free_mem(victim)) {
mark_oom_victim(victim);
wake_oom_reaper(victim);
task_unlock(victim);
put_task_struct(victim);
return;
}
task_unlock(victim);if (__ratelimit(&oom_rs))
dump_header(oc, victim);/*
* Do we need to kill the entire memory cgroup?
* Or even one of the ancestor memory cgroups?
* Check this out before killing the victim task.
*/
oom_group = mem_cgroup_get_oom_group(victim, oc->memcg);__oom_kill_process(victim, message);
/*
* If necessary, kill all tasks in the selected memory cgroup.
*/
if (oom_group) {
mem_cgroup_print_oom_group(oom_group);
mem_cgroup_scan_tasks(oom_group, oom_kill_memcg_member,
(void*)message);
mem_cgroup_put(oom_group);
}
}
}