今天發現進程一直被殺掉,幾經排查,最后確認是被oom-killer殺掉了。
在內核檢測到系統內存不足后,會觸發oom-killer,挑選最占用內存的進程殺掉。
Linux 分配內存策略
Linux內核根據應用程序的要求來分配內存,由於進程實際上並不會將分配的內存全部使用,所以,為了提高性能,內核采用了一種過度分配內存(over-commit-memory)的策略,來間接利用進程的空閑內存,提高內存的使用效率。一般來說,這沒問題。但如果大多數進程都耗光自己的內存,就有麻煩了。因此此時,所有應用程序的內存之和大於物理內存。所以,必須殺掉一部分進程,一般來說,是選內存占用最大的進程殺掉。
挑選原理
挑選的過程由linux/mm/oom_kill.c里的 oom_badness() 函數決定,挑選的算法很直接:是那個最占用內存的進程。
/**
* oom_badness - heuristic function to determine which candidate task to kill
* @p: task struct of which task we should calculate
* @totalpages: total present RAM allowed for page allocation
*
* The heuristic for determining which task to kill is made to be as simple and
* predictable as possible. The goal is to return the highest value for the
* task consuming the most memory to avoid subsequent oom failures.
*/
unsigned long oom_badness(struct task_struct *p, struct mem_cgroup *memcg,
const nodemask_t *nodemask, unsigned long totalpages)
{
long points;
long adj;
if (oom_unkillable_task(p, memcg, nodemask))
return 0;
p = find_lock_task_mm(p);
if (!p)
return 0;
adj = (long)p->signal->oom_score_adj;
if (adj == OOM_SCORE_ADJ_MIN) {
task_unlock(p);
return 0;
}
/*
* The baseline for the badness score is the proportion of RAM that each
* task's rss, pagetable and swap space use.
*/
points = get_mm_rss(p->mm) + p->mm->nr_ptes +
get_mm_counter(p->mm, MM_SWAPENTS);
task_unlock(p);
/*
* Root processes get 3% bonus, just like the __vm_enough_memory()
* implementation used by LSMs.
*/
if (has_capability_noaudit(p, CAP_SYS_ADMIN))
adj -= 30;
/* Normalize to oom_score_adj units */
adj *= totalpages / 1000;
points += adj;
/*
* Never return 0 for an eligible task regardless of the root bonus and
* oom_score_adj (oom_score_adj can't be OOM_SCORE_ADJ_MIN here).
*/
return points > 0 ? points : 1;
}
避免被殺掉的辦法
從上面的代碼里可以看到 oom_badness() 給每個進程打分,根據 points 的高低來決定殺哪個進程,分數越低越不會被殺掉。
這個 points 可以根據 adj 調節,root 權限的進程通常被認為很重要,不應該被輕易殺掉,所以打分的時候可以得到 3% 的優惠(adj -= 30; 分數越低越不容易被殺掉)。
我們可以在用戶空間通過操作每個進程的 oom_adj 內核參數來使得進程不容易被 OOM killer 選中殺掉。比如,如果不想 test進程被輕易殺掉的話可以找到 test運行的進程號后,調整 oom_score_adj 為 -15(注意 points 越小越不容易被殺):
# ps aux | grep test
test 2334 1.6 2.1 623800 4876 ? Ssl 09:52 0:00 /usr/sbin/test
# cat /proc/2334/oom_score_adj
0
# echo -15 > /proc/2334/oom_score_adj
當然,也可以完全關閉 OOM killer,但線上生產環境最好不要這么做。