一次外场宕机引发对linux内存管理的进一步思考--Linux虚拟地址空间如何分布
2017-07-08 15:04
1096 查看
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:《深入理解计算机系统》图
x86_64:
虚拟内存的布局说明,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(将上述注释去掉):
A.运行上述程序后,用户态可访问的内存空间已经占完,结果还是未触发 oom-killer机制,为什么?
运行另一个程序处理过程:
./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,,即交换区,当内存不够的时候,被踢出的进程被暂时存储到交换区。当需要这条被踢出的进程的时候,就从交换区重新加载到内存,否则它不会主动交换到真是内存中。
0x05 结论
当某个进程内存泄露严重时,会触发oom-killer和系统卡顿。宕机的原因还是不明。后期继续分析!!相关文章推荐
- Linux虚拟地址空间如何分布?
- Linux虚拟地址空间分布
- Linux内存管理 —— 进程的虚拟地址空间和VSS
- Linux进程虚拟地址空间的分布、以及堆和栈的区别
- 内存管理分析之一:Linux进程空间与虚拟地址的好处
- Linux中的虚拟地址、物理地址和内存管理方式(一)
- 如何在程序中预留一段固定的虚拟地址空间
- Chap-6 6.4 进程虚拟地址空间分布
- Linux 内核空间虚拟地址和物理地址相互转换
- Linux虚拟地址空间布局
- Linux虚拟地址空间布局
- ]Linux中的虚拟地址、物理地址和内存管理
- [Linux] 进程的虚拟地址空间
- linux内存管理---虚拟地址、逻辑地址、线性地址、物理地址的区别
- Linux虚拟地址空间布局
- linux虚拟地址空间你真的理解了吗?
- Linux中的虚拟地址、物理地址和内存管理方式
- Unix v6中对虚拟地址映射到物理地址以及在内核态如何访问用户空间的u值
- 从赵炯老师的文章中截图linux0.11 内核在线性空间和虚拟空间分布
- Linux虚拟地址空间布局