oom killer

Linux内核为了提高内存的使用效率采用过度分配内存(over-commit memory)的办法,造成物理内存过度紧张进而触发OOM机制来杀死一些进程回收内存。

该机制会监控那些占用内存过大,尤其是瞬间很快消耗大量内存的进程,为了防止内存耗尽会把该进程杀掉。

Linux在内存分配路径上会对内存余量做检查,(1)如果检查到内存不足,则触发OOM机制。(2)OOM首先会对系统所有进程(出init和内核线程等特殊进程)进行打分,并选出最bad的进程;然后杀死该进程。(3)同时会触发内核oom_reaper进行内存收割。(4)同时内核还提供了sysfs接口系统OOM行为,以及进程OOM行为。然后借用一个示例来分析OOM时内存状态。

1. 关于OOM

内核检测到系统内存不足,在内存分配路径上触发out_of_memory(),然后调用select_bad_process()选择一个'bad'进程oom_kill_process()杀掉,判断和选择一个‘bad'进程的过程由oom_badness()决定。

Linux下每个进程都有自己的OOM权重,在/proc/<pid>/oom_adj里面,范围是-17到+15,取值越高,越容易被杀掉。

2. OOM触发路径

在内存分配路径上,当内存不足的时候会触发kswapd、或者内存规整,极端情况会触发OOM,来获取更多内存。

在内存回收失败之后,__alloc_pages_may_oom是OOM的入口,但是主要工作在out_of_memory中进行处理。

由于Linux内存都是以页为单位,所以__alloc_pages_nodemask是必经之处。

static inline struct page *

__alloc_pages_may_oom(gfp_t gfp_mask, unsigned int order,

    const struct alloc_context *ac, unsigned long *did_some_progress)

{

    struct oom_control oc = {---------------------------------------------------------OOM控制参数。

        .zonelist = ac->zonelist,

        .nodemask = ac->nodemask,

        .memcg = NULL,

        .gfp_mask = gfp_mask,

        .order = order,

    };

    struct page *page;

    *did_some_progress = 0;

    /*

    * Acquire the oom lock.  If that fails, somebody else is

    * making progress for us.

    */

    if (!mutex_trylock(&oom_lock)) {

        *did_some_progress = 1;

        schedule_timeout_uninterruptible(1);

        return NULL;

    }

    page = get_page_from_freelist(gfp_mask | __GFP_HARDWALL, order,

                    ALLOC_WMARK_HIGH|ALLOC_CPUSET, ac);-----------------------------再次使用高水位检查一次,是否需要启动OOM流程。

    if (page)

        goto out;

    if (!(gfp_mask & __GFP_NOFAIL)) {----------------------------------------------__GFP_NOFAIL是不允许内存申请失败的情况,下面都是允许失败的处理。

        /* Coredumps can quickly deplete all memory reserves */

        if (current->flags & PF_DUMPCORE)

            goto out;

        /* The OOM killer will not help higher order allocs */

        if (order > PAGE_ALLOC_COSTLY_ORDER)---------------------------------------order超过3的申请失败,不会启动OOM回收。

            goto out;

        /* The OOM killer does not needlessly kill tasks for lowmem */

        if (ac->high_zoneidx < ZONE_NORMAL)

            goto out;

        if (pm_suspended_storage())

            goto out;

        /* The OOM killer may not free memory on a specific node */

        if (gfp_mask & __GFP_THISNODE)

            goto out;

    }

    /* Exhausted what can be done so it's blamo time */

    if (out_of_memory(&oc) || WARN_ON_ONCE(gfp_mask & __GFP_NOFAIL)) {-------------经过上面各种情况,任然需要进行OOM处理。调用out_of_memory()。

        *did_some_progress = 1;

        if (gfp_mask & __GFP_NOFAIL) {

            page = get_page_from_freelist(gfp_mask, order,

                    ALLOC_NO_WATERMARKS|ALLOC_CPUSET, ac);-------------------------对于__GFP_NOFAIL的分配情况,降低分配条件从ALLOC_WMARK_HIGH|ALLOC_CPUSET降低到ALLOC_NO_WATERMARKS|ALLOC_CPUSET。

            /*

            * fallback to ignore cpuset restriction if our nodes

            * are depleted

            */

            if (!page)

                page = get_page_from_freelist(gfp_mask, order,

                    ALLOC_NO_WATERMARKS, ac);--------------------------------------如果还是分配失败,再次降低分配标准,从ALLOC_NO_WATERMARKS|ALLOC_CPUSET降低到ALLOC_NO_WATERMARKS。真的是为了成功,节操越来越低啊。

        }

    }

out:

    mutex_unlock(&oom_lock);

    return page;

}

4.3 OOM处理:对进程打分以及杀死最高评分进程

out_of_memory函数是OOM机制的核心,他可以分为两部分。一是调挑选最’bad‘的进程,二是杀死它。

bool out_of_memory(struct oom_control *oc)

{

    unsigned long freed = 0;

    enum oom_constraint constraint = CONSTRAINT_NONE;

    if (oom_killer_disabled)----------------------------------------------------在freeze_processes会将其置位,即禁止OOM;在thaw_processes会将其清零,即打开OOM。所以,如果在冻结过程,不允许OOM。

        return false;

    if (!is_memcg_oom(oc)) {

        blocking_notifier_call_chain(&oom_notify_list, 0, &freed);

        if (freed > 0)

            /* Got some memory back in the last second. */

            return true;

    }

    if (task_will_free_mem(current)) {----------------------------------------如果当前进程正因为各种原因将要退出,或者释放内存,将当前进程作为OOM候选者,然后唤醒OOM reaper去收割进而释放内存。

        mark_oom_victim(current);

        wake_oom_reaper(current);

        return true;---------------------当前进程由于自身原因将要推出,OOM则将其标注为TIF_MEMDIE状态;然后唤醒OOM Reaper去处理。不需要经过下面的打分和杀死流程。

    }

    if (oc->gfp_mask && !(oc->gfp_mask & (__GFP_FS|__GFP_NOFAIL)))-----------如果内存申请掩码包括__GFP_DS或__GFP_NOFAIL,则不进行OOM收割。

        return true;

    constraint = constrained_alloc(oc);--------------------------------------未定义CONFIG_NUMA返回CONSTRAINT_NONE。

    if (constraint != CONSTRAINT_MEMORY_POLICY)

        oc->nodemask = NULL;

    check_panic_on_oom(oc, constraint);--------------------------------------检查sysctl_panic_on_oom设置,以及是否由sysrq触发,来决定是否触发panic。

    if (!is_memcg_oom(oc) && sysctl_oom_kill_allocating_task &&--------------如果设置了sysctl_oom_kill_allocating_task,那么当内存耗尽时,会把当前申请内存分配的进程杀掉。

        current->mm && !oom_unkillable_task(current, NULL, oc->nodemask) &&

        current->signal->oom_score_adj != OOM_SCORE_ADJ_MIN) {

        get_task_struct(current);

        oc->chosen = current;

        oom_kill_process(oc, "Out of memory (oom_kill_allocating_task)");

        return true;

    }

    select_bad_process(oc);-------------------------------------------------遍历所有进程,进程下的线程,查找合适的候选进程。即得分最高的候选进程。

    /* Found nothing?!?! Either we hang forever, or we panic. */

    if (!oc->chosen && !is_sysrq_oom(oc) && !is_memcg_oom(oc)) {------------如果没有合适候选进程,并且OOM不是由sysrq触发的,进入panic。

        dump_header(oc, NULL);

        panic("Out of memory and no killable processes...\n");

    }

    if (oc->chosen && oc->chosen != (void *)-1UL) {

        oom_kill_process(oc, !is_memcg_oom(oc) ? "Out of memory" :

                "Memory cgroup out of memory");----------------------------杀死选中的进程。

        schedule_timeout_killable(1);

    }

    return !!oc->chosen;

}


select_bad_process()通过oom_evaluate_task()来评估每个进程的得分,对于进程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 int oom_evaluate_task(struct task_struct *task, void *arg)

{

    struct oom_control *oc = arg;

    unsigned long points;

    if (oom_unkillable_task(task, NULL, oc->nodemask))-------------------进程1以及内核线程等等不能被kill的线程跳过。

        goto next;

    if (!is_sysrq_oom(oc) && tsk_is_oom_victim(task)) {

        if (test_bit(MMF_OOM_SKIP, &task->signal->oom_mm->flags))

            goto next;

        goto abort;

    }

    if (oom_task_origin(task)) {

        points = ULONG_MAX;

        goto select;

    }

    points = oom_badness(task, NULL, oc->nodemask, oc->totalpages);------对进程task进行打分。

    if (!points || points < oc->chosen_points)---------------------------这里保证只取最高分的进程,所以分数最高者被选中。其他情况则直接跳过。

        goto next;

    /* Prefer thread group leaders for display purposes */

    if (points == oc->chosen_points && thread_group_leader(oc->chosen))

        goto next;

select:

    if (oc->chosen)

        put_task_struct(oc->chosen);

    get_task_struct(task);

    oc->chosen = task;--------------------------------------------------更新OOM选中的进程和当前最高分。

    oc->chosen_points = points;

next:

    return 0;

abort:

    if (oc->chosen)

        put_task_struct(oc->chosen);

    oc->chosen = (void *)-1UL;

    return 1;

}

在oom_badness()中计算当前进程的得分,返回选中进程的结构体,以及进程得分ppoints。

oom_badness()是给进程打分的函数,可以说是核心中的核心。最终结果受oom_score_adj和当前进程内存使用量综合影响。

oom_score_adj为OOM_SCORE_ADJ_MIN的进程不参加评选。进程的oom_score_adj值在/proc/xxx/oom_score_adj中。

mm->flags为MMF_OOM_SKIP的进程不参加评选。

处于vfork()中的进程不参加评选。

进程的得分取决于其消耗的RSS部分内存(文件映射内存MM_FILEPAGES、匿名映射内存MM_ANONPAGES、shmem内存MM_SHMEMPAGES)、匿名交换内存MM_SWAPENTS、PTE页表所占内存、PMD页表所占内存。

具有root权限的进程只取其97%的得分参加评选。

所以进程得分points=process_pages + oom_score_adj*totalpages/1000;如果是root权限的进程points=process_pages*0.97 + oom_score_adj*totalpages/1000。

在oom_score_adj都为0(默认值)的情况下,最终得分跟进程自身消耗的内存有关;消耗的内存越大越容易被Kill。

oom_score_adj每降低1,可以多获得系统内存资源的1/1000使用量。反之,每增加1,则少获得系统内存资源1/1000使用量。

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);------------找到进程p,并使用task_lock()锁上。

    if (!p)

        return 0;

    /*

    * Do not even consider tasks which are explicitly marked oom

    * unkillable or have been already oom reaped or the are in

    * the middle of vfork

    */

    adj = (long)p->signal->oom_score_adj;--------------------------------------获取当前进程的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;--------------------------------------------------------------如果当前进程oom_score_adj为OOM_SCORE_ADJ_MIN的话,就返回0.等于告诉OOM,此进程不参数'bad'评比。

    }

    /*

    * 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) + get_mm_counter(p->mm, MM_SWAPENTS) +

        atomic_long_read(&p->mm->nr_ptes) + mm_nr_pmds(p->mm);-----------------可以看出points综合了内存占用情况,包括RSS部分、swap file或者swap device占用内存、以及页表占用内存。

    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))------------------------------如果是root用户,增加3%的使用特权。

        points -= (points * 3) / 100;

    /* Normalize to oom_score_adj units */

    adj *= totalpages / 1000;--------------------------------------------------这里可以看出oom_score_adj对最终分数的影响,如果oom_score_adj小于0,则最终points就会变小,进程更加不会被选中。

    points += adj;-------------------------------------------------------------将归一化后的adj和points求和,作为当前进程的分数。

    /*

    * 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;

}


https://www.cnblogs.com/arnoldlu/p/8567559.html#oom_badness

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容