测试环境:
Linux centos-7.shared 3.10.0-693.5.2.el7.x86_64 #1 SMP Fri Oct 20 20:32:50 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

一个进程的虚拟地址空间一般可以大致划分为代码区(text)、只读数据区(rodata)、初始化数据区(data)、为初始化数据区(bss)、堆(heap)、共享内存区(.so,mmap的地方)、栈(stack)、内核区(kernel)。

在这里插入图片描述

对于 Linux 进程或者说主线程,其 stack 是在 fork 的时候生成的,实际上就是复制了父亲的 stack 空间地址,然后写时拷贝 (cow) 以及动态增长。
然而对于主线程生成的子线程而言,其 stack 将不再是这样的了,而是事先固定下来的。
线程栈不能动态增长,一旦用尽就没了,这是和生成进程的 fork 不同的地方。

==线程(非主线程)的栈的大小是固定的==,其会在空闲的堆(堆顶附近自顶向下分配)或者是空闲栈(栈底附近自底向上分配),因此线程栈局部函数中分配的变量是存放到各自分配的栈空间,因此可以说是线程私有的,又因为该线程栈的边界是设定好的,因此该线程栈的大小的固定的。

测试

ulimit -a 查看操作系统的相关限制:
可以看到 stack size 的限制是 8192kb 也就是 8MB。
注意这里的 8MB 是指每个被创建的 thread 的 stack 都是这么大。

1
2
3
4
[parallels@centos-7 LinuxCode]$ ulimit -a
......
stack size (kbytes, -s) 8192
......

测试代码:
创建了 3 个 thread,执行 ThreadEntry。
编译后跑起来!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

void*
ThreadEntry(void* args)
{
(void) args;
while (1)
{
sleep(1);
}
}

int main()
{
pthread_t tid1, tid2, tid3;
pthread_create(&tid1, NULL, ThreadEntry, NULL);
pthread_create(&tid2, NULL, ThreadEntry, NULL);
pthread_create(&tid3, NULL, ThreadEntry, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
return 0;
}

==从 heap 的顶部向下分配。==
ps aux | grep a.out 查看 pid
cat /proc/[pid]/maps 这个显示进程映射了的内存区域和访问权限。
可以看到:在 heap 下面连续的几个属性为 rw-p 的地址大小刚好都为 8192kb。并且每个都在边界穿插了一个大小为 1000H(4096kb) 的边界空间。

在这里插入图片描述

==从 stack 底部向上分配==
ulimit -s unlimited 设置 stack size 为 unlimited,注意虽然设置了stack size为无限,但是实际上其并不是无限的,而也是固定大小的线程栈,大小为1mb。
然后 cat /proc/[pid]/maps 查看虚拟地址空间的映射。
可以看到,这种情况下线程栈是分配在 stack 底附近,自底向上生长的。
在这里插入图片描述
在这里插入图片描述

结论

==不管线程栈是在堆分配还是在栈分配,其都是固定大小的,有边界的。==


参考:
https://blog.csdn.net/qq_16097611/article/details/82592873
https://blog.csdn.net/yangkuanqaz85988/article/details/52403726
https://blog.csdn.net/lijzheng/article/details/23618365

EOF