zoukankan      html  css  js  c++  java
  • Linux内存管理 (21)OOM【转】 sky

    转自:https://www.cnblogs.com/arnoldlu/p/8567559.html

    专题:Linux内存管理专题

    关键词:OOM、oom_adj、oom_score、badness。

    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,取值越高,越容易被杀掉。

    下面从几个方便来分析OOM:

    2. OOM触发路径

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

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

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

    复制代码
    alloc_pages
      ->_alloc_pages
        ->__alloc_pages_nodemask
          ->__alloc_pages_slowpath-------------------------此时已经说明内存不够,会触发一些内存回收、内存规整机制,极端情况触发OOM。
            ->__alloc_pages_may_oom -----------------------进入OOM的开始,包括一些检查动作。
    ->out_of_memory------------------------------OOM的核心
    ->select_bad_process-----------------------选择最'bad'进程
    ->oom_scan_process_thread ->oom_badness----------------------------计算当前进程有多'badness'
    ->oom_kill_process-------------------------杀死选中的进程
    复制代码

    还有一种情况是do_page_fault(),如果产生VM_FAULT_OOM错误,就进入pagefault_out_of_memory()。

    复制代码
    asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long write,
                                  unsigned long mmu_meh)
    {
    ...
    good_area:
    ...
            fault = handle_mm_fault(vma, address, write ? FAULT_FLAG_WRITE : 0);
            if (unlikely(fault & VM_FAULT_ERROR)) {
                    if (fault & VM_FAULT_OOM)-------------------------------------------handle_mm_fault()时产生VM_FAULT_OOM错误,进入out_of_memory处理。
                            goto out_of_memory;
                    else if (fault & VM_FAULT_SIGBUS)
                            goto do_sigbus;
                    else if (fault & VM_FAULT_SIGSEGV)
                            goto bad_area;
                    BUG();
            }
            if (fault & VM_FAULT_MAJOR)
                    tsk->maj_flt++;
            else
                    tsk->min_flt++;
    
            up_read(&mm->mmap_sem);
            return;
    ...
    out_of_memory:
            pagefault_out_of_memory();
            return;
    ...
    }
    
    
    void pagefault_out_of_memory(void)
    {
        struct oom_control oc = {
            .zonelist = NULL,
            .nodemask = NULL,
            .memcg = NULL,
            .gfp_mask = 0,
            .order = 0,------------------------------------------------------------------单个页面情况。
        };
    ...
        out_of_memory(&oc);
    }
    复制代码

    3. 影响OOM的内核参数

    参照Linux内存管理 (23)内存sysfs节点和工具的OOM章节。 

    4. OOM代码分析

    4.1 OOM数据结构

    OOM的核心数据结构是struct oom_control,在include/linux/oom.h中。

    复制代码
    struct oom_control {
        /* Used to determine cpuset */
        struct zonelist *zonelist;
    
        /* Used to determine mempolicy */
        nodemask_t *nodemask;
    
        /* Memory cgroup in which oom is invoked, or NULL for global oom */
        struct mem_cgroup *memcg;
    
        /* Used to determine cpuset and node locality requirement */
        const gfp_t gfp_mask;----------------------------------发生异常时页面分配掩码。
    
        /*
         * order == -1 means the oom kill is required by sysrq, otherwise only
         * for display purposes.
         */
        const int order;---------------------------------------发生异常时申请页面order大小。
    
        /* Used by oom implementation, do not set */
        unsigned long totalpages;
        struct task_struct *chosen;----------------------------OOM选中的当前进程结构。
        unsigned long chosen_points;---------------------------OOM对进程评分的最高分。
    };
    复制代码

    4.2 OOM触发路径

    __alloc_pages_may_oom是内存分配路径上的OOM入口,在进入OOM之前还会检查一些特殊情况。

    复制代码
    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;
    }
    复制代码

    oom_kill_process()用于杀死最高分的进程,包括进程下的线程。

    复制代码
    static void oom_kill_process(struct oom_control *oc, const char *message)
    {
        struct task_struct *p = oc->chosen;
        unsigned int points = oc->chosen_points;
        struct task_struct *victim = p;
        struct task_struct *child;
        struct task_struct *t;
        struct mm_struct *mm;
        unsigned int victim_points = 0;
        static DEFINE_RATELIMIT_STATE(oom_rs, DEFAULT_RATELIMIT_INTERVAL,
                              DEFAULT_RATELIMIT_BURST);
        bool can_oom_reap = true;
    
        task_lock(p);
        if (task_will_free_mem(p)) {------------------------------------------对于非coredump正处于退出状态的线程,标注TIF_MEMDIE并唤醒reaper线程进行收割,然后退出。
            mark_oom_victim(p);
            wake_oom_reaper(p);
            task_unlock(p);
            put_task_struct(p);
            return;
        }
        task_unlock(p);
    
        if (__ratelimit(&oom_rs))
            dump_header(oc, p);----------------------------------------------在kill进程之前,将系统栈信息、内存信息、所有进程的内存消耗情况打印。
    
    5.    pr_err("%s: Kill process %d (%s) score %u or sacrifice child\n",
            message, task_pid_nr(p), p->comm, points);-----------------------输出将要kill掉的进程名、pid、score。
    
        read_lock(&tasklist_lock);
        for_each_thread(p, t) {-----------------------------------------------遍历进程下的线程
            list_for_each_entry(child, &t->children, sibling) {
                unsigned int child_points;
    
                if (process_shares_mm(child, p->mm))--------如果子进程有自己单独的mm内存空间,则可以被选中代提父进程被kill。
                    continue;
                /*
                 * oom_badness() returns 0 if the thread is unkillable
                 */
                child_points = oom_badness(child,
                    oc->memcg, oc->nodemask, oc->totalpages);-----------------计算子线程的得分情况
                if (child_points > victim_points) {---------------------------将得分最高者计为victim,得分为victim_points。
                    put_task_struct(victim);
                    victim = child;-------------------------------------------确保victim是p子进程中得分最高者,代提p受死。
                    victim_points = child_points;
                    get_task_struct(victim);
                }
            }
        }
        read_unlock(&tasklist_lock);
    
        p = find_lock_task_mm(victim);
        if (!p) {
            put_task_struct(victim);
            return;
        } else if (victim != p) {
            get_task_struct(p);
            put_task_struct(victim);
            victim = p;
        }
    
        /* Get a reference to safely compare mm after task_unlock(victim) */
        mm = victim->mm;
        atomic_inc(&mm->mm_count);
    
        do_send_sig_info(SIGKILL, SEND_SIG_FORCED, victim, true);--------------发送SIGKILL信号给victim进程。
        mark_oom_victim(victim);-----------------------------------------------标注TIF_MEMDIE是因为OOM被杀死。
    6.  pr_err("Killed process %d (%s) total-vm:%lukB, anon-rss:%lukB, file-rss:%lukB, shmem-rss:%lukB\n",
            task_pid_nr(victim), victim->comm, K(victim->mm->total_vm),
            K(get_mm_counter(victim->mm, MM_ANONPAGES)),
            K(get_mm_counter(victim->mm, MM_FILEPAGES)),
            K(get_mm_counter(victim->mm, MM_SHMEMPAGES)));---------------------被kill进程的内存信息。
        task_unlock(victim);
    
        rcu_read_lock();
        for_each_process(p) {--------------------------------------------------继续处理共享内存的相关线程
            if (!process_shares_mm(p, mm))
                continue;
            if (same_thread_group(p, victim))
                continue;
            if (is_global_init(p)) {
                can_oom_reap = false;
                set_bit(MMF_OOM_SKIP, &mm->flags);
                pr_info("oom killer %d (%s) has mm pinned by %d (%s)\n",
                        task_pid_nr(victim), victim->comm,
                        task_pid_nr(p), p->comm);
                continue;
            }
    
            if (unlikely(p->flags & PF_KTHREAD))-----------------------------内核线程跳过。
                continue;
            do_send_sig_info(SIGKILL, SEND_SIG_FORCED, p, true);
        }
        rcu_read_unlock();
    
        if (can_oom_reap)
            wake_oom_reaper(victim);----------------------------------------唤醒OOM Reaper内核线程收割。
    
        mmdrop(mm);---------------------------------------------------------释放mm空间的内存。包括申请的页面、mm结构体等。
        put_task_struct(victim);--------------------------------------------释放task_struct占用的内存空间,包括cgroup等等。
    }
    复制代码

     dump_header()有助于发现OOM现场,找出OOM原因。

    复制代码
    static void dump_header(struct oom_control *oc, struct task_struct *p)
    {
        nodemask_t *nm = (oc->nodemask) ? oc->nodemask : &cpuset_current_mems_allowed;
    
    1. pr_warn("%s invoked oom-killer: gfp_mask=%#x(%pGg), nodemask=%*pbl, order=%d, oom_score_adj=%hd\n",
            current->comm, oc->gfp_mask, &oc->gfp_mask,
            nodemask_pr_args(nm), oc->order,
            current->signal->oom_score_adj);--------------------------------显示在哪个进程中触发了OOM。
        if (!IS_ENABLED(CONFIG_COMPACTION) && oc->order)
            pr_warn("COMPACTION is disabled!!!\n");
    
        cpuset_print_current_mems_allowed();
    2.      dump_stack();-------------------------------------------------输出当前现场的栈信息。
        if (oc->memcg)
            mem_cgroup_print_oom_info(oc->memcg, p);
        else
    3.       show_mem(SHOW_MEM_FILTER_NODES);------------------------------输出整个系统的内存使用情况。
        if (sysctl_oom_dump_tasks)
    4.      dump_tasks(oc->memcg, oc->nodemask);--------------------------显示系统所有进程的内存使用情况。
    }
    
    static void dump_tasks(struct mem_cgroup *memcg, const nodemask_t *nodemask)
    {
        struct task_struct *p;
        struct task_struct *task;
    
        pr_info("[ pid ]   uid  tgid total_vm      rss nr_ptes nr_pmds swapents oom_score_adj name\n");
        rcu_read_lock();
        for_each_process(p) {
            if (oom_unkillable_task(p, memcg, nodemask))-------------------不可被kill的进程不显示。
                continue;
    
            task = find_lock_task_mm(p);-----------------------------------内核线程等没有自己的mm,也无法被kill,所以不显示。
            if (!task) {
                continue;
            }
    
            pr_info("[%5d] %5d %5d %8lu %8lu %7ld %7ld %8lu         %5hd %s\n",
                task->pid, from_kuid(&init_user_ns, task_uid(task)),
                task->tgid, task->mm->total_vm, get_mm_rss(task->mm),
                atomic_long_read(&task->mm->nr_ptes),
                mm_nr_pmds(task->mm),
                get_mm_counter(task->mm, MM_SWAPENTS),
                task->signal->oom_score_adj, task->comm);-----------------total_vm和rss单位都是页。
            task_unlock(task);
        }
        rcu_read_unlock();
    }
    复制代码

    4.4 OOM Reaper

    内核创建oom_reaper内核线程,用于快速回收OOM选中的victim进程所使用的匿名内存、非VM_SHARED内存、swapped out内存等。

    4.4.1 创建oom_reaper内核线程

    oom_init()创建oom_reaper内核线程后即进入睡眠状态,等待产生OOM之后唤醒,进行收割。

    复制代码
    subsys_initcall(oom_init)

    static struct task_struct *oom_reaper_th;------------oom_reaper内核线程的task_struct指针。
    static DECLARE_WAIT_QUEUE_HEAD(oom_reaper_wait);-----OOM Reaper等待队列,oom_reaper线程在此等待,当有OOM产生的时候唤醒等待队列,并从oom_reaper_list中获取待收割进程结构体。
    static struct task_struct *oom_reaper_list;----------待收割的进程。
    static DEFINE_SPINLOCK(oom_reaper_lock);
    static int __init oom_init(void)
    {
        oom_reaper_th = kthread_run(oom_reaper, NULL, "oom_reaper");-----创建oom_reaper内核线程。
        if (IS_ERR(oom_reaper_th)) {
            pr_err("Unable to start OOM reaper %ld. Continuing regardless\n",
                    PTR_ERR(oom_reaper_th));
            oom_reaper_th = NULL;
        }
        return 0;
    }
    
    static int oom_reaper(void *unused)
    {
        while (true) {
            struct task_struct *tsk = NULL;
    
            wait_event_freezable(oom_reaper_wait, oom_reaper_list != NULL);----oom_reaper在此睡眠,直到有OOM产生并且通过wake_oom_reaper()唤醒。
            spin_lock(&oom_reaper_lock);
            if (oom_reaper_list != NULL) {
                tsk = oom_reaper_list;
                oom_reaper_list = tsk->oom_reaper_list;
            }
            spin_unlock(&oom_reaper_lock);
    
            if (tsk)
                oom_reap_task(tsk);-----------------------收割OOM选中的最bad进程,从流程看oom_reaper每次只能收割一个线程。
        }
    
        return 0;
    }
    复制代码

    4.4.2 收割进程

    复制代码
    #define MAX_OOM_REAP_RETRIES 10
    static void oom_reap_task(struct task_struct *tsk)
    {
        int attempts = 0;
        struct mm_struct *mm = tsk->signal->oom_mm;
    
        /* Retry the down_read_trylock(mmap_sem) a few times */
        while (attempts++ < MAX_OOM_REAP_RETRIES && !__oom_reap_task_mm(tsk, mm))
            schedule_timeout_idle(HZ/10);-------最多重试10次,每次间隔100ms,进行收割。
    
        if (attempts <= MAX_OOM_REAP_RETRIES)
            goto done;
    
        pr_info("oom_reaper: unable to reap pid:%d (%s)\n",
            task_pid_nr(tsk), tsk->comm);-------收割失败,显示系统hold住的lock信息。
        debug_show_all_locks();
    
    done:
        tsk->oom_reaper_list = NULL;
        set_bit(MMF_OOM_SKIP, &mm->flags);
        put_task_struct(tsk);
    }
    
    static bool __oom_reap_task_mm(struct task_struct *tsk, struct mm_struct *mm)
    {
        struct mmu_gather tlb;
        struct vm_area_struct *vma;
        struct zap_details details = {.check_swap_entries = true,
                          .ignore_dirty = true};
        bool ret = true;
    
        mutex_lock(&oom_lock);
    
        if (!down_read_trylock(&mm->mmap_sem)) {
            ret = false;
            goto unlock_oom;
        }
    
        if (mm_has_notifiers(mm)) {
            up_read(&mm->mmap_sem);
            schedule_timeout_idle(HZ);
            goto unlock_oom;
        }
    
        if (!mmget_not_zero(mm)) {
            up_read(&mm->mmap_sem);
            goto unlock_oom;
        }
    
        set_bit(MMF_UNSTABLE, &mm->flags);
    
        tlb_gather_mmu(&tlb, mm, 0, -1);
        for (vma = mm->mmap ; vma; vma = vma->vm_next) {
            if (is_vm_hugetlb_page(vma))------跳过hugetlb类型页面。
                continue;
    
            if (vma->vm_flags & VM_LOCKED)----跳过VM_LOCKED的vma区域。
                continue;
    
            if (vma_is_anonymous(vma) || !(vma->vm_flags & VM_SHARED))
                unmap_page_range(&tlb, vma, vma->vm_start, vma->vm_end,
                         &details);----------释放匿名、非VM_SHARED类型页面。
        }
        tlb_finish_mmu(&tlb, 0, -1);
        pr_info("oom_reaper: reaped process %d (%s), now anon-rss:%lukB, file-rss:%lukB, shmem-rss:%lukB\n",
                task_pid_nr(tsk), tsk->comm,
                K(get_mm_counter(mm, MM_ANONPAGES)),
                K(get_mm_counter(mm, MM_FILEPAGES)),
                K(get_mm_counter(mm, MM_SHMEMPAGES)));----------------显示经过oom_reaper之后的victim进程所占用的内存信息。
        up_read(&mm->mmap_sem);
    
        mmput_async(mm);
    unlock_oom:
        mutex_unlock(&oom_lock);
        return ret;
    }
    复制代码

    5. OOM实例解析

    对照一个OOM实例并解析如下:

    复制代码
    1. [19174.926798] copy invoked oom-killer: gfp_mask=0x24200c8(GFP_USER|__GFP_MOVABLE), nodemask=0, order=0, oom_score_adj=0--------参考dump_header(),输出OOM产生现场线程信息,包括分配掩码、OOM信息。
    2. [19174.937586] CPU: 0 PID: 163 Comm: copy Not tainted 4.9.56 #1---------参考show_stack(),显示栈信息。可以看出OOM现场的调用信息,这里可以看出是CMA分配出发了OOM。
    [19174.943274] 
    Call Trace:
    [<802f63c2>] dump_stack+0x1e/0x3c
    [<80132224>] dump_header.isra.6+0x84/0x1a0
    [<800f2d68>] oom_kill_process+0x23c/0x49c
    [<800f32fc>] out_of_memory+0xb0/0x3a0
    [<800f7834>] __alloc_pages_nodemask+0xa84/0xb5c
    [<801306b8>] alloc_migrate_target+0x34/0x6c
    [<8012f30c>] migrate_pages+0x108/0xbe4
    [<800f8a0c>] alloc_contig_range+0x188/0x378
    [<80130c54>] cma_alloc+0x100/0x220
    [<80388fe2>] dma_alloc_from_contiguous+0x2e/0x48
    [<8037bb30>] xxxxx_dma_alloc_coherent+0x48/0xdc
    [<8037be8c>] mem_zone_ioctl+0xf0/0x198
    [<80148cec>] do_vfs_ioctl+0x84/0x70c
    [<80149408>] SyS_ioctl+0x94/0xb8
    [<8004a246>] csky_systemcall+0x96/0xe0
    3. [19175.001223] Mem-Info:------------参考show_mem(),输出系统内存详细使用情况。这里可以看出free=592很少,active_anon和shmem非常大。
    [19175.003535] active_anon:99682 inactive_anon:12 isolated_anon:1----------显示当前系统所有node不同类型页面的统计信息,单位是页面。
    [19175.003535]  active_file:55 inactive_file:75 isolated_file:0
    [19175.003535]  unevictable:0 dirty:0 writeback:0 unstable:0
    [19175.003535]  slab_reclaimable:886 slab_unreclaimable:652
    [19175.003535]  mapped:2 shmem:91862 pagetables:118 bounce:0
    [19175.003535]  free:592 free_pcp:61 free_cma:0
    [19175.035394] Node 0 active_anon:398728kB inactive_anon:48kB active_file:220kB inactive_file:300kB unevictable:0kB isolated(anon):4kB isolated(file):0kB mapped:8kB dirty:0kB writeback:0kB shmem:367448kB writeback_tmp:0kB unstable:0kB pages_scanned:2515 all_unreclaimable? yes
    --------------------显示单个节点的内存统计信息,单位是kB。 [19175.059602] Normal free:2368kB min:2444kB low:3052kB high:3660kB active_anon:398728kB inactive_anon:48kB active_file:220kB inactive_file:300kB unevictable:0kB writepending:0kB present:1048572kB managed:734584kB mlocked:0kB slab_reclaimable:3544kB slab_unreclaimable:2608kB kernel_stack:624kB pagetables:472kB bounce:0kB free_pcp:244kB local_pcp:244kB free_cma:0kB
    --------------------显示某一zone下内存统计信息,单位是kB。 [19175.091602] lowmem_reserve[]: 0 0 0 [19175.095144] Normal: 21*4kB (MHI) 14*8kB (MHI) 13*16kB (HI) 2*32kB (HI) 4*64kB (MI) 2*128kB (MH) 0*256kB 2*512kB (HI) 1*1024kB (H) 1*2048kB (I) 0*4096kB = 5076kB
    --------------------显示某一zone的按页面可迁移类型划分页面大小,单位是KB。M表示Movable,U表示Unmovable,E表示rEclaimable,H表示Highatomic,C表示CMA,I表示Isolate。 91996 total pagecache pages [19175.112370] 262143 pages RAM [19175.115254] 0 pages HighMem/MovableOnly [19175.119106] 78497 pages reserved [19175.122350] 90112 pages cma reserved 4. [19175.125942] [ pid ] uid tgid total_vm rss nr_ptes nr_pmds swapents oom_score_adj name-----------参考dump_tasks(),输出系统可被kill的进程内存使用情况。 [19175.134514] [ 135] 0 135 1042 75 4 0 0 -1000 sshd [19175.143070] [ 146] 0 146 597 141 3 0 0 0 autologin [19175.152057] [ 147] 0 147 608 152 4 0 0 0 sh [19175.160434] [ 161] 0 161 109778 7328 104 0 0 0 xxxxx 5. [19175.169068] Out of memory: Kill process 161 (xxxxx) score 39 or sacrifice child---------------因为OOM待kill的进程信息。 6. [19175.176439] Killed process 161 (xxxxx) total-vm:439112kB, anon-rss:29304kB, file-rss:8kB, shmem-rss:0kB----------已经发送信号SIGKILL强制退出的进程信息。
    复制代码

    通过上面的信息可以知道是哪个进程、OOM现场、哪些内存消耗太多。

    这里需要重点查看系统的active_anon和shmem为什么如此大,造成了OOM。

    6. 对OOM的配置

    6.1 系统OOM配置

    /proc/sys/vm/oom_dump_tasks:如果设置,则dump_tasks()显示当前系统所有进程内存使用状态。
    /proc/sys/vm/oom_kill_allocating_task:如果设置了sysctl_oom_kill_allocating_task,在当前进程满足一定条件下,优先选择当前进程作为OOM杀死对象。

    复制代码
    bool out_of_memory(struct oom_control *oc)
    {
    ...
        if (!is_memcg_oom(oc) && 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;
        }
    ...
    }
    复制代码

    /proc/sys/vm/panic_on_oom:在发生OOM的时候是否进行panic。

    复制代码
    static void check_panic_on_oom(struct oom_control *oc,
                       enum oom_constraint constraint)
    {
        if (likely(!sysctl_panic_on_oom))-------------panic_on_oom为0表示发生OOM的时候不进行panic()。
            return;
        if (sysctl_panic_on_oom != 2) {---------------panic_on_oom为非2,是否进入panic还需要根据constraint来决定。
            /*
             * panic_on_oom == 1 only affects CONSTRAINT_NONE, the kernel
             * does not panic for cpuset, mempolicy, or memcg allocation
             * failures.
             */
            if (constraint != CONSTRAINT_NONE)
                return;
        }
        /* Do not panic for oom kills triggered by sysrq */
        if (is_sysrq_oom(oc))return;
        dump_header(oc, NULL);
        panic("Out of memory: %s panic_on_oom is enabled\n",
            sysctl_panic_on_oom == 2 ? "compulsory" : "system-wide");--panic_on_oom为2,则强制进行panic()。
    } 
    复制代码

    6.2 进程OOM配置

    /proc/xxx/oom_adj:可读写,范围是-17~15。oom_adj是oom_score_adj经过换算能得到。

    复制代码
    static ssize_t oom_adj_read(struct file *file, char __user *buf, size_t count,
                    loff_t *ppos)
    {
        struct task_struct *task = get_proc_task(file_inode(file));
        char buffer[PROC_NUMBUF];
        int oom_adj = OOM_ADJUST_MIN;
        size_t len;
    
        if (!task)
            return -ESRCH;
        if (task->signal->oom_score_adj == OOM_SCORE_ADJ_MAX)
            oom_adj = OOM_ADJUST_MAX;
        else
            oom_adj = (task->signal->oom_score_adj * -OOM_DISABLE) /
                  OOM_SCORE_ADJ_MAX;
        put_task_struct(task);
        len = snprintf(buffer, sizeof(buffer), "%d\n", oom_adj);
        return simple_read_from_buffer(buf, count, ppos, buffer, len);
    }
    复制代码

    /proc/xxx/oom_score:只读。经过oom_badness()计算得出对当前进程消耗页面数目,然后相对于totalpages归一化到1000。

    复制代码
    static int proc_oom_score(struct seq_file *m, struct pid_namespace *ns,
                  struct pid *pid, struct task_struct *task)
    {
        unsigned long totalpages = totalram_pages + total_swap_pages;
        unsigned long points = 0;
    
        points = oom_badness(task, NULL, NULL, totalpages) *
                        1000 / totalpages;
        seq_printf(m, "%lu\n", points);
    
        return 0;
    }
    复制代码

    /proc/xxx/oom_score_adj:可读写,范围是-1000~1000。内核中用于计算进程badness points参数。

    相关阅读:《Linux内核OOM Killer机制》、《Linux OOM机制介绍》、《Linux内核OOM机制的详细分析》、《Linux内核OOM机制分析》 

    联系方式:arnoldlu@qq.com
     
    【作者】张昺华
    【大饼教你学系列】https://edu.csdn.net/course/detail/10393
    【新浪微博】 张昺华--sky
    【twitter】 @sky2030_
    【微信公众号】 张昺华
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    tensorflow slim代码使用
    Tensorflow学习之TF-Slim的使用
    FCN用卷积层代替FC层原因(转)
    ubuntu命令查看英伟达显卡型号
    传输
    将tf-faster-rcnn检测结果画在一张图像内
    GPU跑tf-faster-rcnn demo以及训练自己的数据
    以太网适配器的驱动程序出现问题
    TensofFlow函数: tf.image.crop_and_resize
    TensorFlow函数: tf.stop_gradient
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/15529031.html
Copyright © 2011-2022 走看看