kernel hacker修炼之道之内存管理-OOM Killer

本文详细解析了Linux系统的OOM(Out of Memory)机制。介绍了当系统内存不足时如何选择并杀死进程来释放内存,包括关键函数out_of_memory的工作流程,以及如何评估进程的badness值来决定哪个进程被终止。

在系统内存不足的时候会回收页框,但是在这个过程中可能会发现,系统即使是以最高优先级扫描都无法释放足够的页面来满足请求。如果系统不能够释放页面,就会调用out_of_memory函数,告知系统发生内存溢出,这时就会杀死某个进程。在__alloc_pages函数中,当调用try_to_free_pages回收页框无效的时候,会调用out_of_memory杀死一个进程,释放所占有的page后,再重新尝试分配。


这个是out_of_memory的流程图,主要分为两部分,左侧这一部分主要是选择要杀死的进程,右边这一部分执行杀死操作。

[html]  view plain copy
  1. 256void out_of_memory(int gfp_mask)  
  2. 257{  
  3. 258        struct mm_struct *mm = NULL;  
  4. 259        task_t * p;  
  5. 260  
  6. 261        read_lock(&tasklist_lock);  
  7. 262retry:  
  8. 263        p = select_bad_process();  
  9. 264  
  10. 265        if (PTR_ERR(p) == -1UL)  
  11. 266                goto out;  
  12. 267  
  13. 268        /* Found nothing?!?! Either we hang forever, or we panic. */  
  14. 269        if (!p) {  
  15. 270                read_unlock(&tasklist_lock);  
  16. 271                show_free_areas();  
  17. 272                panic("Out of memory and no killable processes...\n");  
  18. 273        }  
  19. 274  
  20. 275        printk("oom-killer: gfp_mask=0x%x\n", gfp_mask);  
  21. 276        show_free_areas();  
  22. 277        mm = oom_kill_process(p);  
  23. 278        if (!mm)  
  24. 279                goto retry;  
  25. 280  
  26. 281 out:  
  27. 282        read_unlock(&tasklist_lock);  
  28. 283        if (mm)  
  29. 284                mmput(mm);  
  30. 285  
  31. 286        /*  
  32. 287         * Give "p" a good chance of killing itself before we  
  33. 288         * retry to allocate memory.  
  34. 289         */  
  35. 290        __set_current_state(TASK_INTERRUPTIBLE);  
  36. 291        schedule_timeout(1);  
  37. 292}  
  1. 调用select_bad_process函数选择一个即将被杀死的进程
  2. 调用oom_kill_process函数杀死进程
  3. 如果在调用select_bad_process函数的时候返回-1,说明已经有进程被OOM Killer选中,等它死就行了
  4. 给被选中的进程一点儿时间,休眠一秒再重新分配内存

下面看这个选择"best" process的函数:

[html]  view plain copy
  1. 138static struct task_struct * select_bad_process(void)  
  2. 139{  
  3. 140        unsigned long maxpoints = 0;  
  4. 141        struct task_struct *g, *p;  
  5. 142        struct task_struct *chosen = NULL;  
  6. 143        struct timespec uptime;  
  7. 144  
  8. 145        do_posix_clock_monotonic_gettime(&uptime);  
  9. 146        do_each_thread(g, p)  
  10. 147                /* skip the init task with pid == 1 */  
  11. 148                if (p->pid > 1) {  
  12. 149                        unsigned long points;  
  13. 150  
  14. 151                        /*  
  15. 152                         * This is in the process of releasing memory so wait it  
  16. 153                         * to finish before killing some other task by mistake.  
  17. 154                         */  
  18. 155                        if ((unlikely(test_tsk_thread_flag(p, TIF_MEMDIE)) || (p->flags & PF_EXITING)) &&  
  19. 156                            !(p->flags & PF_DEAD))  
  20. 157                                return ERR_PTR(-1UL);  
  21. 158                        if (p->flags & PF_SWAPOFF)  
  22. 159                                return p;  
  23. 160  
  24. 161                        points = badness(p, uptime.tv_sec);  
  25. 162                        if (points > maxpoints || !chosen) {  
  26. 163                                chosen = p;  
  27. 164                                maxpoints = points;  
  28. 165                        }  
  29. 166                }  
  30. 167        while_each_thread(g, p);  
  31. 168        return chosen;  
  32. 169}  
  1. 跳过init进程
  2. 如果进程的TIF_MEMDIE标志被设置,表示进程已经被OOM Killer机制选中;如果PF_EXITING标志被设置,表示进程正在被消除;如果PF_DEAD标志被设置,表示进程已经dead;如果这些标志被设置则返回-1,这样告诉调用 out_of_memory函数的进程等一下再分配就好
  3. 如果进程的PF_SWAPOFF标志被设置,表示那个进程调用了sys_swapoff函数,这个函数迫使进程所有驻留在swap中的page进入RAM中,并设置相应的页表,所以这个进程直接被返回,被选中杀死
  4. 如果进程没有设置上诉标志,则调用badness选择一个最该杀死的,什么是最该杀死的呢?它选择的是使用了最大量内存而又没有生存很久的进程

看badness函数实现:

[html]  view plain copy
  1.  45unsigned long badness(struct task_struct *p, unsigned long uptime)  
  2.  46{  
  3.  47        unsigned long points, cpu_time, run_time, s;  
  4.  48        struct list_head *tsk;  
  5.  49  
  6.  50        if (!p->mm)  
  7.  51                return 0;  
  8.  52  
  9.  53        /*  
  10.  54         * The memory size of the process is the basis for the badness.  
  11.  55         */  
  12.  56        ppoints = p->mm->total_vm;  
  13.  57  
  14.  58        /*  
  15.  59         * Processes which fork a lot of child processes are likely  
  16.  60         * a good choice. We add the vmsize of the childs if they  
  17.  61         * have an own mm. This prevents forking servers to flood the  
  18.  62         * machine with an endless amount of childs  
  19.  63         */  
  20.  64        list_for_each(tsk, &p->children) {  
  21.  65                struct task_struct *chld;  
  22.  66                chld = list_entry(tsk, struct task_struct, sibling);  
  23.  67                if (chld->mm != p->mm && chld->mm)  
  24.  68                        points += chld->mm->total_vm;  
  25.  69        }  
  26.  70  
  27.  71        /*  
  28.  72         * CPU time is in tens of seconds and run time is in thousands  
  29.  73         * of seconds. There is no particular reason for this other than  
  30.  74         * that it turned out to work very well in practice.  
  31.  75         */  
  32.  76        cpu_time = (cputime_to_jiffies(p->utime) + cputime_to_jiffies(p->stime))  
  33.  77                >> (SHIFT_HZ + 3);  
  34.  78  
  35.  79        if (uptime >= p->start_time.tv_sec)  
  36.  80                run_time = (uptime - p->start_time.tv_sec) >> 10;  
  37.  81        else  
  38.  82                run_time = 0;  
  39.  83  
  40.  84        s = int_sqrt(cpu_time);  
  41.  85        if (s)  
  42.  86                points /= s;  
  43.  87        s = int_sqrt(int_sqrt(run_time));  
  44.  88        if (s)  
  45.  89                points /= s;  
  46.  90  
  47.  91        /*  
  48.  92         * Niced processes are most likely less important, so double  
  49.  93         * their badness points.  
  50.  94         */  
  51.  95        if (task_nice(p) > 0)  
  52.  96                points *= 2;  
  53.  97  
  54.  98        /*  
  55.  99         * Superuser processes are usually more important, so we make it  
  56. 100         * less likely that we kill those.  
  57. 101         */  
  58. 102        if (cap_t(p->cap_effective) & CAP_TO_MASK(CAP_SYS_ADMIN) ||  
  59. 103                                p->uid == 0 || p->euid == 0)  
  60. 104                points /= 4;  
  61. 105  
  62. 106        /*  
  63. 107         * We don't want to kill a process with direct hardware access.  
  64. 108         * Not only could that mess up the hardware, but usually users  
  65. 109         * tend to only have this flag set on applications they think  
  66. 110         * of as important.  
  67. 111         */  
  68. 112        if (cap_t(p->cap_effective) & CAP_TO_MASK(CAP_SYS_RAWIO))  
  69. 113                points /= 4;  
  70. 114  
  71. 115        /*  
  72. 116         * Adjust the score by oomkilladj.  
  73. 117         */  
  74. 118        if (p->oomkilladj) {  
  75. 119                if (p->oomkilladj > 0)  
  76. 120                        points <<= p->oomkilladj;  
  77. 121                else  
  78. 122                        points >>= -(p->oomkilladj);  
  79. 123        }  
  80. 124  
  81. 125#ifdef DEBUG  
  82. 126        printk(KERN_DEBUG "OOMkill: task %d (%s) got %d points\n",  
  83. 127        p->pid, p->comm, points);  
  84. 128#endif  
  85. 129        return points;  
  86. 130}  
  1. 获得这个进程拥有的内存size,记为权重
  2. 遍历这个进程的子进程,如果它们有自己的内存空间,则增加权重vmsize,防止一个进程创建无限的子进程占用内存
  3. 获得占用cpu的时间cpu_time,占有CPU时间越多,即越忙越可能生存
  4. 获得运行的时间run_time,运行时间越久越可能生存
  5. 如果nice值大于0,说明静态优先级很低,你越nice,越会被杀掉,权重×2
  6. CAP_SYS_ADMIN,管理员的程序要保留下来,权重/4
  7. CAP_SYS_RAWIO,如果有访问源设备的能力,保留,权重/4
  8. 使用p->oomkilladj调节一下权重
至此,已经找到bad points最大的进程了,主要是占用内存大,运行时间短,比较空闲的。下面分析另一半,杀死进程,看oom_kill_process函数:

[html]  view plain copy
  1. 230static struct mm_struct *oom_kill_process(struct task_struct *p)  
  2. 231{  
  3. 232        struct mm_struct *mm;  
  4. 233        struct task_struct *c;  
  5. 234        struct list_head *tsk;  
  6. 235  
  7. 236        /* Try to kill a child first */  
  8. 237        list_for_each(tsk, &p->children) {  
  9. 238                c = list_entry(tsk, struct task_struct, sibling);  
  10. 239                if (c->mm == p->mm)  
  11. 240                        continue;  
  12. 241                mm = oom_kill_task(c);  
  13. 242                if (mm)  
  14. 243                        return mm;  
  15. 244        }  
  16. 245        return oom_kill_task(p);  
  17. 246}  
尝试杀死找到的bad points最大的进程的子进程,如果那个子进程有与父进程不同的内存则杀了子进程,否则杀死父进程。

[html]  view plain copy
  1. 205static struct mm_struct *oom_kill_task(task_t *p)  
  2. 206{  
  3. 207        struct mm_struct *mm = get_task_mm(p);  
  4. 208        task_t * g, * q;  
  5. 209  
  6. 210        if (!mm)  
  7. 211                return NULL;  
  8. 212        if (mm == &init_mm) {  
  9. 213                mmput(mm);  
  10. 214                return NULL;  
  11. 215        }  
  12. 216  
  13. 217        __oom_kill_task(p);  
  14. 218        /*  
  15. 219         * kill all processes that share the ->mm (i.e. all threads),  
  16. 220         * but are in a different thread group  
  17. 221         */  
  18. 222        do_each_thread(g, q)  
  19. 223                if (q->mm == mm && q->tgid != p->tgid)  
  20. 224                        __oom_kill_task(q);  
  21. 225        while_each_thread(g, q);  
  22. 226  
  23. 227        return mm;  
  24. 228}  
不能杀init进程。杀死选中的进程和所有的共享那个进程mm的线程,并且这些线程在不同的线程组。主要调用__oom_kill_task函数,进行实际的杀死操作。

[html]  view plain copy
  1. 176static void __oom_kill_task(task_t *p)  
  2. 177{  
  3. 178        if (p->pid == 1) {  
  4. 179                WARN_ON(1);  
  5. 180                printk(KERN_WARNING "tried to kill init!\n");  
  6. 181                return;  
  7. 182        }  
  8. 183  
  9. 184        task_lock(p);  
  10. 185        if (!p->mm || p->mm == &init_mm) {  
  11. 186                WARN_ON(1);  
  12. 187                printk(KERN_WARNING "tried to kill an mm-less task!\n");  
  13. 188                task_unlock(p);  
  14. 189                return;  
  15. 190        }  
  16. 191        task_unlock(p);  
  17. 192        printk(KERN_ERR "Out of Memory: Killed process %d (%s).\n", p->pid, p->comm);  
  18. 193  
  19. 194        /*  
  20. 195         * We give our sacrificial lamb high priority and access to  
  21. 196         * all the memory it needs. That way it should be able to  
  22. 197         * exit() and clear out its resources quickly...  
  23. 198         */  
  24. 199        p->time_slice = HZ;  
  25. 200        set_tsk_thread_flag(p, TIF_MEMDIE);  
  26. 201  
  27. 202        force_sig(SIGKILL, p);  
  28. 203}  

杀那个进程之前先让他把该干的事干完,给它时间片和很高的优先级。然后发送SIGKILL信号,让它去死吧。

[html]  view plain copy
  1. 1268void  
  2. 1269force_sig(int sig, struct task_struct *p)  
  3. 1270{  
  4. 1271        force_sig_info(sig, (void*)1L, p);  
  5. 1272}  


总结一下,当分配内存,内存不足的时候,会以高优先级调用回收函数,如果还无法回收足够的内存,只能找一个既占内存,又比较空闲的进程杀死,在它死前给它比较高的优先级和时间片,让他把该干的事干一下。杀死后,可以释放出大量内存。


下边一个实例程序:

[html]  view plain copy
  1. #include <stdio.h>  
  2.   
  3. int main()  
  4. {  
  5.     void *p;  
  6.     while(1)  
  7.     {  
  8.         p = malloc(1024 * 1024 * 100);  
  9.         memset(p, 0, 1024 * 1024 * 100);  
  10.     printf("100MB memory has been allocated!\n");  
  11.     }  
  12.     return 0;  
  13. }  
运行结果:


此时查看日志:


可以看到系统只剩下8MB左右内存,swap分区已经被全部耗尽了。此时实在无法分配内存了,即使回收也无法成功,因为free swap已经是0了,不可能将pages swap out了。日志里,计算了各个zone buddy system所剩下的pages。

Linux常见驱动源码分析(kernel hacker修炼)--李万鹏 李万鹏 IBM Linux Technology Center kernel team 驱动资料清单内容如下: Linux设备模型(中)之上层容器.pdf Linux设备模型(上)之底层模型.pdf Linux驱动修炼-驱动中一些常见的宏.pdf Linux驱动修炼-内存映射.pdf Linux驱动修炼-看门狗框架源码分析.pdf Linux驱动修炼-触摸屏驱动之s3c2410_ts源码分析.pdf Linux驱动修炼-SPI驱动框架源码分析(中).pdf Linux驱动修炼-SPI驱动框架源码分析(下).pdf Linux驱动修炼-SPI驱动框架源码分析(上).pdf Linux驱动修炼-RTC子系统框架与源码分析.pdf Linux驱动修炼-platform.pdf Linux驱动修炼-LCD背光与gpio控制.pdf Linux驱动修炼-INPUT子系统(下).pdf Linux驱动修炼-INPUT子系统(上).pdf Linux驱动修炼-framebuffer(中).pdf Linux驱动修炼-framebuffer(下).pdf Linux驱动修炼-framebuffer(上).pdf Linux驱动修炼-DMA框架源码分析(下).pdf Linux驱动修炼-DMA框架源码分析(上).pdf Linux驱动修炼-DM9000A网卡驱动框架源码分析(中).pdf Linux驱动修炼-DM9000A网卡驱动框架源码分析(下).pdf Linux驱动修炼-DM9000A网卡驱动框架源码分析(上).pdf Linux驱动修炼-clock框架.pdf Linux驱动修炼-ADC驱动.pdf Linux内核访问外设I O资源的方式.pdf LINUX内核USB子系统学习笔记之初识USB.pdf kernel hacker修炼之驱动-流水灯.pdf kernel hacker修炼之驱动-混杂设备.pdf kernel hacker修炼之驱动-按键.pdf kernel hacker修炼之PCI subsystem(五).pdf kernel hacker修炼之PCI subsystem(四).pdf kernel hacker修炼之PCI subsystem(三).pdf kernel hacker修炼之PCI subsystem(六).pdf kernel hacker修炼之PCI subsystem(二).pdf
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值