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

    专题: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...
    ");
        }
        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
    ",
            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
    ",
            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)
    ",
                        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
    ",
            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!!!
    ");
    
        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
    ");
        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
    ",
                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
    ",
                    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)
    ",
            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
    ",
                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
    ",
            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
    ", 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
    ", points);
    
        return 0;
    }

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

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

  • 相关阅读:
    HTML DOM 06 节点关系
    HTML DOM 05 事件(三)
    HTML DOM 05 事件(二)
    HTML DOM 05 事件(一)
    html DOM 04 样式
    html DOM 03 节点的属性
    html DOM 02 获取节点
    html DOM 01 节点概念
    JavaScript 29 计时器
    JavaScript 28 弹出框
  • 原文地址:https://www.cnblogs.com/arnoldlu/p/8567559.html
Copyright © 2011-2022 走看看