神刀安全网

谈谈OOM

作为一个不合格的开发人员多多少少都被 OOM(Out of memory) x 过,只是一般大家对于为什么被选中,可能没太考究。

1) OOM 的起因

没有买卖就没有杀害,这个结论在操作系统也是成立的。如果我们不在分配内存的时候严格控制,那么也就没 OOM 机制什么事情了。但为了内存的最大化使用,一般都愿意来冒这个风险。如何控制内存分配的参数,也就是之前有聊过的一个系统参数谈谈 Overcommit 。

简单来说之所以会出现 OOM, 就是已分配的虚拟内存大于物理内存和Swap分区大小,导致需要内存无法分配。如果 overcommit = 2, 在申请虚拟内存时,如果超过限制的内存比例 + Swap 空间会直接返回失败,只要分配虚拟内存不超过物理内存,也就不会有 OOM。

2) 进程选择

既然无法分配内存,就有两种下面的两种选择:

  1. 向申请内存报告申请内存失败
  2. 选择一个杀掉其他的进程来释放内存

内核 2.4 版本之前是第一种做法(走在路上听别人说的), 后面的内核版本才采用第二种。我们这里要来看的是第二种,原因很简单,因为第一种做法没什么好看的…

下面的代码基于内核 2.6.32, 代码在 arch/不同型号/mm/fault.cmm/oom_kill.c :

我们分配物理内存的时候是通过缺页中断来实现的,然后会调用 do_page_fault ,如果内存不足就会通过 mm_fault_error 来处理,调用链如下:

out_of_memory->pagefault_out_of_memory->__out_of_memory ->select_bad_process->badness 

select_bad_process 调用 badness 来计算每个进程的得分(范围 0-1000),然后干掉 score 最高的进程。每个进程的 score 我们通过 /proc/pid/oom_score 来查看。我们下面来看这个 score 是如何计算,主要是下面几个纬度:

  • 子进程内存消耗,越多越容易被选中
  • CPU 密集型以及老进程,比刚启动的进程更不容易被选中
  • root 启动的进程更不容易被选中
  • 用户通过控制 oom_adj 来控制进程选中优先级(范围是-17到15)
unsigned long badness(struct task_struct *p, unsigned long uptime) {     ...     // 这个 oom_adj 值是从 /proc/pid/oom_adj 获取     // 用户可以通过控制 oom_adj 来控制 score     int oom_adj = p->signal->oom_adj;     ...      // 系统总内存大小作为基础分数     points = mm->total_vm;      if (p->flags & PF_OOM_ORIGIN)         return ULONG_MAX;      // 如果 fork 的进程子进程也太多子进程被选到的概率也比较大。     list_for_each_entry(child, &p->children, sibling) {         task_lock(child);         if (child->mm != mm && child->mm)             points += child->mm->total_vm/2 + 1;         task_unlock(child);     }      thread_group_cputime(p, &task_time);     // 进程用户空间消耗的 cpu 分片数     utime = cputime_to_jiffies(task_time.utime);     // 进程系统消耗的 cpu 分片数     stime = cputime_to_jiffies(task_time.stime);     cpu_time = (utime + stime) >> (SHIFT_HZ + 3);      // 计算启动时间     if (uptime >= p->start_time.tv_sec)         run_time = (uptime - p->start_time.tv_sec) >> 10;     else         run_time = 0;      // 消耗 CPU 越多的进程,降低 score     if (cpu_time)         points /= int_sqrt(cpu_time);     // 根据启动时间降低 score, 所以最新启动的进程最可能被杀     if (run_time)         points /= int_sqrt(int_sqrt(run_time));      // 降低 root 用户启动的进程的 score     if (has_capability_noaudit(p, CAP_SYS_ADMIN) ||         has_capability_noaudit(p, CAP_SYS_RESOURCE))         points /= 4;      if (!has_intersects_mems_allowed(p))         points /= 8;       if (oom_adj) {         if (oom_adj > 0) {             if (!points)                 points = 1;             points <<= oom_adj;         } else             points >>= -(oom_adj);     }     return points; } 

3) 总结

从整体来看,占用内存越大,非CPU消耗刑,非 Root 启动以及新启动的进程更加容易被选中。而用户可以通过调整 /proc/$pid/oom_adj 来调整进程的优先级。

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » 谈谈OOM

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
分享按钮