问题背景
栈溢出导致crash,某模块子线程的栈空间只有1M大小,超过1M导致crash。
分析方法
- 系统栈内存大小可,通过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
- 线程栈大小,可以在需要查看栈占用大小的地方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
解决方案
- 栈主要为非静态局部变量、函数参数、返回地址等提供存储空间,所以优化栈内存,即将函数局部变量由栈内存改为堆内存即可。
- 优化后可以采用上述方式check栈内存是否减少。
- 堆内存的查看与以上方法一致。