统计栈大小及优化方法

问题背景

栈溢出导致crash,某模块子线程的栈空间只有1M大小,超过1M导致crash。

分析方法

  1. 系统栈内存大小可,通过ulimit -a查看
    core file size          (blocks, -c) 0
    data seg size           (kbytes, -d) unlimited
    scheduling priority             (-e) 0
    file size               (blocks, -f) unlimited
    pending signals                 (-i) 30539
    max locked memory       (kbytes, -l) 64
    max memory size         (kbytes, -m) unlimited
    open files                      (-n) 1024
    pipe size            (512 bytes, -p) 8
    POSIX message queues     (bytes, -q) 819200
    real-time priority              (-r) 0
    stack size              (kbytes, -s) 8192
    cpu time               (seconds, -t) unlimited
    max user processes              (-u) 30539
    virtual memory          (kbytes, -v) unlimited
    file locks                      (-x) unlimited

可以看到栈大小为8M

  1. 线程栈大小,可以在需要查看栈占用大小的地方sleep,然后用cat /proc/[PID]/smaps 查看其[stacks]信息,其中Size是线程栈大小,Rss是实际使用的栈大小

举个例子,smaps_stack_example.c代码如下:

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    
    int main(void)
    {
        printf("Start main\n");
        printf("the main process ID = %d\n", getpid());
        printf("the main parent process ID = %d\n", getppid());
        usleep(20 * 1000 * 1000);
    
        {
            int8_t a[2 * 1024 *1024] = { 1 };
            printf("stack addr a = %p\n", a);
            usleep(10 * 1000 * 1000);
        }
    
        {
            int8_t b[3 * 1024 * 1024] = { 1 };
            printf("stack addr b = %p\n", b);
            usleep(10 * 1000 * 1000);
        }
    
        {
            int8_t c[1 * 1024 * 1024] = { 1 };
            printf("stack addr c = %p\n", c);
            usleep(10 * 1000 * 1000);
        }
    
        printf("End main\n");
    
        return 0;
    }

分别在分配a\b\c三个局部变量之后加延时,通过smaps查看其实际使用栈空间大小。

用gcc编译smaps_stack_example.c并执行smaps_stack_example,日志如下:

    $ ./smaps_stack_example
    Start main
    the main process ID = 30841
    the main parent process ID = 15090
    stack addr a = 0x7fff0feea7f0
    stack addr b = 0x7fff0feea7f0
    stack addr c = 0x7fff0feea7f0
    End main

通过日志可以看出,a\b\c三个局部变量由于作用域在自己的{}内,所以a\b\c的栈地址一致,说明栈内存被复用。

对应的栈空间使用大小如下:

    $ cat /proc/30841/smaps | egrep -si -A 2 "\[stack"
    7fff0feea000-7fff101ee000 rw-p 00000000 00:00 0                          [stack]
    Size:               3092 kB
    Rss:                  20 kB
    
    $ cat /proc/30841/smaps | egrep -si -A 2 "\[stack"
    7fff0feea000-7fff101ee000 rw-p 00000000 00:00 0                          [stack]
    Size:               3092 kB
    Rss:                2068 kB
    
    $ cat /proc/30841/smaps | egrep -si -A 2 "\[stack"
    7fff0feea000-7fff101ee000 rw-p 00000000 00:00 0                          [stack]
    Size:               3092 kB
    Rss:                3084 kB
    
    $ cat /proc/30841/smaps | egrep -si -A 2 "\[stack"
    7fff0feea000-7fff101ee000 rw-p 00000000 00:00 0                          [stack]
    Size:               3092 kB
    Rss:                3084 kB

通过smaps文件的stack可以看到,stack的Rss表示栈内存实际使用物理内存大小,它会记录当前最大使用过的物理内存大小。

例子2,smaps_stack_example_2.c代码如下:

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    
    int main(void)
    {
        printf("Start main\n");
        printf("the main process ID = %d\n", getpid());
        printf("the main parent process ID = %d\n", getppid());
        usleep(20 * 1000 * 1000);
    
        {
            int8_t a[2 * 1024 *1024] = { 1 };
            printf("stack addr a = %p\n", a);
            usleep(10 * 1000 * 1000);
        }
    
        int8_t b[3 * 1024 * 1024] = { 1 };
        printf("stack addr b = %p\n", b);
        usleep(10 * 1000 * 1000);
    
        {
            int8_t c[1 * 1024 * 1024] = { 1 };
            printf("stack addr c = %p\n", c);
            usleep(10 * 1000 * 1000);
        }
    
        printf("End main\n");
    
        return 0;
    }

问:此时stack的size为多大?

日志如下:

    Start main
    the main process ID = 4110
    the main parent process ID = 15090
    stack addr a = 0x7ffc185e43a0
    stack addr b = 0x7ffc185e43a0
    stack addr c = 0x7ffc184e43a0
    End main

通过日志可以看出,此时stack的size为4M,a\c两个局部变量由于作用域在自己的{}内,b作用域在整个main中
a占用2M栈内存;
b占用3M栈内存,可以复用a的栈内存,所以首地址一致;
c占用1M栈内存,由于b在整个main线程中占用,所以c的栈内存不能复用前面的,c的首地址为0x7ffc184e43a0,与a\b的首地址不一致;

0x7ffc185e43a0 - 0x7ffc184e43a0 = 1M,因为栈地址是由高地址向低地址增长。

对应的栈空间使用大小如下:

    $ cat /proc/4110/smaps | egrep -si -A 2 "\[stack"
    7ffc184e3000-7ffc188e6000 rw-p 00000000 00:00 0                          [stack]
    Size:               4112 kB
    Rss:                  20 kB
    
    $ cat /proc/4110/smaps | egrep -si -A 2 "\[stack"
    7ffc184e3000-7ffc188e6000 rw-p 00000000 00:00 0                          [stack]
    Size:               4112 kB
    Rss:                2180 kB
    
    $ cat /proc/4110/smaps | egrep -si -A 2 "\[stack"
    7ffc184e3000-7ffc188e6000 rw-p 00000000 00:00 0                          [stack]
    Size:               4112 kB
    Rss:                3088 kB
    
    $ cat /proc/4110/smaps | egrep -si -A 2 "\[stack"
    7ffc184e3000-7ffc188e6000 rw-p 00000000 00:00 0                          [stack]
    Size:               4112 kB
    Rss:                4108 kB

解决方案

  1. 栈主要为非静态局部变量、函数参数、返回地址等提供存储空间,所以优化栈内存,即将函数局部变量由栈内存改为堆内存即可。
  2. 优化后可以采用上述方式check栈内存是否减少。
  3. 堆内存的查看与以上方法一致。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值