0x01 缘由
外场一次服务器宕机,一群人baba的上去围观,分析问题,大部分是猜测,通过回退版本后只解决了问题表象,内在的真实原因没确定。服务器上运行着JAVA程序和C程序,到底是什么导致这次宕机事故。通过分析日志发现有类似如下错误:
test_mem invoked oom-killer: gfp_mask=0x280da, order=0, oom_adj=0, oom_score_adj=0
Pid: 10021, comm: test_mem Tainted: G --------------- H 2.6.32-431.el6.i686 #1
Call Trace:
[<c04ec6c4>] ? dump_header+0x84/0x190
[<c04eca68>] ? oom_kill_process+0x68/0x280
[<c04ec9a2>] ? oom_badness+0x92/0xf0
[<c04ecfe8>] ? out_of_memory+0xc8/0x1e0
[<c04f773d>] ? __alloc_pages_nodemask+0x7fd/0x810
[<c050df91>] ? handle_pte_fault+0x9d1/0xdd0
[<c0444e82>] ? __wake_up+0x42/0x60
[<c043fb80>] ? kmap_atomic_prot+0x120/0x150
[<c050e4c1>] ? handle_mm_fault+0x131/0x1d0
[<c04398fb>] ? __do_page_fault+0xfb/0x410
[<c0536df1>] ? vfs_write+0x121/0x190
[<c086279a>] ? do_page_fault+0x2a/0x90
[<c0862770>] ? do_page_fault+0x0/0x90
[<c0860247>] ? error_code+0x73/0x78
Mem-Info:
DMA per-cpu:
CPU 0: hi: 0, btch: 1 usd: 0
CPU 1: hi: 0, btch: 1 usd: 0
CPU 2: hi: 0, btch: 1 usd: 0
CPU 3: hi: 0, btch: 1 usd: 0
Normal per-cpu:
CPU 0: hi: 186, btch: 31 usd: 0
CPU 1: hi: 186, btch: 31 usd: 30
CPU 2: hi: 186, btch: 31 usd: 0
CPU 3: hi: 186, btch: 31 usd: 0
HighMem per-cpu:
CPU 0: hi: 186, btch: 31 usd: 0
CPU 1: hi: 186, btch: 31 usd: 0
CPU 2: hi: 186, btch: 31 usd: 0
CPU 3: hi: 186, btch: 31 usd: 0
active_anon:693728 inactive_anon:221684 isolated_anon:64
active_file:26 inactive_file:51 isolated_file:0
unevictable:0 dirty:0 writeback:3072 unstable:0
free:27528 slab_reclaimable:1391 slab_unreclaimable:9790
mapped:131 shmem:872 pagetables:4219 bounce:0
DMA free:7576kB min:64kB low:80kB high:96kB active_anon:0kB inactive_anon:0kB active_file:0kB inactive_file:0kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:15796kB mlocked:0kB dirty:0kB writeback:0kB mapped:0kB shmem:0kB slab_reclaimable:0kB slab_unreclaimable:0kB kernel_stack:0kB pagetables:0kB unstable:0kB bounce:0kB writeback_tmp:0kB pages_scanned:0 all_unreclaimable? yes
lowmem_reserve[]: 0 863 3938 3938
Normal free:102148kB min:3724kB low:4652kB high:5584kB active_anon:260712kB inactive_anon:259352kB active_file:156kB inactive_file:88kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:883912kB mlocked:0kB dirty:0kB writeback:4380kB mapped:68kB shmem:0kB slab_reclaimable:5564kB slab_unreclaimable:39160kB kernel_stack:1312kB pagetables:1916kB unstable:0kB bounce:0kB writeback_tmp:0kB pages_scanned:32 all_unreclaimable? no
lowmem_reserve[]: 0 0 24605 24605
HighMem free:388kB min:512kB low:3828kB high:7148kB active_anon:2514200kB inactive_anon:627384kB active_file:0kB inactive_file:116kB unevictable:0kB isolated(anon):256kB isolated(file):0kB present:3149484kB mlocked:0kB dirty:0kB writeback:7908kB mapped:456kB shmem:3488kB slab_reclaimable:0kB slab_unreclaimable:0kB kernel_stack:0kB pagetables:14960kB unstable:0kB bounce:0kB writeback_tmp:0kB pages_scanned:53 all_unreclaimable? no
lowmem_reserve[]: 0 0 0 0
DMA: 4*4kB 3*8kB 3*16kB 4*32kB 3*64kB 2*128kB 3*256kB 0*512kB 2*1024kB 2*2048kB 0*4096kB = 7576kB
Normal: 623*4kB 395*8kB 236*16kB 129*32kB 89*64kB 54*128kB 33*256kB 18*512kB 15*1024kB 7*2048kB 7*4096kB = 102196kB
HighMem: 23*4kB 14*8kB 11*16kB 2*32kB 0*64kB 2*128kB 0*256kB 0*512kB 0*1024kB 0*2048kB 0*4096kB = 700kB
4464 total pagecache pages
3404 pages in swap cache
Swap cache stats: add 2804401, delete 2800997, find 71974/72729
Free swap = 0kB
Total swap = 4046840kB
1046000 pages RAM
819202 pages HighMem
76971 pages reserved
1403 pages shared
939149 pages non-shared
[ pid ] uid tgid total_vm rss cpu oom_adj oom_score_adj name
[ 485] 0 485 696 2 0 -17 -1000 udevd
[ 1211] 0 1211 3233 21 3 -17 -1000 auditd
[ 1227] 0 1227 9296 19 2 0 0 rsyslogd
[ 1273] 0 1273 3102 22 0 0 0 usbdaemon
[ 1287] 0 1287 2237 13 2 -17 -1000 sshd
[ 1363] 0 1363 3242 12 0 0 0 master
[ 1372] 89 1372 3278 9 2 0 0 qmgr
[ 1373] 0 1373 1501 33 2 0 0 crond
[ 1405] 0 1405 1376 2 0 0 0 su
[ 1413] 0 1413 502 2 0 0 0 mingetty
[ 1415] 0 1415 502 2 3 0 0 mingetty
[ 1417] 0 1417 502 2 0 0 0 mingetty
[ 1419] 0 1419 502 2 1 0 0 mingetty
[ 1421] 0 1421 502 2 3 0 0 mingetty
[ 1423] 0 1423 502 2 1 0 0 mingetty
[ 1430] 0 1430 860 2 0 -17 -1000 udevd
[ 1431] 0 1431 860 2 2 -17 -1000 udevd
[ 1433] 0 1433 2525 1286 2 0 0 startup.sh
[ 1452] 0 1452 2973 33 2 0 0 sshd
[ 1507] 0 1507 1314 2 0 0 0 bash
[ 2305] 0 2305 3073 13 2 0 0 sshd
[ 2309] 0 2309 1314 17 0 0 0 bash
[30012] 0 30012 3307 2 2 0 0 sshd
[30024] 0 30024 2280 2 0 0 0 sftp-server
[30075] 0 30075 2280 2 0 0 0 sftp-server
[30094] 0 30094 2280 2 0 0 0 sftp-server
[ 6973] 0 6973 2973 11 2 0 0 sshd
[ 6985] 0 6985 1314 2 2 0 0 bash
[12591] 0 12591 3065 11 2 0 0 sshd
[12611] 0 12611 1314 2 0 0 0 bash
[19446] 0 19446 3196 41 0 0 0 sshd
[19458] 0 19458 1314 17 0 0 0 bash
[21347] 0 21347 2973 38 2 0 0 sshd
[21351] 0 21351 1314 17 0 0 0 bash
[21483] 0 21483 3008 44 0 0 0 sshd
[21495] 0 21495 1314 17 0 0 0 bash
[21583] 0 21583 3008 41 3 0 0 sshd
[21587] 0 21587 1314 27 2 0 0 bash
[ 5391] 89 5391 3261 18 2 0 0 pickup
[ 8906] 0 8906 642 46 2 0 0 top
[ 9064] 0 9064 675 74 2 0 0 top
[ 9289] 0 9289 2973 40 2 0 0 sshd
[ 9301] 0 9301 1314 17 0 0 0 bash
[10020] 0 10020 785602 2722 3 0 0 test_mem1
[10021] 0 10021 636028 457164 0 0 0 test_mem
[10022] 0 10022 495963 449718 1 0 0 test_mem
[10118] 0 10118 1016 23 1 0 0 sleep
Out of memory: Kill process 10020 (test_mem1) score 365 or sacrifice child
0x02 日志现象分析
系统触发了invoked oom-killer机制,下面为相关简介,但不是导致宕机的原因,只能导致程序退出重启;
oom kiiler会在内存紧张的时候,会依次kill内存占用较高的进程,发送Signal 15(SIGTERM)。并在/var/log/message中进行记录。里面会记录一些如pid,process name,cpu mask,trace等信息,通过监控可以发现类似问题。
0x03 内存布局
x86_32:
《深入理解计算机系统》图
![](https://img-blog.csdn.net/20170708142028557?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcGFuZ3llbWVuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
![](https://img-blog.csdn.net/20170708142052457?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcGFuZ3llbWVuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
x86_64:
![](https://img-blog.csdn.net/20170708142120221?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcGFuZ3llbWVuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
虚拟内存的布局说明,Linux使用虚拟地址空间,大大增加了进程的寻址空间,由低地址到高地址分别为:
a.只读段:该部分空间只能读,不可写,包括代码段、rodata 段(C常量字符串和#define定义的常量);
b.数据段:保存全局变量、静态变量的空间;
c.堆:就是平时所说的动态内存,malloc/new大部分都来源于此。其中堆顶的位置可通过函数brk()和 sbrk()进行动态调整;
d.文件映射区域:如动态库、共享内存等映射物理空间的内存,一般是mmap函数所分配的虚拟地址空间;
e.栈:用于维护函数调用的上下文空间,一般为8M ,可通过ulimit -s查看;
f.内核虚拟空间:用户代码不可见的内存区域,由内核管理;
利用测试程序查看下各自情况:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int global_num = 0;
char global_str_arr [65536] = {'a'};
int main(int argc, char** argv)
{
char* heap_var = NULL;
int local_var = 0;
printf("Address of function main 0x%lx\n", main);
printf("Address of global_num 0x%lx\n", &global_num);
printf("Address of global_str_arr 0x%lx ~ 0x%lx\n", &global_str_arr[0], &global_str_arr[65535]);
printf("Bot of stack is 0x%lx\n", &heap_var);
printf("Top of stack is 0x%lx\n", &local_var);
printf("Top of heap is 0x%lx\n", sbrk(0));
heap_var = malloc(sizeof(char) * 127 * 1024);
printf("Address of heap_var is 0x%lx\n", heap_var);
printf("Top of heap after malloc is 0x%lx\n", sbrk(0));
free(heap_var);
heap_var = NULL;
printf("Top of heap after free is 0x%lx\n", sbrk(0));
return 1;
}
运行结果:
gcc -o mem_layout mem_layout.c
root@node test]# ./mem_layout
Address of function main 0x8048454
Address of global_num 0x8059928
Address of global_str_arr 0x8049920 ~ 0x805991f
Bot of stack is 0xbfbb8b9c
Top of stack is 0xbfbb8b98
Top of heap is 0x8486000
Address of heap_var is 0x8486008
Top of heap after malloc is 0x84c6000
Top of heap after free is 0x84a7000
可以查看汇编:
objdump -d mem_layout -M intel > mem_layout.asm 找对应的关系。
虚拟地址位数:
x86_32: 32位表示地址,可寻址空间为4G;
x86_64:
[root@y test]# ./mem_layout
Address of function main 0x400594
Address of global_num 0x610bd0
Address of global_str_arr 0x600bc0 ~ 0x610bbf
Bot of stack is 0x7fff3d81ce38
Top of stack is 0x7fff3d81ce34
Top of heap is 0x1984000
Address of heap_var is 0x1984010
Top of heap after malloc is 0x19c4000
Top of heap after free is 0x19a5000
虚拟地址位数:
x86_64: 48位表示地址,可虚拟空间为128TB;具体系统具体查看:cat /proc/cpuinfo | grep address
0x04 模拟内存分配耗尽场景
模拟目的:
1、触发oom-killer机制,看是否导致宕机;
2、malloc 内存申请和分配机制的原理;
3、多个程序对内存资源的抢占问题;
测试代码1:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BLOCK_SIZE 1024*1024
int main(void)
{
char *block = NULL;
int mem_total = 0;
while(1)
{
usleep(200);
block = malloc(BLOCK_SIZE);
if(block == NULL)
{
printf("Malloc Fail\n");
sleep(360);
}
mem_total++;
printf("Mem Total %d Kb\n", mem_total);
//memset(block, 'a', BLOCK_SIZE);
}
return EXIT_SUCCESS;
}
编译运行:
gcc -o test_mem test_mem.c
./test_mem
运行结果:
Mem Total 3052 Kb
Mem Total 3053 Kb
Mem Total 3054 Kb
Mem Total 3055 Kb
Mem Total 3056 Kb
Malloc Fail
ps -ef | grep test_mem
top -p xxxx
截图如下:
虚拟地址 3G 物理地址才12M,为什么?
因为不是 malloc 后就马上占用实际内存,而是第一次使用时发现虚存对应的物理页面未分配,产生缺页中断,才真正分配物理页面,同时更新进程页面的映射关系。这也是 Linux 虚拟内存管理的核心概念之一。
测试程序2(将上述注释去掉):
![](https://img-blog.csdn.net/20170708142556021?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcGFuZ3llbWVuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
A.运行上述程序后,用户态可访问的内存空间已经占完,结果还是未触发 oom-killer机制,为什么?
运行另一个程序处理过程:
./test_mem2
B.top观察,出现的现象时,test_mem占用的内存在减少,test_mem2内存在不断上升,为什么?
同时运行两个程序后,也未触发 oom-killer,为什么?
观察下内存情况:
运行后,系统相当卡顿,说明资源不足。此时触发了oom-killer,杀掉了test_mem;
A问题的解答:当物理内存使用完或者达到一定比例之后,我们可以使用swap做临时的内存使用。当物理内存和swap都被使用完那么就会出错,out of memory。对于使用多大比例内存之后开始使用swap,在系统的配置文件中可以通过调整参数进行修改。cat /proc/sys/vm/swappiness
B问题的解答:当内存没有可用的,就必须要把内存中不经常运行的程序给踢出去。但是踢到哪里去,这时候swap就出现了。swap全称为swap place,,即交换区,当内存不够的时候,被踢出的进程被暂时存储到交换区。当需要这条被踢出的进程的时候,就从交换区重新加载到内存,否则它不会主动交换到真是内存中。
运行另一个程序处理过程:
./test_mem2
B.top观察,出现的现象时,test_mem占用的内存在减少,test_mem2内存在不断上升,为什么?
同时运行两个程序后,也未触发 oom-killer,为什么?
观察下内存情况:
[root@node ~]# free -m
total used free shared buffers cached
Mem: 3819 3693 126 0 0 8
-/+ buffers/cache: 3684 135
Swap: 3951 2654 1297
./test_mem3运行后,系统相当卡顿,说明资源不足。此时触发了oom-killer,杀掉了test_mem;
A问题的解答:当物理内存使用完或者达到一定比例之后,我们可以使用swap做临时的内存使用。当物理内存和swap都被使用完那么就会出错,out of memory。对于使用多大比例内存之后开始使用swap,在系统的配置文件中可以通过调整参数进行修改。cat /proc/sys/vm/swappiness
B问题的解答:当内存没有可用的,就必须要把内存中不经常运行的程序给踢出去。但是踢到哪里去,这时候swap就出现了。swap全称为swap place,,即交换区,当内存不够的时候,被踢出的进程被暂时存储到交换区。当需要这条被踢出的进程的时候,就从交换区重新加载到内存,否则它不会主动交换到真是内存中。