`
nuistcc
  • 浏览: 84028 次
社区版块
存档分类
最新评论

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内核完全注释及linux内核源码

    深入理解和分析Linux内核源码对于任何想要成为高级系统程序员或Linux开发者来说都是至关重要的。这份"Linux内核完全注释及linux内核源码"资料提供了宝贵的教育资源,帮助读者理解这个复杂的开源项目的内部运作机制。...

    linux2.6内核源码

    1. **目录结构**:Linux内核源码通常按照功能划分目录,如`arch`包含不同架构的代码,`drivers`包含各种设备驱动,`fs`处理文件系统,`include`存放头文件,`kernel`包含核心内核功能,`mm`涉及内存管理等。...

    LINUX内核源代码情景分析

    《LINUX内核源代码情景分析》这本书是深入理解Linux操作系统内核的重要参考资料。它以情境分析的方式,带领读者逐步探索Linux内核的工作原理和设计思路。以下将围绕该书内容,详细介绍Linux内核源代码的相关知识点。...

    linux内核完全注释v3.0(带目录版).pdf

    《Linux内核完全注释v3.0(带目录版)》是一本深入解析Linux内核的权威指南,特别针对版本3.0进行了全面而详尽的注解。该书对于理解Linux操作系统的核心机制和工作原理具有极大的价值,是开发者、系统管理员以及对...

    Android内核与标准Linux内核对比分析.pdf

    《Android内核与标准Linux内核对比分析》 Android系统是Google公司开发的一款基于Linux内核的开源操作系统,尤其在移动设备领域广泛应用。其系统架构包括四个主要层次:基于Linux的内核模块、运行时库与其他库、...

    Understanding_the_Linux_Kernel_3rd 深入理解LINUX内核. Linux内核注释

    《深入理解Linux内核》是Linux系统开发领域的一本经典著作,它为读者提供了深入了解Linux内核机制的机会。这本书的第三版,"Understanding_the_Linux_Kernel_3rd",全面解析了Linux 2.6版本的内核,涵盖了自第二版...

    PDF电子书《Linux内核情景分析》

    《Linux内核情景分析》是一本深入探讨Linux操作系统内核的权威著作,旨在帮助读者理解Linux内核的工作原理和机制。这本书将理论与实践相结合,通过丰富的实例和情景分析,让读者能够对复杂的内核概念有更直观的认识...

    linux内核内存管理图解

    Linux内核内存管理是操作系统设计中的关键部分,它负责有效地分配和管理系统的物理和虚拟内存。在Linux系统中,内存管理的复杂性在于它需要...仔细分析这些图像,可以深入理解Linux内核如何高效地管理和优化内存使用。

    深入理解linux内核V3(中文版)

    《深入理解Linux内核》是Linux领域的经典之作,特别是V3中文版,它详细解析了Linux 2.6内核的各个方面,对于任何想要深入了解操作系统底层运作机制的开发者或研究者来说,都是一本不可多得的参考书。这本书不仅涵盖...

    Linux内核内存管理技术分享

    Linux内核内存管理技术是一个复杂的系统,涉及到计算机体系结构、MMU、Cache、DMA、EPT、虚拟地址空间布局、伙伴系统、SLAB、用户空间地址布局、匿名页和文件页、缺页异常、反向映射、内存规整、OOM、KSM、巨型页、...

    OOM分析工具-MemoryAnalyzer.zip

    当应用程序出现Out of Memory (OOM)错误时,通常意味着系统无法分配足够的内存来执行任务,这时就需要借助专业的分析工具来查找问题的根源。MemoryAnalyzer(MAT)是IBM开发的一款强大的JVM堆内存分析工具,它能够...

    深入理解linux内核 Understanding the linux kerne

    《深入理解Linux内核》是Linux领域的一本经典著作,旨在帮助读者全面了解Linux操作系统的内核机制。这本书深入探讨了Linux内核的工作原理,涵盖了从进程管理、内存管理到I/O子系统等多个核心主题。无论是对操作系统...

    Android对Linux内核的改造及其影响

    ### Android对Linux内核的改造及其影响 #### 一、Android对Linux内核的使用 Android选择使用Linux内核作为其基础操作系统的核心部分,并对其进行了多方面的调整与优化,以适应移动设备的需求。Linux内核作为一种...

    JVM状态监控与OOM案例分析

    JVM状态监控与OOM案例分析…… 简单认识,了解

    深入理解LINUX内核(中文第三版)第八章 内存管理2

    8. **内存压力与OOM Killer**:当系统面临内存压力时,Linux内核会触发Out-of-Memory (OOM) killer,选择并终止一些进程以释放内存,防止系统崩溃。 9. **内存故障恢复**:内核还包含错误检测和处理机制,如对内存...

    linux内核知识系列:内存管理

    Linux内核内存管理是操作系统设计的核心部分,它负责有效地分配、使用和回收系统中的物理及虚拟内存。在Linux系统中,内存管理确保了程序的高效运行,防止数据损坏,并优化了系统的整体性能。本篇文章将深入探讨...

    安卓内存OOM分析

    例如,理解Linux内核的oom killer机制,它根据每个进程的oom_score来决定优先杀死哪个进程。oom_score是基于进程的内存使用情况和其他因素计算出来的,较高的值表示更容易被杀死。 此外,内核层面的优化还包括调整...

    android对linux内核的改造及影响

    ### Android对Linux内核的改造及其影响 #### 一、Android对Linux内核的使用 Android基于Linux内核进行开发,但为了适应移动设备的需求,对其进行了必要的优化和改造。Linux作为一个通用的操作系统内核,在应用于以...

    深入理解LINUX内核(中文第三版)第八章 内存管理1

    《深入理解Linux内核》是Linux开发者和爱好者的重要参考资料,其中第八章专注于内存管理这一核心主题。内存管理在操作系统中起着至关重要的作用,它决定了系统如何高效地分配和使用有限的内存资源,以满足各个进程的...

Global site tag (gtag.js) - Google Analytics