linux内核OOM源码分析

阅读更多

Out Of Memory(OOM),即内存耗尽,当系统中内存耗尽时,如果不做处理,将处于崩溃的边缘,因为无内核资源可用,而系统运行时刻都可能需要申请内存。这时,内核需要采取一定的措施来防止系统崩溃,这就是我们熟知的OOM流程,其实就是要回收一些内存,而走到OOM流程,已经基本说明其它的回收内存的手段都已经尝试过了(比如回收cache),这里通常只能通过kill进程来回收内存了,而选择被kill进程的标准就比较简单直接了,总体就是:谁用的多,就kill谁。
OOM处理的基本流程简单描述如下:
1、检查是否配置了/proc/sys/kernel/panic_on_oom,如果是则直接触发panic。
2、检查是否配置了oom_kill_allocating_task,即是否需要kill current进程来回收内存,如果是,且current进程是killable的,则kill current进程。
3、根据既定策略选择需要kill的process,基本策略为:通过进程的内存占用情况计算“点数”,点数最高者被选中。
4、如果没有选出来可kill的进程,那么直接panic(通常不会走到这个流程,但也有例外,比如,当被选中的进程处于D状态,或者正在被kill)
5、kill掉被选中的进程,以释放内存。
代码注释如下:

点击(此处)折叠或打开

  1. /*
  2.   * OOM处理的主流程,上面的注释应该比较清楚了。
  3.   */
  4. void out_of_memory(struct zonelist *zonelist, gfp_t gfp_mask,
  5.         int order, nodemask_t *nodemask, bool force_kill)
  6. {
  7.     const nodemask_t *mpol_mask;
  8.     struct task_struct *p;
  9.     unsigned long totalpages;
  10.     unsigned long freed = 0;
  11.     unsigned int uninitialized_var(points);
  12.     enum oom_constraint constraint = CONSTRAINT_NONE;
  13.     int killed = 0;
  14.     // 调用block通知链oom_nofify_list中的函数
  15.     blocking_notifier_call_chain(&oom_notify_list, 0, &freed);
  16.     if (freed > 0)
  17.         /* Got some memory back in the last second. */
  18.         return;
  19.     /*
  20.      * If current has a pending SIGKILL or is exiting, then automatically
  21.      * select it. The goal is to allow it to allocate so that it may
  22.      * quickly exit and free its memory.
  23.      */
  24.     /*
  25.      * 如果当前进程有pending的SIGKILL(9)信号,或者正在退出,则选择当前进程来kill,
  26.      * 这样可以最快的达到释放内存的目的。
  27.      */
  28.     if (fatal_signal_pending(current) || current->flags & PF_EXITING) {
  29.         set_thread_flag(TIF_MEMDIE);
  30.         return;
  31.     }
  32.     /*
  33.      * Check if there were limitations on the allocation (only relevant for
  34.      * NUMA) that may require different handling.
  35.      */
  36.     /*
  37.      * 检查是否有限制,有几种不同的限制策略,仅用于NUMA场景
  38.      */
  39.     constraint = constrained_alloc(zonelist, gfp_mask, nodemask,
  40.                         &totalpages);
  41.     mpol_mask = (constraint == CONSTRAINT_MEMORY_POLICY) ? nodemask : NULL;
  42.     // 检查是否配置了/proc/sys/kernel/panic_on_oom,如果是则直接触发panic
  43.     check_panic_on_oom(constraint, gfp_mask, order, mpol_mask);
  44.     /* 
  45.      * 检查是否配置了oom_kill_allocating_task,即是否需要kill current进程来
  46.      * 回收内存,如果是,且current进程是killable的,则kill current进程。
  47.      */
  48.     if (sysctl_oom_kill_allocating_task && current->mm &&
  49.      !oom_unkillable_task(current, NULL, nodemask) &&
  50.      current->signal->oom_score_adj != OOM_SCORE_ADJ_MIN) {
  51.         get_task_struct(current);
  52.         // kill被选中的进程。
  53.         oom_kill_process(current, gfp_mask, order, 0, totalpages, NULL,
  54.                  nodemask,
  55.                  "Out of memory (oom_kill_allocating_task)");
  56.         goto out;
  57.     }
  58.     // 根据既定策略选择需要kill的process。
  59.     p = select_bad_process(&points, totalpages, mpol_mask, force_kill);
  60.     /* Found nothing?!?! Either we hang forever, or we panic. */
  61.     /*
  62.      * 如果没有选出来,即没有可kill的进程,那么直接panic
  63.      * 通常不会走到这个流程,但也有例外,比如,当被选中的进程处于D状态,或者正在被kill
  64.      */
  65.     if (!p) {
  66.         dump_header(NULL, gfp_mask, order, NULL, mpol_mask);
  67.         panic("Out of memory and no killable processes...\n");
  68.     }
  69.     // kill掉被选中的进程,以释放内存。
  70.     if (PTR_ERR(p) != -1UL) {
  71.         oom_kill_process(p, gfp_mask, order, points, totalpages, NULL,
  72.                  nodemask, "Out of memory");
  73.         killed = 1;
  74.     }
  75. out:
  76.     /*
  77.      * Give the killed threads a good chance of exiting before trying to
  78.      * allocate memory again.
  79.      */
  80.     /*
  81.      * 在重新分配内存之前,给被kill的进程1s的时间完成exit相关处理,通常情况
  82.      * 下,1s应该够了。
  83.      */
  84.     if (killed)
  85.         schedule_timeout_killable(1);
  86. }

out_of_memory->select_bad_process
通过select_bad_process函数选择被kill的进程,其基本流程为:
1、遍历系统中的所有进程,进行"点数"计算
2、进行一些特殊情况的处理,比如: 优先选择触发OOM的进程、不处理正在exit的进程等。
3、计算"点数",选择点数最大的进程。通过函数oom_badness()
代码注释和分析如下:

点击(此处)折叠或打开

  1. /*
  2.   * OOM流程中,用来选择被kill的进程的函数
  3.   * @ppoints:点数,用来计算每个进程被"选中"可能性,点数越高,越可能被"选中"
  4.   */
  5. static struct task_struct *select_bad_process(unsigned int *ppoints,
  6.         unsigned long totalpages, const nodemask_t *nodemask,
  7.         bool force_kill)
  8. {
  9.     struct task_struct *g, *p;
  10.     struct task_struct *chosen = NULL;
  11.     unsigned long chosen_points = 0;
  12.     rcu_read_lock();
  13.     // 遍历系统中的所有进程,进行"点数"计算
  14.     do_each_thread(g, p) {
  15.         unsigned int points;
  16.         /* 
  17.          * 进行一些特殊情况的处理,比如: 优先选择触发OOM的进程、不处理
  18.          * 正在exit的进程等。
  19.          */        
  20.         switch (oom_scan_process_thread(p, totalpages, nodemask,
  21.                         force_kill)) {
  22.         case OOM_SCAN_SELECT:
  23.             chosen = p;
  24.             chosen_points = ULONG_MAX;
  25.             /* fall through */
  26.         case OOM_SCAN_CONTINUE:
  27.             continue;
  28.         case OOM_SCAN_ABORT:
  29.             rcu_read_unlock();
  30.             return ERR_PTR(-1UL);
  31.         case OOM_SCAN_OK:
  32.             break;
  33.         };
  34.         // 计算"点数",选择点数最大的进程。
  35.         points = oom_badness(p, NULL, nodemask, totalpages);
  36.         if (points > chosen_points) {
  37.             chosen = p;
  38.             chosen_points = points;
  39.         }
  40.     } while_each_thread(g, p);
  41.     if (chosen)
  42.         get_task_struct(chosen);
  43.     rcu_read_unlock();
  44.     *ppoints = chosen_points * 1000 / totalpages;
  45.     return chosen;
  46. }

out_of_memory->select_bad_process->oom_scan_process_thread
oom_scan_process_thread函数的分析和注释如下:

点击(此处)折叠或打开

  1. enum oom_scan_t oom_scan_process_thread(struct task_struct *task,
  2.         unsigned long totalpages, const nodemask_t *nodemask,
  3.         bool force_kill)
  4. {
  5.     // 如果进程正在exit
  6.     if (task->exit_state)
  7.         return OOM_SCAN_CONTINUE;
  8.     /*
  9.      * 如果进程不能被kill,比如: init进程或进程在nodemask对应的节点上,
  10.      * 没有可以释放的内存。
  11.      */
  12.     if (oom_unkillable_task(task, NULL, nodemask))
  13.         return OOM_SCAN_CONTINUE;
  14.     /*
  15.      * This task already has access to memory reserves and is being killed.
  16.      * Don't allow any other task to have access to the reserves.
  17.      */
  18.     /* 
  19.      * 如果有进程正在被OOM流程kill,那么应该有内存可以释放了,就不需要再kill
  20.      * 其它进程了,此时返回abort,结束oom kill流程。
  21.      */
  22.     if (test_tsk_thread_flag(task, TIF_MEMDIE)) {
  23.         if (unlikely(frozen(task)))
  24.             __thaw_task(task);
  25.         if (!force_kill)
  26.             return OOM_SCAN_ABORT;
  27.     }
  28.     // 如果不存在mm了(可能进程刚退出了)
  29.     if (!task->mm)
  30.         return OOM_SCAN_CONTINUE;
  31.     /*
  32.      * If task is allocating a lot of memory and has been marked to be
  33.      * killed first if it triggers an oom, then select it.
  34.      */
  35.     // 优先选择触发OOM的进程。
  36.     if (oom_task_origin(task))
  37.         return OOM_SCAN_SELECT;
  38.     if (task->flags & PF_EXITING && !force_kill) {
  39.         /*
  40.          * If this task is not being ptraced on exit, then wait for it
  41.          * to finish before killing some other task unnecessarily.
  42.          */
  43.         if (!(task->group_leader->ptrace & PT_TRACE_EXIT))
  44.             return OOM_SCAN_ABORT;
  45.     }
  46.     return OOM_SCAN_OK;
  47. }

out_of_memory->select_bad_process->oom_badness
oom_badness用于计算进程的“点数”,点数最高者被选中,代码注释和分析如下:

点击(此处)折叠或打开

  1. /*
  2.  * 计算进程"点数"(代表进程被选中的可能性)的函数,点数根据进程占用的物理内存来计算
  3.  * 物理内存占用越多,被选中的可能性越大。root processes有3%的bonus。
  4.  */
  5. unsigned long oom_badness(struct task_struct *p, struct mem_cgroup *memcg,
  6.              const nodemask_t *nodemask, unsigned long totalpages)
  7. {
  8.     long points;
  9.     long adj;
  10.     if (oom_unkillable_task(p, memcg, nodemask))
  11.         return 0;
  12.     // 确认进程是否还存在
  13.     p = find_lock_task_mm(p);
  14.     if (!p)
  15.         return 0;
  16.     adj = (long)p->signal->oom_score_adj;
  17.     if (adj == OOM_SCORE_ADJ_MIN) {
  18.         task_unlock(p);
  19.         return 0;
  20.     }
  21.     /*
  22.      * The baseline for the badness score is the proportion of RAM that each
  23.      * task's rss, pagetable and swap space use.
  24.      */
  25.     // 点数=rss(驻留内存/占用物理内存)+pte数+交换分区用量
  26.     points = get_mm_rss(p->mm) + p->mm->nr_ptes +
  27.          get_mm_counter(p->mm, MM_SWAPENTS);
  28.     task_unlock(p);
  29.     /*
  30.      * Root processes get 3% bonus, just like the __vm_enough_memory()
  31.      * implementation used by LSMs.
  32.      */
  33.     /*
  34.      * root用户启动的进程,有总 内存*3% 的bonus,就是说可以使用比其它进程多3%的内存
  35.      * 3%=30/1000
  36.      */
  37.     if (has_capability_noaudit(p, CAP_SYS_ADMIN))
  38.         adj -= 30;
  39.     /* Normalize to oom_score_adj units */
  40.     // 归一化"点数"单位
  41.     adj *= totalpages / 1000;
  42.     points += adj;
  43.     /*
  44.      * Never return 0 for an eligible task regardless of the root bonus and
  45.      * oom_score_adj (oom_score_adj can't be OOM_SCORE_ADJ_MIN here).
  46.      */
  47.     return points > 0 ? points : 1;
  48. }

out_of_memory->oom_kill_process
oom_kill_process()函数用于:kill被选中的进程,其实就是给指定进程发送SIGKILL信号,待被选中进程返回用户态时,进行信号处理。
相关代码注释和分析如下:

点击(此处)折叠或打开

  1. /*
  2.   * kill被选中的进程,在OOM流程中被调用
  3.   */
  4. void oom_kill_process(struct task_struct *p, gfp_t gfp_mask, int order,
  5.          unsigned int points, unsigned long totalpages,
  6.          struct mem_cgroup *memcg, nodemask_t *nodemask,
  7.          const char *message)
  8. {
  9.     struct task_struct *victim = p;
  10.     struct task_struct *child;
  11.     struct task_struct *= p;
  12.     struct mm_struct *mm;
  13.     unsigned int victim_points = 0;
  14.     static DEFINE_RATELIMIT_STATE(oom_rs, DEFAULT_RATELIMIT_INTERVAL,
  15.                      DEFAULT_RATELIMIT_BURST);
  16.     /*
  17.      * If the task is already exiting, don't alarm the sysadmin or kill
  18.      * its children or threads, just set TIF_MEMDIE so it can die quickly
  19.      */
  20.     /*
  21.      * 如果进程正在exiting,就没有必要再kill它了,直接设置TIF_MEMDIE,然后返回。
  22.     */
  23.     if (p->flags & PF_EXITING) {
  24.         set_tsk_thread_flag(p, TIF_MEMDIE);
  25.         put_task_struct(p);
  26.         return;
  27.     }
  28.     if (__ratelimit(&oom_rs))
  29.         dump_header(p, gfp_mask, order, memcg, nodemask);
  30.     task_lock(p);
  31.     pr_err("%s: Kill process %d (%s) score %d or sacrifice child\n",
  32.         message, task_pid_nr(p), p->comm, points);
  33.     task_unlock(p);
  34.     /*
  35.      * If any of p's children has a different mm and is eligible for kill,
  36.      * the one with the highest oom_badness() score is sacrificed for its
  37.      * parent. This attempts to lose the minimal amount of work done while
  38.      * still freeing memory.
  39.      */
  40.     /*
  41.      * 如果被选中的进程的子进程,不跟其共享mm(通常是这样),且膐om_badness的
  42.      * 得分更高,那么重新选择该子进程为被kill的进程。
  43.      */
  44.     read_lock(&tasklist_lock);
  45.     do {
  46.         // 遍历被选中进程的所有子进程
  47.         list_for_each_entry(child, &t->children, sibling) {
  48.             unsigned int child_points;
  49.             // 如果不共享mm
  50.             if (child->mm == p->mm)
  51.                 continue;
  52.             /*
  53.              * oom_badness() returns 0 if the thread is unkillable
  54.              */
  55.             // 计算child?om_badness得分
  56.             child_points = oom_badness(child, memcg, nodemask,
  57.                                 totalpages);
  58.             // 如果child得分更高,则将被选中进程换成child
  59.             if (child_points > victim_points) {
  60.                 put_task_struct(victim);
  61.                 victim = child;
  62.                 victim_points = child_points;
  63.                 get_task_struct(victim);
  64.             }
  65.         }
  66.     } while_each_thread(p, t);
  67.     read_unlock(&tasklist_lock);
  68.     rcu_read_lock();
  69.     /*
  70.      * 遍历确认被选中进程的线程组,判断是否还存在task_struct->mm,如果不存在
  71.      * (有可能这个时候进程退出了,或释放了mm),就没必要再kill了。
  72.      * 如果存在则选择线程组中的进程。
  73.      */
  74.     p = find_lock_task_mm(victim);
  75.     if (!p) {
  76.         rcu_read_unlock();
  77.         put_task_struct(victim);
  78.         return;
  79.     // 如果新选择的进程跟之前的不是同一个,那么更新victim。
  80.     } else if (victim != p) {
  81.         get_task_struct(p);
  82.         put_task_struct(victim);
  83.         victim = p;
  84.     }
  85.     /* mm cannot safely be dereferenced after task_unlock(victim) */
  86.     mm = victim->mm;
  87.     pr_err("Killed process %d (%s) total-vm:%lukB, anon-rss:%lukB, file-rss:%lukB\n",
  88.         task_pid_nr(victim), victim->comm, K(victim->mm->total_vm),
  89.         K(get_mm_counter(victim->mm, MM_ANONPAGES)),
  90.         K(get_mm_counter(victim->mm, MM_FILEPAGES)));
  91.     task_unlock(victim);
  92.     /*
  93.      * Kill all user processes sharing victim->mm in other thread groups, if
  94.      * any. They don'get access to memory reserves, though, to avoid
  95.      * depletion of all memory. This prevents mm->mmap_sem livelock when an
  96.      * oom killed thread cannot exit because it requires the semaphore and
  97.      * its contended by another thread trying to allocate memory itself.
  98.      * That thread will now get access to memory reserves since it has a
  99.      * pending fatal signal.
  100.      */
  101.     /*
  102.      * 遍历系统中的所有进程,寻找在其它线程组中,跟被选中进程(victim)共享mm结构
  103.      * 的进程(内核线程除外),共享mm结构即共享进程地址空间,比如fork后exec之前,
  104.      * 父子进程是共享mm的,回收内存必须要将共享mm的所有进程都kill掉。
  105.      */
  106.     for_each_process(p)
  107.         if (p->mm == mm && !same_thread_group(p, victim) &&
  108.          !(p->flags & PF_KTHREAD)) {
  109.             if (p->signal->oom_score_adj == OOM_SCORE_ADJ_MIN)
  110.                 continue;
  111.             // 进行task_struct相关操作时,通常需要获取该锁。
  112.             task_lock(p);    /* Protect ->comm from prctl() */
  113.             pr_err("Kill process %d (%s) sharing same memory\n",
  114.                 task_pid_nr(p), p->comm);
  115.             task_unlock(p);
  116.             // 通过向被选中的进程发送kill信号,来kill进程。
  117.             do_send_sig_info(SIGKILL, SEND_SIG_FORCED, p, true);
  118.         }
  119.     rcu_read_unlock();
  120.     // 进程设置TIF_MEMDIE标记,表示进程正在被oom killer终止中。
  121.     set_tsk_thread_flag(victim, TIF_MEMDIE);
  122.     /*
  123.      * 最终通过向被选中的进程发送kill信号,来kill进程,被kill的进程在从内核态
  124.      * 返回用户态时,进行信号处理。
  125.      * 被选中的进程可以是自己(current),则current进程会在oom流程执行完成后,返回
  126.      * 用户态时,处理信号。
  127.      */
  128.     do_send_sig_info(SIGKILL, SEND_SIG_FORCED, victim, true);
  129.     put_task_struct(victim);
  130. }

你可能感兴趣的:(linux,oom,源码)