zoukankan      html  css  js  c++  java
  • 《Linux内核精髓:精通Linux内核必会的75个绝技》一HACK #16 OOM Killer的运行与结构

    HACK #16 OOM Killer的运行与结构(1)

    本节介绍OOM Killer的运行与结构。

    Linux中的Out Of Memory(OOM) Killer功能作为确保内存的最终手段,可以在耗尽系统内存或交换区后,向进程发送信号,强制终止该进程。

    这个功能即使在无法释放内存的情况下,也能够重复进行确保内存的处理过程,防止系统停滞。还可以找出过度消耗内存的进程。本节将介绍2.6内核的OOM Killer。

    确认运行、日志

    进行系统验证或负载试验时,有时会出现正在运行中的进程终止或者SSH连接突然断开、尝试重新登录也无法连接的情况。

    这时需要查看日志。有时会输出如下内核信息。

    1. Pid: 4629, comm: stress Not tainted 2.6.26 #3  
    2. Call Trace:  
    3. [<ffffffff80265a2c>] oom_kill_process+0x57/0x1dc  
    4. [<ffffffff80238855>] __capable+0x9/0x1c  
    5. [<ffffffff80265d39>] badness+0x16a/0x1a9  
    6. [<ffffffff80265f59>] out_of_memory+0x1e1/0x24b  
    7. [<ffffffff80268967>] __alloc_pages_internal+0x320/0x3c2  
    8. [<ffffffff802726cb>] handle_mm_fault+0x225/0x708  
    9. [<ffffffff8047514b>] do_page_fault+0x3b4/0x76f  
    10. [<ffffffff80473259>] error_exit+0x0/0x51  
    11. Node 0 DMA per-cpu:  
    12. CPU    0: hi:    0, btch:   1 usd:   0  
    13. CPU    1: hi:    0, btch:   1 usd:   0  
    14. ...  
    15. Active:250206 inactive:251609 dirty:0 writeback:0 unstable:0  
    16. free:3397 slab:2889 mapped:1 pagetables:2544 bounce:0  
    17. Node 0 DMA free:8024kB min:20kB low:24kB high:28kB active:8kB inactive:180kB present:7448kB pa  
    18. ges_scanned:308 all_unreclaimable? yes  
    19. lowmem_reserve[]: 0 2003 2003 2003  
    20. ...  
    21. Node 0 DMA: 6*4kB 4*8kB 2*16kB 2*32kB 5*64kB 1*128kB 3*256kB 1*512kB 2*1024kB 2*2048kB 0*4096k  
    22. B = 8024kB 
    23. Node 0 DMA32: 1*4kB 13*8kB 1*16kB 6*32kB 2*64kB 2*128kB 1*256kB 1*512kB 0*1024kB 0*2048kB 1*40  
    24. 96kB = 5564kB 
    25. 29 total pagecache pages  
    26. Swap cache: add 1630129, delete 1630129, find 2279/2761  
    27. Free swap  = 0kB 
    28. Total swap = 2048248kB 
    29. Out of memory: kill process 2875 (sshd) score 94830592 or a child  
    30. Killed process 3082 (sshd) 

    最后出现了Out of memory(内存不足)。这就表示OOM Killer已经运行。无法重新连接的情况就是因为sshd被OOM Killer终止。如果不重新启动sshd就无法登录。

    OOM Killer通过终止进程来确保空闲内存,接下来将介绍如何选定这个进程。

    进程的选定方法

    OOM Killer在内存耗尽时,会查看所有进程,并分别为每个进程计算分数。将信号发送给分数最高的进程。

    计算分数的方法

    在OOM Killer计算分数时要考虑很多方面。首先要针对每个进程确认下列1~9个事项再计算分数。

    1.首先,计算分数时是以进程的虚拟内存大小为基准的。虚拟内存大小可以使用ps命令的VSZ或/proc/<PID>/status的VmSize注2来确认。对于正在消耗虚拟内存的进程,其最初的得分较高。单位是将1KB作为1个得分。消耗1GB内存的进程,得分约为1 000 000。

    2.如果进程正在执行swapoff系统调用,则得分设置为最大值(unsigned long的最大值)。这是因为禁用swap的行为与消除内存不足是相反的,会立刻将其作为OOM Killer的对象进程。

    3.如果是母进程,则将所有子进程内存大小的一半作为分数。

    4. 根据进程的CPU使用时间和进程启动时间调整得分。这是因为在这里认为越是长时间运行或从事越多工作的进程越重要,需保持得分较低。

    首先,用得分除以CPU使用时间(以10秒为单位)的平方根。如果CPU使用时间为90秒,由于以10秒为单位,因此就是用得分除以9的平方根“3”。另外,根据进程启动开始的时间也可以调整得分。用得分除以启动时间(以1000秒为单位)的平方根的平方根。如果是持续运行16 000秒的进程,则用得分除以16的平方根“4”的平方根“2”。越是长时间运行的进程就越重要。

    小贴士:虽然源代码的备注中写有以10秒为单位、以1000秒为单位,但是实际上在位运算中是以8和1024为单位来计算。

    5.对于通过nice命令等将优先级设置得较低的进程,要将得分翻倍。nice-n中设置为1~19的命令的得分翻倍。

    6.特权进程普遍较为重要,因此将其得分设置为1/4。

    7.通过capset(3)等设置了功能(capability)CAP_SYS_RAWIO注3的进程,其得分为1/4。将直接对硬件进行操作的进程判断为重要进程。

    8.关于Cgroup,如果进程只允许与促使OOM Killer运行的进程所允许的内存节点完全不同的内存节点,则其得分为1/8。

    9.最后通过proc文件系统oom_adj的值调整得分。

    依据以上规则,为所有进程打分,向得分最高的进程发送信号SIGKILL(到Linux 2.6.10为止,在设置了功能CAP_SYS_RAWIO的情况下,发送SIGTERM,在没有设置的情况下,发送SIGKILL)。

    各进程的得分可以使用/proc/<PID>/oom_score来确认。

    但是init(PID为1的)进程不能成为OOM Killer的对象。当成为对象的进程包含子进程时,先向其子进程发送信号。

    向成为对象的进程发送信号后,对于引用系统的全线程,即使线程组(TGID)不同,如果存在与对象进程共享相同内存空间的进程,则也向这些进程发送信号。

    关于OOM Killer的proc文件系统

    下面开始介绍与OOM Killer相关的proc文件系统。

    1. /proc/<PID>/oom_adj 

    为/proc/<PID>/oom_adj设置值就可以调整得分。调整值的范围为–16~15。正的值容易被OOM Killer选定。负值可能性较低。例如,当指定3时,得分就变为23倍;当指定–5时,得分就变为1/25。

    “–17”是一个特殊的值。如果设置为–17,就会禁止OOM Killer发出的信号(从Linux 2.6.12开始支持设置–17)。

    在OOM Killer运行的情况下,为了实现远程登录而想要将sshd排除在对象外时,可以执行下列命令。

    1. # cat /proc/'cat /var/run/sshd.pid'/oom_score  
    2. 15  
    3. # echo -17 >  /proc/'cat /var/run/sshd.pid'/oom_adj  
    4. # tail /proc/'cat /var/run/sshd.pid'/oom_*  
    5. ==> /proc/2278/oom_adj <==  
    6. -17 
    7. ==> /proc/2278/oom_score <==  
    8. 0                               /*得分变成0*/ 

    从Linux 2.6.18开始可以使用/proc//oom_adj。内容记载在Documentation /filesystems/proc.txt中。

    1. /proc/sys/vm/panic_on_oom 

    将/proc/sys/vm/panic_on_oom设置为1时,在OOM Killer运行时可以不发送进程信号,而是使内核产生重大故障。

    1. # echo 1 > /proc/sys/vm/panic_on_oom  
    2. /proc/sys/vm/oom_kill_allocating_task 

    从Linux 2.6.24开始proc文件系统就有oom_kill_allocating_task。如果对此设置除0以外的值,则促使OOM Killer运行的进程自身将接收信号。此处省略对所有进程的得分计算过程。

    1. # echo 1 > /proc/sys/vm/oom_kill_allocating_task 

    这样就不需要参照所有进程,但是也不会考虑进程的优先级和root权限等,只发送信号。

    1. /proc/sys/vm/oom_dump_tasks 

    从Linux 2.6.25开始,将oom_dump_tasks设置为除0以外的值时,在OOM Killer运行时的输出中会增加进程的列表信息。

    下面为设置示例。

    1. # echo 1 > /proc/sys/vm/oom_dump_tasks 

    列表信息显示如下,可以使用dmesg或syslog来确认。

    1. [ pid ]   uid  tgid total_vm      rss cpu oom_adj name  
    2. [    1]     0     1     2580        1   0       0 init  
    3. [  500]     0   500     3231        0   1     -17 udevd  
    4. [ 2736]     0  2736     1470        1   0       0 syslogd  
    5. [ 2741]     0  2741      944        0   0       0 klogd  
    6. [ 2765]    81  2765     5307        0   0       0 dbus-daemon  
    7. [ 2861]     0  2861      944        0   0       0 acpid  
    8. ...  
    9. [ 3320]     0  3320   525842   241215   1       0 stress  
    10. /proc/<PID>/oom_score_adj 

    从Linux 2.6.36开始都安装了/proc/<PID>/oom_score_adj,此后将替换为/proc/ <PID>/oom_adj。详细内容请参考Documentation/feature-removal-schedules.txt。即使当前是对/proc/<PID>/oom_adj进行的设置,在内核内部进行变换后的值也是针对/proc/<PID>/oom_score_adj设置的。

    /proc/<PID>/oom_score_adj可以设置–1000~1000之间的值。设置为–1000时,该进程就被排除在OOM Killer强制终止的对象外。

    在内核2.6.36以后的版本中写入oom_adj,只会输出一次如下的信息。

    1. # dmesg  
    2. .....  
    3. udevd (60): /proc/60/oom_adj is deprecated, please use /proc/60/oom_score_adj instead.  
    4. ..... 

    RHEL5的特征

    在RHEL5中运行OOM Killer时要比在上游内核中更加慎重。OOM Killer会计算调用的次数,仅在一定时间段内超出调用一定次数的情况下运行。

    1.OOM Killer从上次调出到下一次调出之间超过5秒时,调用次数重新开始计算。这是为了避免仅因为产生突发性的内存负载就终止进程。

    2.在计数变成0后的1秒以内调出时,不计入调用的次数。

    3.OOM Killer的调用次数不足10次时,实际不会运行。OOM Killer调用10次时才开始认为内存不足。

    4.最后OOM Killer运行不到5秒的话,OOM Killer不会再次运行。因此运行频率最高也有5秒一次。这是为了防止不必要地连续终止多个进程。也有等待接收到OOM Killer发出信号的进程终止(释放内存)的意思。

    5. OOM Killer一旦运行,调用的次数就重新回到0。

    也就是说,只有在OOM Killer在5秒以内调出的状态连续出现10次以上时才会运行。

    这些限制原本是到Linux 2.6.10为止都有的。因此在基于Linux 2.6.9的RHEL4中也需要实施这些限制。当前的上游内核中已经取消了这些限制。

    RHEL4的运行

    查看OOM Killer在RHEL4(Linux 2.6.9)中的运行情况。在下例中,是内存、交换区都为2GB的环境下,使用负载测试工具stress刻意消耗内存。

    stress是给内存、CPU、磁盘I/O施加负载的工具。既可以为其中一项增加负载,也可以同时为这三项中的几项增加负载。stress在运行中如果接收到信号,就会输出信息并终止。

    1. # wget -t0 -c http://weather.ou.edu/~apw/projects/stress/stress-1.0.0.tar.gz  
    2. # tar zxvf stress-1.0.0.tar.gz  
    3. # cd stress-1.0.0  
    4. # ./configure ; make ; make install  
    5. # stress --vm 2 --vm-bytes 2G --vm-keep    /* 两个进程分别消耗2GB内存*/  
    6. stress: info: [17327] dispatching hogs: 0 cpu, 0 io, 2 vm, 0 hdd  
    7. stress: FAIL: [17327] (416) <-- worker 17328 got signal 15       /* 接收SIGTERM信号*/  
    8. stress: WARN: [17327] (418) now reaping child worker processes  
    9. stress: FAIL: [17327] (452) failed run completed in 70s 

    此时的控制台画面显示如下。

    1. oom-killer: gfp_mask=0xd0 
    2. Mem-info:  
    3. ...  
    4. Swap cache: add 524452, delete 524200, find 60/102, race 0+0  
    5. Free swap:            0kB                    /* 交换区剩余为0 */  
    6. 524224 pages of RAM                          /* 1页4KB,因此内存大小为2GB */  
    7. 10227 reserved pages                         /* 在内核内部预约的内存 */  
    8. 19212 pages shared  
    9. 253 pages swap cached  
    10. Out of Memory: Killed process 17328 (stress).   /* 根据信号终止的进程 */ 

    在上游内核中无法禁用OOM Killer,而在RHEL4中则通过/proc/sys/vm/oom-kill可以禁用OOM Killer。

    1. # echo 0 > /proc/sys/vm/oom-kill 

    或者

    1. # /sbin/sysctl -w vm.oom-kill=

    禁用后OOM Killer就不会发送信号,但是会输出如上内存信息。

    RHEL5的运行

    在RHEL5(Linux 2.6.18)中对OOM Killer的运行进行确认的方法与RHEL4中相同。

    1. # stress --vm 2 --vm-bytes 2G --vm-keep  
    2. stress: info: [11779] dispatching hogs: 0 cpu, 0 io, 2 vm, 0 hdd  
    3. stress: FAIL: [11779] (416) <-- worker 11780 got signal 9         /* SIGKILL */  
    4. stress: WARN: [11779] (418) now reaping child worker processes  
    5. stress: FAIL: [11779] (452) failed run completed in 46s 

    此时的控制台画面如下所示。添加了运行OOM Killer时的回溯输出,便于调试。

    1. Call Trace:  
    2. [<ffffffff800bf551>] out_of_memory+0x8e/0x321  
    3. [<ffffffff8000f08c>] __alloc_pages+0x22b/0x2b4  
    4. ...  
    5. [<ffffffff800087fd>] __handle_mm_fault+0x208/0xe04  
    6. [<ffffffff80065a6a>] do_page_fault+0x4b8/0x81d  
    7. [<ffffffff800894ad>] default_wake_function+0x0/0xe  
    8. [<ffffffff80039dda>] tty_ldisc_deref+0x68/0x7b  
    9. [<ffffffff8005cde9>] error_exit+0x0/0x84  
    10. Mem-info:  
    11. ...  
    12. Swap cache: add 512503, delete 512504, find 90/129, race 0+0  
    13. Free swap  = 0kB 
    14. Total swap = 2048276kB 
    15. Free swap:            0kB  
    16. 524224 pages of RAM  
    17. 42102 reserved pages  
    18. 78 pages shared  
    19. 0 pages swap cached  
    20. Out of memory: Killed process 11780 (stress). 

    RHEL6的运行

    RHEL6.0中OOM Killer计算得分的方式基本和RHEL5中没有不同。RHEL6系不会如“RHEL5的特征”中所述慎重地运行。其运行基本与上游内核相同。

    小结

    本节介绍了OOM Killer的结构和各种设置。当系统运行异常时确认syslog等,如果有OOM Killer的输出,就可以得知曾出现内存不足。

    参考文献

    stress

    http://weather.ou.edu/~apw/projects/stress/

    —Naohiro Ooiwa

  • 相关阅读:
    《Microsoft Sql server 2008 Internals》读书笔记第十一章DBCC Internals(2)
    《Microsoft Sql server 2008 Internals》读书笔记第十一章DBCC Internals(9)
    《Microsoft Sql server 2008 Internals》读书笔记第九章Plan Caching and Recompilation(10)
    CKEditor在asp.net环境下的使用一例
    《Microsoft Sql server 2008 Internals》读书笔记第五章Table(7)
    《Microsoft Sql server 2008 Internals》读书笔记第九章Plan Caching and Recompilation(11)
    千万数据的连续ID表,快速读取其中指定的某1000条数据?
    javascript中的float运算精度
    .Net与Java的互操作(.NET StockTrader微软官方示例应用程序)
    《Microsoft Sql server 2008 Internals》读书笔记第十一章DBCC Internals(6)
  • 原文地址:https://www.cnblogs.com/tcicy/p/8552804.html
Copyright © 2011-2022 走看看