博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
菜鸟的 linux 学习笔记 -- OOM
阅读量:6825 次
发布时间:2019-06-26

本文共 7439 字,大约阅读时间需要 24 分钟。

缘起

作为一个菜鸟,扒代码是提升自己内功的必修课,因此,本弱菜也没事扒一把代码学习。今儿,扒的是 openssh 的代码中的 sshd 部分的代码。这部分的代码不难理解,但是其中有个 oom_adjust_setup 的函数引起了俺的兴趣。(openssh 6.3p1 openbsd-compat/port-linux.c:262) 想起之前也见过 syslog 里面出现 oom-killer 的记录,但是究竟这背后意味着什么?linux 是怎么选择被 kill 的进程的捏?还有待研究一番。

从 oom_adjust_setup 开始

好,我们来看看这个函数究竟想干啥?诚如这个函数上面的注释所说的一样 Tell the kernel's out-of-memory killer to avoid sshd.  也就是说经过这个函数一番捣鼓,偶们的 sshd 进程就死活不会被 linux 系统给 kill 掉啦,很好很强大。那,让我们看看它是怎么做到的。(// 后面的注释是俺加的)

/* * Tell the kernel's out-of-memory killer to avoid sshd. * Returns the previous oom_adj value or zero. */voidoom_adjust_setup(void){        int i, value;        FILE *fp;        debug3("%s", __func__);         for (i = 0; oom_adjust[i].path != NULL; i++) {                oom_adj_path = oom_adjust[i].path;                value = oom_adjust[i].value;                if ((fp = fopen(oom_adj_path, "r+")) != NULL) {                        // read value from                        //     "/proc/self/oom_score_adj" (kernels >= 2.6.36)                        // or  "/proc/self/oom_adj" (kernels <= 2.6.35)                        // save it to variable oom_adj_save                        if (fscanf(fp, "%d", &oom_adj_save) != 1)                                verbose("error reading %s: %s", oom_adj_path,                                    strerror(errno));                        else {                                // the same as fseek(stream, 0L, SEEK_SET)                                rewind(fp);                                // rewrite                                if (fprintf(fp, "%d\n", value) <= 0)                                        verbose("error writing %s: %s",                                           oom_adj_path, strerror(errno));                                else                                        verbose("Set %s from %d to %d",                                           oom_adj_path, oom_adj_save, value);                        }                        fclose(fp);                        return;                }        }        oom_adj_path = NULL;}

其实呢就是修改两个文件(/proc/self/oom_score_adj 和 /proc/self/oom_adj)的值,下面就是上面这个函数中用到的结构体变量的定义

/* * The magic "don't kill me" values, old and new, as documented in eg: * http://lxr.linux.no/#linux+v2.6.32/Documentation/filesystems/proc.txt * http://lxr.linux.no/#linux+v2.6.36/Documentation/filesystems/proc.txt */static int oom_adj_save = INT_MIN;static char *oom_adj_path = NULL;struct {        char *path;        int value;} oom_adjust[] = {        {"/proc/self/oom_score_adj", -1000},    /* kernels >= 2.6.36 */        {"/proc/self/oom_adj", -17},            /* kernels <= 2.6.35 */        {NULL, 0},};

咦,这个结构体中的两个数字(-1000 和 -17)是怎么来的,代码中附上的注释中已经解释得很清楚了(就是那两个 url 链接)。大意就是说捏,通过不同的数字控制自己本身被 linux oom-kill 的优先级,代码中的这两个数字就是别杀我的意思。OK,看来到这里就差不多可以结束了,但是还是有问题 linux 到底是怎么决定去 kill 哪个进程来释放内存的捏?咱继续往下扒-

刨根问底

那么打开 linux kernel 代码开始扒(这里用的是linux-3.12.5 的代码)。内存相关的代码都在 linux-3.12.5/mm/ 目录下,其中有一个叫做 oom_kill.c 就是 kill 掉消耗太多内存的幕后凶手。这里面干活的是 oom_kill_process 这个函数。这个函数的主要流程如下

1)使用 oom_badness 函数去计算每个进程的分数, 取出分数值最高的进程

2)把上面分数最高的进程 kill 掉(do_send_sig_info(SIGKILL, SEND_SIG_FORCED, victim, true);)

那么接着往里挖,咱来看看 oom_badness 是怎么计算分数的(// 后面是俺的注释)

unsigned long oom_badness(struct task_struct *p, struct mem_cgroup *memcg,                          const nodemask_t *nodemask, unsigned long totalpages){        long points;        long adj;        // some process as follows can not be killed        // 1) init process        // 2) kernel thread        // 3) not the member of oom cgroup        // 4) TODO: I'm a newbie, so still don't know which type this process is        // ( use functiong 'has_intersects_mems_allowed' to judge)        if (oom_unkillable_task(p, memcg, nodemask))                return 0;        p = find_lock_task_mm(p);        if (!p)                return 0;        // this is the value of we set in /proc/self/oom_score_adj        adj = (long)p->signal->oom_score_adj;        // Aha, OOM_SCORE_ADJ_MIN this is the magic number (-1000) we told in the sshd code        if (adj == OOM_SCORE_ADJ_MIN) {                task_unlock(p);                return 0;        }        /*         * The baseline for the badness score is the proportion of RAM that each         * task's rss, pagetable and swap space use.         */        points = get_mm_rss(p->mm) + p->mm->nr_ptes +                 get_mm_counter(p->mm, MM_SWAPENTS);        task_unlock(p);        /*         * Root processes get 3% bonus, just like the __vm_enough_memory()         * implementation used by LSMs.         */        // why 3% ? 'Casuse it will be divided by 1000 next        // root process may be import so lower its priority        if (has_capability_noaudit(p, CAP_SYS_ADMIN))                adj -= 30;        /* Normalize to oom_score_adj units */        adj *= totalpages / 1000;        points += adj;        /*         * Never return 0 for an eligible task regardless of the root bonus and         * oom_score_adj (oom_score_adj can't be OOM_SCORE_ADJ_MIN here).         */        return points > 0 ? points : 1;}

从上面的代码可以看出,计算进程得分的依据就是该进程是用了多少的 rss 啦用了多少页啦 swap 空间的使用情况(这些是加分项),如果是 root 进程就稍微降降分数,最后加上偶们自定义的 oom_score_adj 就大功告成啦。

那么知道了 kernel 是这么干的之后偶们还能干啥捏?当然就是动手实践啦。

注1: linux 代码是压缩的,本弱菜一看我擦  xz 格式咋解压捏?xz -d linux-3.12.5.tar.xz;tar -xvf linux-3.12.5.tar

Try

首先声明下咱的实验环境

ubuntu# free -m

total used free shared buffers cached
Mem: 2003 958 1045 0 207 487
-/+ buffers/cache: 263 1739
Swap: 2047 0 2047
ubuntu# cat /proc/sys/vm/overcommit_memory
0
ubuntu# uname -a
Linux ubuntu 3.5.0-23-generic #35~precise1-Ubuntu SMP Fri Jan 25 17:13:26 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
ubuntu#

在实验过程中,咱又有所发现。咱们常用的 malloc 行为和咱理解的略略有所不同。大家都知道 malloc 失败返回 NULL 指针,成功返回指向一块连续内存的起始地址。那么问题来了,我们是不是可以分配超过物理内存大小的内存空间捏?答:可以

请看下面一段代码

#include 
#include
int main (void){ int n = 0; while(1) { if(malloc(1<<20) == NULL) { printf("malloc failure after %d MiB\n", n); return 0; } printf ("got %d MiB\n", ++n); } return 0;}

 

编译后运行,你会发现它会分配远远大于物理内存大小的空间出来而不会被 oom-kill.为啥?咱们先在看另一段代码

#include 
#include
#include
int main(void){ int n = 0; char *p; while (1) { if ((p = (char *)malloc(1<<20)) == NULL) { printf("malloc failure after %d MiB\n", n); return 0; } memset (p, 0, (1<<20)); printf ("got %d MiB\n", ++n); } return 0;}

这段代码就比较符合我们的理解了,会被 oom-kill 干掉

got 3729 MiB

got 3730 MiB
zsh: killed ./malloc_use
ubuntu#

 

syslog 中也有相应的 oom 信息

Dec 16 08:13:52 ubuntu kernel: [600632.103656] Out of memory: Kill process 24642 (malloc_use) score 896 or sacrifice child

Dec 16 08:13:52 ubuntu kernel: [600632.103661] Killed process 24642 (malloc_use) total-vm:3895912kB, anon-rss:1906672kB, file-rss:128kB

 

出现这种现象的原因是为啥捏?原来使用 malloc 分配内存的时候,linux 并没有真正的我们分配的内存地址和物理内存关联上,只有在使用的时候才真正的关联上。于是,我们看到在第二段代码中一个 memset 就能引发 oom 啦。这种事情捏就叫做 memory overcommit。那么有木有办法让我们在 malloc 的时候就关联上物理内存捏?有,别忘了 linux 有一堆系统参数可以调,其中就有一个叫做 vm.overcommit_memory  的,其取值范围如下

0: Heuristic overcommit handling(使用启发式算法去控制要不要 overcommit 等,是默认值)

1: Always overcommit.(总是会在真正使用的时候才和物理内存关联上)

2:Don't overcommit(malloc 的时候就和物理内存关联上啦)

注1:关于这个参数值的说明参考 

注2: 不会调系统参数?很简单啊两种方法选一种 1)修改 /etc/sysctl.conf 然后 sysctl -p 2) echo "xxx" > /proc/sys/yyyyyy

注3: /proc/[pid]/oom_score 可以查看计算出来的 OOM 的分数

Summary

关于 oom 的种种到这里也差不多可以告一个段落了,休息一下休息一下。 

 

 

 

转载于:https://www.cnblogs.com/foreverfree/p/3477659.html

你可能感兴趣的文章
阿里yum源
查看>>
淡扯javascript编程思想
查看>>
搬家到博客园
查看>>
百度地图API示例:使用vue添加删除覆盖物
查看>>
Beanutils.copyProperties( )用法
查看>>
mysql的使用命令(1)
查看>>
【java 获取路径的方法】
查看>>
Flex 布局教程:实例篇
查看>>
JavaScript学习
查看>>
C#DataTable与Grid的差别
查看>>
apache配置中ProxyPassReverse指令的含义
查看>>
《Apache kafka实战》读书笔记-kafka集群监控工具
查看>>
简单工厂
查看>>
【模板】矩阵快速幂
查看>>
AJAX笔记
查看>>
cadence 封装制作小结
查看>>
AFNetwork 作用和用法详解
查看>>
登录linux,输入ls显示anaconda-ks.cfg cobbler.ks ....., 原因在于root@ ~ / 区别
查看>>
虚拟机CentOS6.5网络配置
查看>>
bzoj2563 阿狸和桃子的游戏
查看>>