近期,线上一些内存占用比較敏感的应用。在訪问峰值的时候,偶尔会被kill掉,导致服务重新启动。发现是Linux的out-of-memory kiiler的机制触发的。

http://linux-mm.org/OOM_Killer

oom kiiler会在内存紧张的时候,会依次kill内存占用较高的进程,发送Signal 15(SIGTERM)。并在/var/log/message中进行记录。里面会记录一些如pid,process name。cpu mask,trace等信息,通过监控能够发现类似问题。

今天特意分析了一下oom killer相关的选择机制。挖了一下代码。感觉该机制简单粗暴。只是效果还是挺明显的。给大家分享出来。

  • oom killer初探

        一个简单分配heap memroy的代码片段(big_mm.c):
  1. #define block (1024L*1024L*MB)
  2. #define MB 64L
  3. unsigned long total = 0L;
  4. for(;;) {
  5. // malloc big block memory and ZERO it !!
  6. char* mm = (char*) malloc(block);
  7. usleep(100000);
  8. if (NULL == mm)
  9. continue;
  10. bzero(mm,block);
  11. total += MB;
  12. fprintf(stdout,"alloc %lum mem\n",total);
  13. }

这里有2个地方须要注意:

        
        1、malloc是分配虚拟地址空间,假设不memset或者bzero,那么就不会触发physical allocate,不会映射物理地址,所以这里用bzero填充
        2、每次申请的block大小比較有讲究。Linux内核分为LowMemroy和HighMemroy,LowMemory为内存紧张资源,LowMemroy有个阀值,通过free -lm和

/proc/sys/vm/lowmem_reserve_ratio来查看当前low大小和阀值low大小。低于阀值时候才会触发oom killer,所以这里block的分配小雨默认的256M,否则假设每次申请512M(大于128M),malloc可能会被底层的brk这个syscall堵塞住,内核触发page cache回写或slab回收。

 測试:

       gcc big_mm.c -o big_mm ; ./big_mm & ./big_mm & ./big_mm &

       (同一时候启动多个big_mm进程争抢内存)       

       启动后,部分big_mm被killed。在/var/log/message下tail -n 1000 | grep -i oom 看到:

  1. Apr 18 16:56:16 v125000100.bja kernel: : [22254383.898423] Out of memory: Kill process 24894 (big_mm) score 277 or sacrifice child
  2. Apr 18 16:56:16 v125000100.bja kernel: : [22254383.899708] Killed process 24894, UID 55120, (big_mm) total-vm:2301932kB, anon-rss:2228452kB, file-rss:24kB
  3. Apr 18 16:56:18 v125000100.bja kernel: : [22254386.738942] big_mm invoked oom-killer: gfp_mask=0x280da, order=0, oom_adj=0, oom_score_adj=0
  4. Apr 18 16:56:18 v125000100.bja kernel: : [22254386.738947] big_mm cpuset=/ mems_allowed=0
  5. Apr 18 16:56:18 v125000100.bja kernel: : [22254386.738950] Pid: 24893, comm: big_mm Not tainted 2.6.32-220.23.2.ali878.el6.x86_64 #1
  6. Apr 18 16:56:18 v125000100.bja kernel: : [22254386.738952] Call Trace:
  7. Apr 18 16:56:18 v125000100.bja kernel: : [22254386.738961] [<ffffffff810c35e1>] ? cpuset_print_task_mems_allowed+0x91/0xb0
  8. Apr 18 16:56:18 v125000100.bja kernel: : [22254386.738968] [<ffffffff81114d70>] ? dump_header+0x90/0x1b0
  9. Apr 18 16:56:18 v125000100.bja kernel: : [22254386.738973] [<ffffffff810e1b2e>] ? __delayacct_freepages_end+0x2e/0x30
  10. Apr 18 16:56:18 v125000100.bja kernel: : [22254386.738979] [<ffffffff81213ffc>] ? security_real_capable_noaudit+0x3c/0x70
  11. Apr 18 16:56:18 v125000100.bja kernel: : [22254386.738982] [<ffffffff811151fa>] ?
  12.  
  13. oom_kill_process+0x8a/0x2c0
  14. Apr 18 16:56:18 v125000100.bja kernel: : [22254386.738985] [<ffffffff81115131>] ?
  15.  
  16. select_bad_process+0xe1/0x120
  17. Apr 18 16:56:18 v125000100.bja kernel: : [22254386.738989] [<ffffffff81115650>] ?
  18.  
  19. out_of_memory+0x220/0x3c0
  20. Apr 18 16:56:18 v125000100.bja kernel: : [22254386.738995] [<ffffffff81125929>] ? __alloc_pages_nodemask+0x899/0x930
  21. Apr 18 16:56:18 v125000100.bja kernel: : [22254386.739001] [<ffffffff81159c6a>] ? alloc_pages_vma+0x9a/0x150

通过标红的部分能够看到big_mm占用了2301932K,anon-rss所有是mmap分配的大内存块。后面红色的CallTrace标识出来kernel oom-killer的stack,后面我们会针对该call trace分析一下oom killer的代码。

  • oom killer机制分析

我们触发了oom killer的机制。那么oom killer是计算出选择哪个进程kill呢?我们先来看一下kernel提供给用户态的/proc下的一些參数:

/proc/[pid]/oom_adj ,该pid进程被oom killer杀掉的权重,介于 [-17,15]之间,越高的权重,意味着更可能被oom killer选中,-17表示禁止被kill掉。

/proc/[pid]/oom_score,当前该pid进程的被kill的分数。越高的分数意味着越可能被kill,这个数值是依据oom_adj运算后的结果,是oom_killer的主要參考。

sysctl 下有2个可配置选项:

vm.panic_on_oom = 0         #内存不够时内核是否直接panic

                vm.oom_kill_allocating_task = 1        #oom-killer是否选择当前正在申请内存的进程进行kill

触发oom killer时/var/log/message打印了进程的score:

  1. Apr 18 16:56:18 v125000100.bja kernel: : [22254386.758297] [ pid ] uid tgid total_vm rss cpu oom_adj oom_score_adj name
  2. Apr 18 16:56:18 v125000100.bja kernel: : [22254386.758311] [ 399] 0 399 2709 133 2 -17 -1000 udevd
  3. Apr 18 16:56:18 v125000100.bja kernel: : [22254386.758314] [ 810] 0 810 2847 43 0 0 0 svscanboot
  4. Apr 18 16:56:18 v125000100.bja kernel: : [22254386.758317] [ 824] 0 824 1039 21 0 0 0 svscan
  5. Apr 18 16:56:18 v125000100.bja kernel: : [22254386.758320] [ 825] 0 825 993 17 1 0 0 readproctitle
  6. Apr 18 16:56:18 v125000100.bja kernel: : [22254386.758322] [ 826] 0 826 996 16 0 0 0 supervise
  7. Apr 18 16:56:18 v125000100.bja kernel: : [22254386.758325] [ 827] 0 827 996 17 0 0 0 supervise
  8. Apr 18 16:56:18 v125000100.bja kernel: : [22254386.758327] [ 828] 0 828 996 16 0 0 0 supervise
  9. Apr 18 16:56:18 v125000100.bja kernel: : [22254386.758330] [ 829] 0 829 996 17 2 0 0 supervise
  10. Apr 18 16:56:18 v125000100.bja kernel: : [22254386.758333] [ 830] 0 830 6471 152 0 0 0 run
  11. Apr 18 16:56:18 v125000100.bja kernel: : [22254386.758335] [ 831] 99 831 1032 21 0 0 0 multilog

所以。假设想改动被oom killer选中的概率,改动上树參数就可以。

  • oom killer 代码分析

上面已经给出了相应策略,以下剖析一下kernel相应的代码。有个清晰认识。

代码选择的是kernel 3.0.12的代码,源代码文件 mm/oom_kill.c。首先看一下call trace调用关系:

__alloc_pages_nodemask分配内存 -> 发现内存不足(或低于low memory)out_of_memory -> 选中一个得分最高的processor进行select_bad_process -> kill

  1. /**
  2. * out_of_memory - kill the "best" process when we run out of memory
  3. */
  4. void out_of_memory(struct zonelist *zonelist, gfp_t gfp_mask,
  5. int order, nodemask_t *nodemask, bool force_kill)
  6. {
  7. // 等待notifier调用链返回,假设有内存了则返回
  8. blocking_notifier_call_chain(&oom_notify_list, 0, &freed);
  9. if (freed > 0)
  10. return;
  11.  
  12. // 假设进程即将退出,则表明可能会有内存能够使用了,返回
  13. if (fatal_signal_pending(current) || current->flags & PF_EXITING) {
  14. set_thread_flag(TIF_MEMDIE);
  15. return;
  16. }
  17.  
  18. // 假设设置了sysctl的panic_on_oom,则内核直接panic
  19. check_panic_on_oom(constraint, gfp_mask, order, mpol_mask);
  20.  
  21. // 假设设置了oom_kill_allocating_task
  22. // 则杀死正在申请内存的process
  23. if (sysctl_oom_kill_allocating_task && current->mm &&
  24. !oom_unkillable_task(current, NULL, nodemask) &&
  25. current->signal->oom_score_adj != OOM_SCORE_ADJ_MIN) {
  26. get_task_struct(current);
  27. oom_kill_process(current, gfp_mask, order, 0, totalpages, NULL,
  28. nodemask,
  29. "Out of memory (oom_kill_allocating_task)");
  30. goto out;
  31. }
  32.  
  33. // 用select_bad_process()选择badness指
  34. // 数(oom_score)最高的进程
  35. p = select_bad_process(&points, totalpages, mpol_mask, force_kill);
  36.  
  37. if (!p) {
  38. dump_header(NULL, gfp_mask, order, NULL, mpol_mask);
  39. panic("Out of memory and no killable processes...\n");
  40. }
  41. if (p != (void *)-1UL) {
  42. // 查看child process, 是否是要被killed,则直接影响当前这个parent进程
  43. oom_kill_process(p, gfp_mask, order, points, totalpages, NULL,
  44. nodemask, "Out of memory");
  45. killed = 1;
  46. }
  47. out:
  48.  
  49. if (killed)
  50. schedule_timeout_killable(1);
  51. }

select_bad_process() 调用oom_badness计算权值:

  1. /**
  2. * oom_badness - heuristic function to determine which candidate task to kill
  3. *
  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.  
  11. // 内部推断是否是pid为1的initd进程,是否是kthread内核进程。是否是其它cgroup。假设是则跳过
  12. if (oom_unkillable_task(p, memcg, nodemask))
  13. return 0;
  14.  
  15. p = find_lock_task_mm(p);
  16. if (!p)
  17. return 0;
  18.  
  19. // 获得/proc/[pid]/oom_adj权值,假设是OOM_SCORE_ADJ_MIN则返回
  20. adj = (long)p->signal->oom_score_adj;
  21. if (adj == OOM_SCORE_ADJ_MIN) {
  22. task_unlock(p);
  23. return 0;
  24. }
  25.  
  26. // 获得进程RSS和swap内存占用
  27. points = get_mm_rss(p->mm) + p->mm->nr_ptes +
  28. get_mm_counter(p->mm, MM_SWAPENTS);
  29. task_unlock(p);
  30.  
  31. // 计算过程例如以下。【计算逻辑比較简单,不赘述了】
  32. if (has_capability_noaudit(p, CAP_SYS_ADMIN))
  33. adj -= 30;
  34. adj *= totalpages / 1000;
  35. points += adj;
  36.  
  37. return points > 0 ?
  38.  
  39. points : 1;
  40.  
  41. }

总结,大家能够依据上述策略调整oom killer,禁止或者给oom_adj最小或偏小的值,也能够通过sysctl调节oom killer行为!


















Linux -- 内存控制之oom killer机制及代码分析的更多相关文章

  1. Linux内核OOM killer机制

    程序运行了一段时间,有个进程挂掉了,正常情况下进程不会主动挂掉,简单分析后认为可能是运行时某段时间内存占用过大,系统内存不足导致触发了Linux操作系统OOM killer机制,将运行中的进程杀掉了. ...

  2. Linux内存管理 (21)OOM

    专题:Linux内存管理专题 关键词:OOM.oom_adj.oom_score.badness. Linux内核为了提高内存的使用效率采用过度分配内存(over-commit memory)的办法, ...

  3. Linux系统OOM killer机制详解

    介绍: Linux下面有个特性叫OOM killer(Out Of Memory killer),会在系统内存耗尽的情况下出现,选择性的干掉一些进程以求释放一些内存.广大从事Linux方面的IT农民工 ...

  4. linux如何查看进程OOM killer

    基本概念: Linux 内核有个机制叫OOM killer(Out-Of-Memory killer),该机制会监控那些占用内存过大,尤其是瞬间很快消耗大量内存的进程,为了防止内存耗尽而内核会把该进程 ...

  5. Linux时间子系统(十七) ARM generic timer驱动代码分析

    一.前言 关注ARM平台上timer driver(clocksource chip driver和clockevent chip driver)的驱动工程师应该会注意到timer硬件的演化过程.在单 ...

  6. Spring Cloud 请求重试机制核心代码分析

    场景 发布微服务的操作一般都是打完新代码的包,kill掉在跑的应用,替换新的包,启动. spring cloud 中使用eureka为注册中心,它是允许服务列表数据的延迟性的,就是说即使应用已经不在服 ...

  7. Linux下OOM Killer机制详解

    http://www.cnblogs.com/ylqmf/archive/2012/11/05/2754795.html http://wuquan-1230.blog.163.com/blog/st ...

  8. linux内存管理及手动释放机制

    inux系统中查看内存状态一般都会用到free linux的free命令中,cached和buffers的区别 Free Mem:表示物理内存统计 -/+ buffers/cached:表示物理内存的 ...

  9. Android:内存控制及OOM处理

      1. OOM(内存溢出)和Memory Leak(内存泄露)有什么关系? OOM可能是因为Memory Leak,也可能是你的应用本身就比较耗内存(比如图片浏览型的).所以,出现OOM不一定是Me ...

随机推荐

  1. NetBeans将java项目编译成jar包

    1.找到file选项下的build.xml.

  2. Pycharm:debug调试时使用参数

    一种操作方法: 文章链接:MAC下使用Pycharm,debug调试时怎样带参数 今天在网上找了一个例子敲代码,因为我使用的是PyCharm,例子运行时需要带参数,开始不知道怎么带参数,网上搜了大半天 ...

  3. Ubuntu下获取内核源码

    查看当前系统使用的内核版本: apt-cache search linux-source 输出如下: linux-source - Linux kernel source with Ubuntu pa ...

  4. JS 获得节点

    var ele = ev.parentNode; var elem_child = ele.childNodes; in elem_child) { //遍历子元素数组 if (elem_child[ ...

  5. CAD设置当前显示的光标(com接口VB语言)

    主要用到函数说明: MxDrawXCustomFunction::Mx_SetCursor 设置当前显示的光标,光标可以从cur文件加载,详细说明如下: 参数 说明 CString sCursorFi ...

  6. LINUX -- pthread_detach()与pthread_join()

    pthread_detach()即主线程与子线程分离,子线程结束后,资源自动回收. int pthread_join(pthread_t tid, void **thread_return); {su ...

  7. 浅谈 extern "C"

    今天上课实在无聊,就看了看 extern "C" 的作用,看了以后对它有了一点点理解,在这里给大家分享一下(本菜鸡水平有限,如若有说得不对的地方,还望大家指出). extern 关 ...

  8. 爬虫文件存储:txt文档,json文件,csv文件

    5.1 文件存储 文件存储形式可以是多种多样的,比如可以保存成 TXT 纯文本形式,也可以保存为 Json 格式.CSV 格式等,本节我们来了解下文本文件的存储方式. 5.1.1 TXT文本存储 将数 ...

  9. 02. 爬取get请求的页面数据

    目录 02. 爬取get请求的页面数据 一.urllib库 二.由易到难的爬虫程序: 02. 爬取get请求的页面数据 一.urllib库 urllib是Python自带的一个用于爬虫的库,其主要作用 ...

  10. 第七节:numpy之矩阵及特殊矩阵的创建