Linux -- 内存控制之oom killer机制及代码分析

近期,线上一些内存占用比較敏感的应用。在訪问峰值的时候,偶尔会被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):

#define block (1024L*1024L*MB)
#define MB 64L
    unsigned long total = 0L;
    for(;;) {
        // malloc big block memory and ZERO it !!
        char* mm = (char*) malloc(block);
        usleep(100000);
        if (NULL == mm)
            continue;
        bzero(mm,block);
        total += MB;
        fprintf(stdout,"alloc %lum mem\n",total);
    }   

这里有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 看到:

Apr 18 16:56:16 v125000100.bja kernel: : [22254383.898423] Out of memory: Kill process 24894 (big_mm) score 277 or sacrifice child
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
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
Apr 18 16:56:18 v125000100.bja kernel: : [22254386.738947] big_mm cpuset=/ mems_allowed=0
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
Apr 18 16:56:18 v125000100.bja kernel: : [22254386.738952] Call Trace:
Apr 18 16:56:18 v125000100.bja kernel: : [22254386.738961]  [<ffffffff810c35e1>] ? cpuset_print_task_mems_allowed+0x91/0xb0
Apr 18 16:56:18 v125000100.bja kernel: : [22254386.738968]  [<ffffffff81114d70>] ? dump_header+0x90/0x1b0
Apr 18 16:56:18 v125000100.bja kernel: : [22254386.738973]  [<ffffffff810e1b2e>] ? __delayacct_freepages_end+0x2e/0x30
Apr 18 16:56:18 v125000100.bja kernel: : [22254386.738979]  [<ffffffff81213ffc>] ? security_real_capable_noaudit+0x3c/0x70
Apr 18 16:56:18 v125000100.bja kernel: : [22254386.738982]  [<ffffffff811151fa>] ?

oom_kill_process+0x8a/0x2c0
Apr 18 16:56:18 v125000100.bja kernel: : [22254386.738985]  [<ffffffff81115131>] ?

select_bad_process+0xe1/0x120
Apr 18 16:56:18 v125000100.bja kernel: : [22254386.738989]  [<ffffffff81115650>] ?

out_of_memory+0x220/0x3c0
Apr 18 16:56:18 v125000100.bja kernel: : [22254386.738995]  [<ffffffff81125929>] ? __alloc_pages_nodemask+0x899/0x930
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:

Apr 18 16:56:18 v125000100.bja kernel: : [22254386.758297] [ pid ]   uid  tgid total_vm      rss cpu oom_adj oom_score_adj name
Apr 18 16:56:18 v125000100.bja kernel: : [22254386.758311] [  399]     0   399     2709      133   2     -17         -1000 udevd
Apr 18 16:56:18 v125000100.bja kernel: : [22254386.758314] [  810]     0   810     2847       43   0       0             0 svscanboot
Apr 18 16:56:18 v125000100.bja kernel: : [22254386.758317] [  824]     0   824     1039       21   0       0             0 svscan
Apr 18 16:56:18 v125000100.bja kernel: : [22254386.758320] [  825]     0   825      993       17   1       0             0 readproctitle
Apr 18 16:56:18 v125000100.bja kernel: : [22254386.758322] [  826]     0   826      996       16   0       0             0 supervise
Apr 18 16:56:18 v125000100.bja kernel: : [22254386.758325] [  827]     0   827      996       17   0       0             0 supervise
Apr 18 16:56:18 v125000100.bja kernel: : [22254386.758327] [  828]     0   828      996       16   0       0             0 supervise
Apr 18 16:56:18 v125000100.bja kernel: : [22254386.758330] [  829]     0   829      996       17   2       0             0 supervise
Apr 18 16:56:18 v125000100.bja kernel: : [22254386.758333] [  830]     0   830     6471      152   0       0             0 run
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

/**
 * out_of_memory - kill the "best" process when we run out of memory
 */
void out_of_memory(struct zonelist *zonelist, gfp_t gfp_mask,
        int order, nodemask_t *nodemask, bool force_kill)
{
    // 等待notifier调用链返回,假设有内存了则返回
    blocking_notifier_call_chain(&oom_notify_list, 0, &freed);
    if (freed > 0)
        return;

    // 假设进程即将退出,则表明可能会有内存能够使用了,返回
    if (fatal_signal_pending(current) || current->flags & PF_EXITING) {
        set_thread_flag(TIF_MEMDIE);
        return;
    }

    // 假设设置了sysctl的panic_on_oom,则内核直接panic
    check_panic_on_oom(constraint, gfp_mask, order, mpol_mask);

    // 假设设置了oom_kill_allocating_task
    // 则杀死正在申请内存的process
    if (sysctl_oom_kill_allocating_task && current->mm &&
        !oom_unkillable_task(current, NULL, nodemask) &&
        current->signal->oom_score_adj != OOM_SCORE_ADJ_MIN) {
        get_task_struct(current);
        oom_kill_process(current, gfp_mask, order, 0, totalpages, NULL,
                 nodemask,
                 "Out of memory (oom_kill_allocating_task)");
        goto out;
    }

    // 用select_bad_process()选择badness指
    // 数(oom_score)最高的进程
    p = select_bad_process(&points, totalpages, mpol_mask, force_kill);

    if (!p) {
        dump_header(NULL, gfp_mask, order, NULL, mpol_mask);
        panic("Out of memory and no killable processes...\n");
    }
    if (p != (void *)-1UL) {
        // 查看child process, 是否是要被killed,则直接影响当前这个parent进程
        oom_kill_process(p, gfp_mask, order, points, totalpages, NULL,
                 nodemask, "Out of memory");
        killed = 1;
    }
out:

    if (killed)
        schedule_timeout_killable(1);
}

select_bad_process() 调用oom_badness计算权值:

/**
 * oom_badness - heuristic function to determine which candidate task to kill
 *
 */
unsigned long oom_badness(struct task_struct *p, struct mem_cgroup *memcg,
              const nodemask_t *nodemask, unsigned long totalpages)
{
    long points;
    long adj;

    // 内部推断是否是pid为1的initd进程,是否是kthread内核进程。是否是其它cgroup。假设是则跳过
    if (oom_unkillable_task(p, memcg, nodemask))
        return 0;

    p = find_lock_task_mm(p);
    if (!p)
        return 0;

    // 获得/proc/[pid]/oom_adj权值,假设是OOM_SCORE_ADJ_MIN则返回
    adj = (long)p->signal->oom_score_adj;
    if (adj == OOM_SCORE_ADJ_MIN) {
        task_unlock(p);
        return 0;
    }

    // 获得进程RSS和swap内存占用
    points = get_mm_rss(p->mm) + p->mm->nr_ptes +
         get_mm_counter(p->mm, MM_SWAPENTS);
    task_unlock(p);

    // 计算过程例如以下。【计算逻辑比較简单,不赘述了】
    if (has_capability_noaudit(p, CAP_SYS_ADMIN))
        adj -= 30;
    adj *= totalpages / 1000;
    points += adj;

    return points > 0 ?

points : 1;

}

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

时间: 2024-10-24 14:06:36

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

《linux 内核完全剖析》 keyboard.S 部分代码分析(key_map)

keyboard.S 部分代码分析(key_map) keyboard中间有这么一段,我一开始没看明白,究竟啥意思 key_map: .byte 0,27 .ascii "1234567890-=" .byte 127,9 .ascii "qwertyuiop[]" .byte 13,0 .ascii "asdfghjkl;'" .byte '`,0 .ascii "\\zxcvbnm,./" .byte 0,'*,0,32

Linux OOM killer 机制

Linux中的Out Of Memory(OOM) Killer功能是一种确保系统内存足够的最终手段,可以在耗尽系统内存或交换区后,按某种算法判断占用系统最多资源的进程,向进程发送信号,强制终止该进程. 简单来说该机制会监控那些占用内存过大,尤其是瞬间很快消耗大量内存的进程,为了防止内存耗尽而内核会把该进程杀掉. 这个功能即使在无法释放内存的情况下,也能够重复进行确保内存的处理过程,防止系统停滞,还可以找出过度消耗内存的进程. 典型的情况是:某天一台机器突然ssh远程登录不了,但能ping通,说

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

一.前言 关注ARM平台上timer driver(clocksource chip driver和clockevent chip driver)的驱动工程师应该会注意到timer硬件的演化过程.在单核时代,各个SOC vendor厂商购买ARM core的IP,然后自己设计SOC上的peripherals,这里面就包括了timer的硬件.由于没有统一的标准,各个厂商的设计各不相同,这给驱动工程师带来了工作量.然而,如果仅仅是工作量的话就还好,实际上,不仅仅如此.linux的时间子系统要求硬件t

Linux下OOM Killer机制详解

http://www.cnblogs.com/ylqmf/archive/2012/11/05/2754795.html http://wuquan-1230.blog.163.com/blog/static/298111532011112851419497/ http://www.linuxidc.com/Linux/2013-09/90092.htm http://www.cnblogs.com/GoodGoodWorkDayDayUp/p/3473348.html http://www.t

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

inux系统中查看内存状态一般都会用到free linux的free命令中,cached和buffers的区别 Free Mem:表示物理内存统计 -/+ buffers/cached:表示物理内存的缓存统计 Swap:表示硬盘上交换分区的使用情况 系统的总物理内存:8098060 8Gb,但系统当前真正可用的内存并不是第一行free 标记的6054972Kb,它仅代表未被分配的内存. 我们使用total1.used1.free1.used2.free2 等名称来代表上面统计数据的各值,1.2

Android:内存控制及OOM处理

  1. OOM(内存溢出)和Memory Leak(内存泄露)有什么关系? OOM可能是因为Memory Leak,也可能是你的应用本身就比较耗内存(比如图片浏览型的).所以,出现OOM不一定是Memory Leak. 同样,Memory Leak也不一定就会导致OOM,如果泄露的速度很慢,可能还没用完可用内存应用就被重启了,那就不会OOM咯.当然了,有bug解决了最好. 2. 什么是shallow heap与retained heap? shallow heap:你自身占了多少内存,比如你有

DataNode与NameNode交互机制相关代码分析

HDFS Federation是为解决HDFS单点故障而提出的NameNode水平扩展方案,该方案允许HDFS创建多个Namespace以提高集群的扩展性和隔离性.在Federation中新增了block-pool的概念,block-pool就是属于单个Namespace的一组block,每个DataNode为所有的block-pool存储block,可以理解block-pool是一个重新将block划分的逻辑概念,同一个DataNode中可以存储属于多个block-pool的多个block.所

fshc之请求仲裁机制的代码分析

1 always@(posedge spi_clk or negedge spiclk_rst_n) 2 begin 3 if(~spiclk_rst_n) 4 arbiter2cache_ack_r <=1'b0; 5 else if(cache_req_sclk && flash_idle && ~atom_op_en_sync2 && ~arbiter2mcu_ack_r) 6 arbiter2cache_ack_r <=1'b1; 7 e

Cgroup - Linux 内存资源管理

Hi ,我是 Zorro .这是我的微博地址,我会不定期在这里更新文章,如果你有兴趣,可以来关注我呦. 另外,我的其他联系方式: Email: [email protected] QQ: 30007147 本文PDF 在聊 cgroup 的内存限制之前,我们有必要先来讲解一下: Linux 内存管理基础知识 free 命令 无论从任何角度看, Linux 的内存管理都是一坨麻烦的事情,当然我们也可以用一堆.一片.一块.一筐来形容这个事情,但是毫无疑问,用一坨来形容它简直恰当无比.在理解它之前,我