zoukankan      html  css  js  c++  java
  • Linux OOM机制分析

    一、OOM机制简介

      Linux下面有个特性叫OOM killer(Out Of Memory killer),这个东西会在系统内存耗尽的情况下跳出来,选择性的干掉一些进程以求释放一些内存。相信广大从事Linux服务端编程的农民工兄弟们或多或少遇到过(人在江湖漂,哪有不挨刀啊)。典型的情况是:某天机器突然登不上了,能ping通,但是ssh死活连不了。原因是sshd进程被OOM killer干掉了(泪流满面)。重启机器后查看系统日志会发现血淋淋的Out of Memory: Killed process ×××、Out of Memory: Killed process 〇〇〇。一篇狼藉,惨不忍睹。具体的记录日志是在/var/log/messages中,如果出现了Out of memory字样,说明系统曾经出现过OOM!

    二、When(什么时候出现)

      Linux下允许程序申请比系统可用内存更多的内存,这个特性叫Overcommit。这样做是出于优化系统考虑,因为不是所有的程序申请了内存就立刻使用的,当你使用的时候说不定系统已经回收了一些资源了。不幸的是,当你用到这个Overcommit给你的内存的时候,系统还没有资源的话,OOM killer就跳出来了。

      参数/proc/sys/vm/overcommit_memory可以控制进程对内存过量使用的应对策略

      • 1.当overcommit_memory=0 允许进程轻微过量使用内存,但对于大量过载请求则不允许,也就是当内存消耗过大就是触发OOM killer。
      • 2.当overcommit_memory=1 永远允许进程overcommit,不会触发OOM killer
      • 3.当overcommit_memory=2 永远禁止overcommit,不会触发OOM killer。

    三、相关系统参数

    3.1 overcommit_memory

      参数/proc/sys/vm/overcommit_memory可以控制进程对内存过量使用的应对策略

      • 1.当overcommit_memory=0 允许进程轻微过量使用内存,但对于大量过载请求则不允许,也就是当内存消耗过大就是触发OOM killer。
      • 2.当overcommit_memory=1 永远允许进程overcommit,不会触发OOM killer。
      • 3.当overcommit_memory=2 永远禁止overcommit,不会触发OOM killer。

    3.2 panic_on_oom

      参数:panic_on_oom: 用来控制当内存不足时该如何做。
      查看:cat /proc/sys/vm/panic_on_oom
      值为0:内存不足时,启动 OOM killer。
      值为1:内存不足时,有可能会触发 kernel panic(系统重启),也有可能启动 OOM killer。
      值为2:内存不足时,表示强制触发 kernel panic,内核崩溃GG(系统重启)。

    3.3 oom_kill_allocating_task

      参数:oom_kill_allocating_task: 用来决定触发OOM时先杀掉哪种进程
      cat /proc/sys/vm/oom_kill_allocating_task
      值为0:会 kill 掉得分最高的进程
      值为非0:会kill 掉当前申请内存而触发OOM的进程
      当然,一些系统进程(如init)或者被用户设置了oom_score_adj的进程等可不是说杀就杀的。

    3.4 oom_dump_tasks

      参数:oom_dump_tasks用来记录触发OOM时记录哪些日志
      cat /proc/sys/vm/oom_dump_tasks
      oom_dump_tasks参数可以记录进程标识信息、该进程使用的虚拟内存总量、物理内存、进程的页表信息等。

      值为0:关闭打印上述日志。在大型系统中,可能存在上千进程,逐一打印使用内存信息可能会造成性能问题。
      值为非0:有三种情况会打印进程内存使用情况。
        1、由 OOM 导致 kernel panic 时
        2、没有找到符合条件的进程 kill 时
        3、找到符合条件的进程并 kill 时

    3.5 oom_adj、oom_score_adj  和 oom_score

      参数:oom_adj、oom_score_adj 和 oom_score用来控制进程打分(分数越高,就先杀谁)
      这三个参数的关联性比较紧密,都和具体的进程相关,位置都是在 /proc/进程PID/ 目录下。

      内核会对进程打分(oom_score),主要包括两部分,系统打分和用户打分。系统打分就是根据进程的物理内存消耗量(进程自身的空间、swap空间、页缓存空间);用户打分就是 oom_score_adj 的值。如果用户指定 oom_score_adj 的值为 -1000,也就是表示禁止 OOM killer 杀死该进程

      用户可以通过调整 oom_score_adj 的值来决定最终 oom_score 的值,oom_score_adj 的取值范围是 -1000~1000,为0时表示用户不调整 oom_score。另外,root进程拥有3%的内存使用特权,因此做最终 oom_score 计算时需要减去这些内存使用量。

      oom_adj是一个旧的接口参数,其功能类似oom_score_adj,为了兼容,目前仍然保留这个参数,当操作这个参数的时候,kernel实际上是会换算成oom_score_adj。

    四、OOM机制详解

    4.1 OOM分析

      oom_killer(out of memory killer)是Linux内核的一种内存管理机制,在系统可用内存较少的情况下,内核为保证系统还能够继续运行下去,会选择杀掉一些进程释放掉一些内存。通常oom_killer的触发流程是:进程A想要分配物理内存(通常是当进程真正去读写一块内核已经“分配”给它的内存)->触发缺页异常->内核去分配物理内存->物理内存不够了,触发OOM。

      一句话说明oom_killer的功能:当系统物理内存不足时,oom_killer遍历当前所有进程,根据进程的内存使用情况进行打分,然后从中选择一个分数最高的进程,杀之取内存

    4.2 函数解析

    4.2.1 out_of_memory函数

      oom_killer的处理主要集中在mm/oom_kill.c。

      核心函数为out_of_memory,函数处理流程:

      • 通知系统中注册了oom_notify_list的模块释放一些内存,如果从这些模块中释放出了一些内存,那么皆大欢喜,直接结束oom killer流程,回收失败, 那只有进入下一步开始oom_killer了;
      • 触发oom killer通常是由当前进程进行内存分配所引起,而如果当前进程已经挂起了一个SIG_KILL信号或者正在退出,直接选中当前进程,否则进入下一步;
      • check_panic_on_oom检查系统管理员的态度,看oom时是进行oom killer还是直接panic掉(系统崩溃重启),如果进行oom killer,则进入下一步;
      • 如果系统管理员规定,谁引起oom,杀掉谁,那就杀掉正在尝试分配内存的进程,oom killer结束,否则进入下一步;
      • 调用select_bad_process选中合适进程,然后调用oom_kill_process杀死选中进程,如果不幸select_bad_process没有选出任何进程,那么内核走投无路,只有panic了

      out_of_memory函数位于linux-source-4.15.0mmoom_kill.c中,源码如下:

      1 /**
      2  * out_of_memory - kill the "best" process when we run out of memory
      3  * @oc: pointer to struct oom_control
      4  *
      5  * If we run out of memory, we have the choice between either
      6  * killing a random task (bad), letting the system crash (worse)
      7  * OR try to be smart about which process to kill. Note that we
      8  * don't have to be perfect here, we just have to be good.
      9  * 当我们内存不足时,我们有两种处理方案:随机杀死一个任务,这可能会导致系统崩溃;
     10  * 或者尝试有策略地选出值得杀死的那个任务。我们没有必要做到最好,但我们只需尽力把这事做好”。
     11  * -- 潜台词:这件事我们至今都没找到一个完美算法,误杀进程我们不背锅。
     12  */
     13 bool out_of_memory(struct oom_control *oc)
     14 {
     15     unsigned long freed = 0;
     16     enum oom_constraint constraint = CONSTRAINT_NONE;
     17 
     18     /**
     19      * 系统没有配置OOM Killer
     20      */
     21     if (oom_killer_disabled)
     22         return false;
     23 
     24     if (!is_memcg_oom(oc)) {
     25         /**
     26          * 通知注册在oom_notify_list上的模块,释放一些内存出来,如果成功,那就不用启动oom killer了
     27          */
     28         blocking_notifier_call_chain(&oom_notify_list, 0, &freed);
     29         if (freed > 0)
     30             /**
     31              * Got some memory back in the last second.
     32              * 如果从这些模块中释放出了一些内存,那么皆大欢喜,直接结束oom killer流程
     33              */
     34             return true;
     35     }
     36 
     37     /*
     38      * If current has a pending SIGKILL or is exiting, then automatically
     39      * select it.  The goal is to allow it to allocate so that it may
     40      * quickly exit and free its memory.
     41      * 如果当前进程有一个挂起的SIGKILL未决信号或者正在退出,则自动选择它,
     42      * 目标是允许它分派以便它可以尽快的的退出和释放掉它所占有的内存
     43      *
     44      * task_will_free_mem函数其实是去检查一下当前有没有进程挂了,有的话就回收他的内存,
     45      * 回收内存后那就不需要oom killer杀进程了
     46      */
     47     if (task_will_free_mem(current)) {
     48         mark_oom_victim(current);
     49         wake_oom_reaper(current);
     50         return true;
     51     }
     52 
     53     /*
     54      * The OOM killer does not compensate for IO-less reclaim.
     55      * pagefault_out_of_memory lost its gfp context so we have to
     56      * make sure exclude 0 mask - all other users should have at least
     57      * ___GFP_DIRECT_RECLAIM to get here. But mem_cgroup_oom() has to
     58      * invoke the OOM killer even if it is a GFP_NOFS allocation.
     59      * OOM 杀手不补偿 IO-less 回收。
     60      * pagefault_out_of_memory 丢失了它的 gfp 上下文,所以我们必须确保排除 0 掩码 - 所有其他用户应该至少有
     61      *  ___GFP_DIRECT_RECLAIM 才能到达这里。但是 mem_cgroup_oom() 必须调用 OOM 杀手,即使它是 GFP_NOFS 分配。
     62      */
     63     if (oc->gfp_mask && !(oc->gfp_mask & __GFP_FS) && !is_memcg_oom(oc))
     64         return true;
     65 
     66     /*
     67      * Check if there were limitations on the allocation (only relevant for
     68      * NUMA and memcg) that may require different handling.
     69      * 检查可能需要不同处理的分配限制(仅与 NUMA 和 memcg 相关)。
     70      *
     71      * 对于有NUMA节点,会有节点间的限制
     72      */
     73     constraint = constrained_alloc(oc);
     74     if (constraint != CONSTRAINT_MEMORY_POLICY)
     75         oc->nodemask = NULL;
     76 
     77     /**
     78      * 检查/proc/sys/vm/panic_on_oom的设置,看看系统管理员是什么态度
     79      * 值为0:内存不足时,启动 OOM killer。
     80      * 值为1:内存不足时,有可能会触发 kernel panic(系统重启),也有可能启动 OOM killer。
     81      * 值为2:内存不足时,表示强制触发 kernel panic,内核崩溃GG(系统重启)。
     82      */
     83     check_panic_on_oom(oc, constraint);
     84 
     85     /**
     86      *  /proc/sys/vm/oom_kill_allocating_task为true的时候,直接kill掉当前想要分配内存的进程(此进程能够被kill时)
     87      * 当系统内存不足时,OOM Killer要选择哪个进程来杀死呢?怎样的进程才符合被杀死的条件?
     88      * 系统有如下选择:
     89      *  谁触发了OOM就干掉谁;
     90      *     谁最坏就干掉谁。
     91      * oom_kill_allocating_task参数就是用于控制OOM Killer选择杀死谁的。
     92      * 当oom_kill_allocating_task为0时就选择2,谁最坏就杀死谁;
     93      * 当oom_kill_allocating_task为其他值时,就选择1,谁触发的OOM就杀死谁。
     94      */
     95     if (!is_memcg_oom(oc) && sysctl_oom_kill_allocating_task &&
     96         current->mm && !oom_unkillable_task(current, NULL, oc->nodemask) &&
     97         current->signal->oom_score_adj != OOM_SCORE_ADJ_MIN) {
     98         get_task_struct(current);
     99         oc->chosen = current;
    100         oom_kill_process(oc, "Out of memory (oom_kill_allocating_task)");
    101         return true;
    102     }
    103 
    104     /**
    105      * 执行到此处,内核开始对所有进程进行审判,择其最坏者杀之
    106      */
    107     select_bad_process(oc);
    108     /* Found nothing?!?! Either we hang forever, or we panic. */
    109     /**
    110      * 找了一圈,没有找到任何一个进程可以被杀死(全都是背景深厚的进程…),内核走投无路,自杀
    111      */
    112     if (!oc->chosen && !is_sysrq_oom(oc) && !is_memcg_oom(oc)) {
    113         dump_header(oc, NULL);
    114         panic("Out of memory and no killable processes...
    ");
    115     }
    116 
    117     /**
    118      * 幸运的找到了一个合适的进程,去kill它,释放一点内存出来
    119      */
    120     if (oc->chosen && oc->chosen != (void *)-1UL) {
    121         oom_kill_process(oc, !is_memcg_oom(oc) ? "Out of memory" :
    122                  "Memory cgroup out of memory");
    123         /*
    124          * Give the killed process a good chance to exit before trying
    125          * to allocate memory again.
    126          * 如果有进程被选中了kill掉,且又不是当前进程,那主动让出CPU,给被选中
    127          * 的进程一些时间去处理后事,结束它自己的生命
    128          */
    129         schedule_timeout_killable(1);/*主动让出cpu*/
    130     }
    131     return !!oc->chosen;
    132 }
    View Code

    4.2.2 check_panic_on_oom函数

      check_panic_on_oom会对”/proc/sys/vm/panic_on_oom”值进行检查:

      • 值为0:内存不足时,启动 OOM killer。
      • 值为2:内存不足时,表示强制触发 kernel panic,内核崩溃GG(系统重启)。
      • 其它值:内存不足时,有可能会触发 kernel panic(系统重启),也有可能启动 OOM killer。根据相关约束进行判断。在有CONSTRAINT_CPUSET、CONSTRAINT_MEMORY_POLICY、CONSTRAINT_MEMCG的约束情况下的OOM,可以考虑不panic,而是启动OOM killer。
    oom_constraint约束
     1 enum oom_constraint {
     2     /**
     3      * 没有任何约束的情况下发生了OOM,表明确实是内存不够用了,直接panic;
     4      */
     5     CONSTRAINT_NONE,
     6     /**
     7      * 表明当前内存是NUMA(非一致内存),此时会将一组cpuset和memory绑定,
     8      * 当出现CONSTRAINT_CPUSET的OOM时,可能时当前memory node的内存不足了,整个系统的内存还是充足的;
     9      */
    10     CONSTRAINT_CPUSET,
    11     /**
    12      * memory policy是NUMA系统用于控制分配各个memory node资源的策略模块。
    13      * 用户空间进行(NUMA-aware)通过memory policy的API,
    14      * 来对整个系统、一个特定的进程、一个特定进程的特定虚拟地址VMA来制定策略。
    15      * 在这种约束条件下如果出现了OOM可能是memory policy约束导致的,而不是系统内存不足;
    16      */
    17     CONSTRAINT_MEMORY_POLICY,
    18     /**
    19      * 这是Cgroup中的memory control cgroup内存组控制策略。
    20      * 是cgroup子系统下memory的控制策略。在该约束下的OOM也不一定是系统内存不足。
    21      */
    22     CONSTRAINT_MEMCG,
    23 };

    4.2.3 select_bad_process函数

      位于linux-source-4.15.0mmoom_kill.c,源码如下

     1 /*
     2  * Simple selection loop. We choose the process with the highest number of
     3  * 'points'. In case scan was aborted, oc->chosen is set to -1.
     4  * 当系统内存不足的时候,out_of_memory()被触发,然后调用 select_bad_process() 选择一个“bad”进程杀掉
     5  * slect_bad_process从系统中选择一个适合被杀死的进程,对于系统关键进程(如init进程、内核线程等)是不能被杀死的,
     6  * 其它进程则通过oom_badness进行打分(0~1000),分数最高者被选中。
     7  */
     8 static void select_bad_process(struct oom_control *oc)
     9 {
    10     if (is_memcg_oom(oc))
    11         mem_cgroup_scan_tasks(oc->memcg, oom_evaluate_task, oc);
    12     else {
    13         struct task_struct *p;
    14 
    15         rcu_read_lock();
    16         for_each_process(p)
    17             if (oom_evaluate_task(p, oc))
    18                 break;
    19         rcu_read_unlock();
    20     }
    21 
    22     oc->chosen_points = oc->chosen_points * 1000 / oc->totalpages;
    23 }

       for_each_process(p)遍历所有进程,选出最坏的进程。

    4.2.4 oom_evaluate_task函数

      如果是核心进程如(init、内核线程等)一类,直接返回0,遍历下一个进程。如果遍历到的当前进程已经可以访问内存储备并且正在被终止,如果该进程持有MMF_OOM_SKIP,就返回0,开始遍历下一个对象,如果没有持有MMF_OOM_SKIP,就选择该进程。前面条件不符合,就调用oom_badness函数对该进程进行打分。

      位于linux-source-4.15.0mmoom_kill.c,源码如下

     1 static int oom_evaluate_task(struct task_struct *task, void *arg)
     2 {
     3     struct oom_control *oc = arg;
     4     unsigned long points;
     5 
     6     /*核心进程不能杀(init、内核线程等)*/
     7     if (oom_unkillable_task(task, NULL, oc->nodemask))
     8         goto next;
     9 
    10     /*
    11      * This task already has access to memory reserves and is being killed.
    12      * Don't allow any other task to have access to the reserves unless
    13      * the task has MMF_OOM_SKIP because chances that it would release
    14      * any memory is quite low.
    15      * 这个任务已经可以访问内存储备并且正在被终止。 除非任务具有 MMF_OOM_SKIP,
    16      * 否则不允许任何其他任务访问保留区,因为它释放任何内存的机会非常低。
    17      */
    18     if (!is_sysrq_oom(oc) && tsk_is_oom_victim(task)) {
    19         if (test_bit(MMF_OOM_SKIP, &task->signal->oom_mm->flags))
    20             goto next;
    21         goto abort;
    22     }
    23 
    24     /*
    25      * If task is allocating a lot of memory and has been marked to be
    26      * killed first if it triggers an oom, then select it.
    27      * 如果任务正在分配大量内存并且已被标记为在触发 oom 时首先被杀死,则选择它。
    28      */
    29     if (oom_task_origin(task)) {
    30         points = ULONG_MAX;
    31         goto select;
    32     }
    33 
    34     /**
    35      * 根据进程对物理内存(以及swap分区使用情况)给进程打分
    36      */
    37     points = oom_badness(task, NULL, oc->nodemask, oc->totalpages);
    38     /**
    39      * 如果分数为0,或者分数小于已选出的进程的分数,开始遍历后面的进程
    40      */
    41     if (!points || points < oc->chosen_points)
    42         goto next;
    43 
    44     /* Prefer thread group leaders for display purposes 尽量选择线程组的组长进程*/
    45     if (points == oc->chosen_points && thread_group_leader(oc->chosen))
    46         goto next;
    47 select:
    48     if (oc->chosen)
    49         put_task_struct(oc->chosen);
    50     get_task_struct(task);
    51     oc->chosen = task;
    52     oc->chosen_points = points;
    53 next:
    54     return 0;
    55 abort:
    56     if (oc->chosen)
    57         put_task_struct(oc->chosen);
    58     oc->chosen = (void *)-1UL;
    59     return 1;
    60 }

    4.2.5 oom_badness函数

      oom killer通过这个oom_badness函数进行打分,返回值是根据一定策略给进程打的分数,后续oom killer根据该分数高低选择出最该杀死的那个进程(分数越高越优先杀死),这里需要注意3个点:
      1、adj == OOM_SCORE_ADJ_MIN时,说明该进程已被设置为不可被杀死进程,返回的得分将无限低(LONG_MIN)。
      2、points = get_mm_rss(p->mm) + get_mm_counter(p->mm, MM_SWAPENTS) + mm_pgtables_bytes(p->mm) / PAGE_SIZE;分数公式,分数是由这三部分计算打出:进程所占用的内存中的空间、SWAP所占用的空间、page cache里所占用的空间 ;
      3、adj *= totalpages / 1000; points += adj; 分数另一部分的构成是这个oom_score_adj,这个是配置在内核文件的,范围是-1000~1000,默认是0,所以oom_badness先把该分数归一化,再做加法。

      位于linux-source-4.15.0mmoom_kill.c,源码如下

     1 /**
     2  * oom_badness - heuristic function to determine which candidate task to kill
     3  * @p: task struct of which task we should calculate
     4  * @totalpages: total present RAM allowed for page allocation
     5  *
     6  * The heuristic for determining which task to kill is made to be as simple and
     7  * predictable as possible.  The goal is to return the highest value for the
     8  * task consuming the most memory to avoid subsequent oom failures.
     9  *
    10  * oom killer通过这个oom_badness函数进行打分,返回值是根据一定策略给进程打的分数,
    11  * 后续oom killer根据该分数高低选择出最该杀死的那个进程(分数越高越优先杀死),这里需要注意3个点:
    12  *     1、adj == OOM_SCORE_ADJ_MIN时,说明该进程已被设置为不可被杀死进程,返回的得分将无限低(LONG_MIN)。
    13  *     2、points = get_mm_rss(p->mm) + get_mm_counter(p->mm, MM_SWAPENTS)
    14  *                 + mm_pgtables_bytes(p->mm) / PAGE_SIZE;分数公式,
    15  *        分数是由这三部分计算打出:进程所占用的内存中的空间、SWAP所占用的空间、page cache里所占用的空间 ;
    16  *     3、adj *= totalpages / 1000; points += adj; 分数另一部分的构成是这个oom_score_adj,
    17  *        这个是配置在内核文件的,范围是-1000~1000,默认是0,所以oom_badness先把该分数归一化,再做加法。
    18  */
    19 unsigned long oom_badness(struct task_struct *p, struct mem_cgroup *memcg,
    20               const nodemask_t *nodemask, unsigned long totalpages)
    21 {
    22     long points;
    23     long adj;
    24 
    25     //背景深厚杀不得的进程
    26     if (oom_unkillable_task(p, memcg, nodemask))
    27         return 0;
    28 
    29     p = find_lock_task_mm(p);
    30     if (!p)
    31         return 0;
    32 
    33     /*
    34      * Do not even consider tasks which are explicitly marked oom
    35      * unkillable or have been already oom reaped or the are in
    36      * the middle of vfork
    37      * oom_score_adj为-1000(或者oom_adj为-17)的不做处理,
    38      * 此值可以通过/proc/pid_num/oom_score_adj(oom_adj)设置,
    39      */
    40     adj = (long)p->signal->oom_score_adj;
    41     if (adj == OOM_SCORE_ADJ_MIN ||
    42             test_bit(MMF_OOM_SKIP, &p->mm->flags) ||
    43             in_vfork(p)) {
    44         task_unlock(p);
    45         return 0;
    46     }
    47 
    48     /*
    49      * The baseline for the badness score is the proportion of RAM that each
    50      * task's rss, pagetable and swap space use.
    51      * 获取进程的rss(用户空间的文件映射和匿名页占用的物理内存页数)、页表和swap中使用内存空间的情况
    52      */
    53     points = get_mm_rss(p->mm) + get_mm_counter(p->mm, MM_SWAPENTS) +
    54         mm_pgtables_bytes(p->mm) / PAGE_SIZE;
    55     task_unlock(p);
    56 
    57     /*
    58      * Root processes get 3% bonus, just like the __vm_enough_memory()
    59      * implementation used by LSMs.
    60      * 如果进程拥有CAP_SYS_ADMIN能力,得分减少3%,通常具有CAP_SYS_ADMIN的进程是被当做表现良好,
    61      * 一般不会出现内存泄露的进程
    62      */
    63     if (has_capability_noaudit(p, CAP_SYS_ADMIN))
    64         points -= (points * 3) / 100;
    65 
    66     /* Normalize to oom_score_adj units 归一化调整*/
    67     adj *= totalpages / 1000;
    68     points += adj;
    69 
    70     /*
    71      * Never return 0 for an eligible task regardless of the root bonus and
    72      * oom_score_adj (oom_score_adj can't be OOM_SCORE_ADJ_MIN here).
    73      * 无论最终的分数和 oom_score_adj(oom_score_adj 在这里不能是 OOM_SCORE_ADJ_MIN),
    74      * 都不要为符合条件的任务返回 0。
    75      */
    76     return points > 0 ? points : 1;
    77 }

    4.2.6 oom_badness函数

       杀死选出来的进程,函数源码如下

      1 static void oom_kill_process(struct oom_control *oc, const char *message)
      2 {
      3     struct task_struct *p = oc->chosen;
      4     unsigned int points = oc->chosen_points;
      5     struct task_struct *victim = p;
      6     struct task_struct *child;
      7     struct task_struct *t;
      8     struct mm_struct *mm;
      9     unsigned int victim_points = 0;
     10     static DEFINE_RATELIMIT_STATE(oom_rs, DEFAULT_RATELIMIT_INTERVAL,
     11                           DEFAULT_RATELIMIT_BURST);
     12     bool can_oom_reap = true;
     13 
     14     /*
     15      * If the task is already exiting, don't alarm the sysadmin or kill
     16      * its children or threads, just give it access to memory reserves
     17      * so it can die quickly
     18      */
     19     task_lock(p);
     20     if (task_will_free_mem(p)) {
     21         mark_oom_victim(p);
     22         wake_oom_reaper(p);
     23         task_unlock(p);
     24         put_task_struct(p);
     25         return;
     26     }
     27     task_unlock(p);
     28 
     29     if (__ratelimit(&oom_rs))
     30         dump_header(oc, p);//打印内核进程等的状态信息
     31 
     32     pr_err("%s: Kill process %d (%s) score %u or sacrifice child
    ",
     33         message, task_pid_nr(p), p->comm, points);
     34 
     35     /*
     36      * If any of p's children has a different mm and is eligible for kill,
     37      * the one with the highest oom_badness() score is sacrificed for its
     38      * parent.  This attempts to lose the minimal amount of work done while
     39      * still freeing memory.
     40      */
     41     read_lock(&tasklist_lock);
     42 
     43     /*
     44      * The task 'p' might have already exited before reaching here. The
     45      * put_task_struct() will free task_struct 'p' while the loop still try
     46      * to access the field of 'p', so, get an extra reference.
     47      */
     48     get_task_struct(p);
     49     for_each_thread(p, t) {
     50         list_for_each_entry(child, &t->children, sibling) {
     51             unsigned int child_points;
     52 
     53             if (process_shares_mm(child, p->mm))
     54                 continue;
     55             /*
     56              * oom_badness() returns 0 if the thread is unkillable
     57              */
     58             child_points = oom_badness(child,
     59                 oc->memcg, oc->nodemask, oc->totalpages);
     60             if (child_points > victim_points) {
     61                 put_task_struct(victim);
     62                 victim = child;
     63                 victim_points = child_points;
     64                 get_task_struct(victim);
     65             }
     66         }
     67     }
     68     put_task_struct(p);
     69     read_unlock(&tasklist_lock);
     70 
     71     p = find_lock_task_mm(victim);
     72     if (!p) {
     73         put_task_struct(victim);
     74         return;
     75     } else if (victim != p) {
     76         get_task_struct(p);
     77         put_task_struct(victim);
     78         victim = p;
     79     }
     80 
     81     /* Get a reference to safely compare mm after task_unlock(victim) */
     82     mm = victim->mm;
     83     mmgrab(mm);
     84 
     85     /* Raise event before sending signal: task reaper must see this */
     86     count_vm_event(OOM_KILL);
     87     count_memcg_event_mm(mm, OOM_KILL);
     88 
     89     /*
     90      * We should send SIGKILL before granting access to memory reserves
     91      * in order to prevent the OOM victim from depleting the memory
     92      * reserves from the user space under its control.
     93      */
     94     do_send_sig_info(SIGKILL, SEND_SIG_FORCED, victim, true);
     95     mark_oom_victim(victim);
     96     pr_err("Killed process %d (%s) total-vm:%lukB, anon-rss:%lukB, file-rss:%lukB, shmem-rss:%lukB
    ",
     97         task_pid_nr(victim), victim->comm, K(victim->mm->total_vm),
     98         K(get_mm_counter(victim->mm, MM_ANONPAGES)),
     99         K(get_mm_counter(victim->mm, MM_FILEPAGES)),
    100         K(get_mm_counter(victim->mm, MM_SHMEMPAGES)));
    101     task_unlock(victim);
    102 
    103     /*
    104      * Kill all user processes sharing victim->mm in other thread groups, if
    105      * any.  They don't get access to memory reserves, though, to avoid
    106      * depletion of all memory.  This prevents mm->mmap_sem livelock when an
    107      * oom killed thread cannot exit because it requires the semaphore and
    108      * its contended by another thread trying to allocate memory itself.
    109      * That thread will now get access to memory reserves since it has a
    110      * pending fatal signal.
    111      */
    112     rcu_read_lock();
    113     for_each_process(p) {
    114         if (!process_shares_mm(p, mm))
    115             continue;
    116         if (same_thread_group(p, victim))
    117             continue;
    118         if (is_global_init(p)) {
    119             can_oom_reap = false;
    120             set_bit(MMF_OOM_SKIP, &mm->flags);
    121             pr_info("oom killer %d (%s) has mm pinned by %d (%s)
    ",
    122                     task_pid_nr(victim), victim->comm,
    123                     task_pid_nr(p), p->comm);
    124             continue;
    125         }
    126         /*
    127          * No use_mm() user needs to read from the userspace so we are
    128          * ok to reap it.
    129          */
    130         if (unlikely(p->flags & PF_KTHREAD))
    131             continue;
    132         do_send_sig_info(SIGKILL, SEND_SIG_FORCED, p, true);
    133     }
    134     rcu_read_unlock();
    135 
    136     if (can_oom_reap)
    137         wake_oom_reaper(victim);
    138 
    139     mmdrop(mm);
    140     put_task_struct(victim);
    141 }
    View Code

     五、参考文章

    https://www.modb.pro/db/25985

    https://cloud.tencent.com/developer/article/1157275

    本文来自博客园,作者:Mr-xxx,转载请注明原文链接:https://www.cnblogs.com/MrLiuZF/p/15229868.html

  • 相关阅读:
    试玩mpvue,用vue的开发模式开发微信小程序
    laravel 整合 swoole ,并简单 ab 测试对比性能以及在 PHPstorm 中利用debug调试配置swoole服务中的PHP代码
    移动端固定头部和固定左边第一列的实现方案(Vue中实现demo)
    PhpStorm 2017.3 版本在 Mac 系统 macOS High Sierra 版本 10.13.3 中运行很卡顿
    xdebug : Debug session was finished without being paused
    SVN checkout 出的项目在PHPstorm中打开没有subversion(SVN)选项按钮怎么办?
    PHP应用的CI/CD流程实践与学习:一、PHP运行环境的准备
    Mac环境下PHPstorm配置xdebug开发调试web程序
    『备忘录』elasticsearch 去重分页查询
    Mac下docker搭建lnmp环境 + redis + elasticsearch
  • 原文地址:https://www.cnblogs.com/MrLiuZF/p/15229868.html
Copyright © 2011-2022 走看看