一次外场宕机引发对linux内存管理的进一步思考--Linux虚拟地址空间如何分布

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和系统卡顿。宕机的原因还是不明。后期继续分析!!



  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值