[linux] x86_64, 线程栈如何分配的

一、背景

我们知道一个进程的空间地址可以大致划分为代码区(text)、只读数据区(rodata)、初始化数据区(data)、为初始化数据区(bss)、

堆(heap)、共享内存区(.so,mmap的地方)、栈(stack)、内核区(kernel)。

堆:自底向上增长

栈:自顶向下增长

其中这里的栈可以说成是进程栈(或者是主线程栈),但是当主进程(主线程)之外创建了新的子线程,我们都知道线程和进程在linux区别不大,其都是存放在task_struct中,区别只是进程通过fork,一开始只复制父进程的虚拟地址空间,而这些虚拟地址空间利用写时拷贝(cow)技术,在其中的段(或者区),前面所提到的数据区等,如果有更新操作,会分配新的物理空间给子进程,以实现语义上的独立空间地址。对于线程,其一样也会复制父进程的虚拟地址空间,但是不是设置成私有,而是共享,那么相当于一个进程的的所有线程都共享相同的虚拟空间地址。

二、疑问

根据一背景提到我们知道线程会共享父进程的虚拟地址空间,但是我们的知识里也知道线程的栈是私有的,那么是如何实现的呢?这里直接给出答案,线程(非主线程)的栈的大小是固定的,其会在空闲的堆(堆顶附近自顶向下分配)或者是空闲栈(栈底附近自底向上分配),因此线程栈局部函数中分配的变量是存放到各自分配的栈空间,因此可以说是线程私有的,又因为该线程栈的边界是设定好的,因此该线程栈的大小的固定的。

三、例子说明

3.1 ulimit -a ,看到stack size(-s) 8192,这便是当前系统执行程序时候的栈的大小,这里的栈的大小指的是线程栈的大小,而每一个新的线程都会以该值大小进行分配线程栈大小,所以这是每个线程的栈大小,而不是一个进程中栈大小总和,单位kb

core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 15661
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 65535
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) 15661
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

3.2 执行下面程序,然后查看cat /proc/{pid}/maps中的虚拟地址的分配情况

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void *task(void *a) {
    for (;;) {
        sleep(1000);
    }
}

int main() {
    pthread_t pid1, pid2, pid3;
    int rt = -1;
    rt = pthread_create(&pid1, NULL, task, NULL);
    rt = pthread_create(&pid2, NULL, task, NULL);
    rt = pthread_create(&pid3, NULL, task, NULL);
    pthread_join(pid1, NULL);     
    pthread_join(pid2, NULL);     
    pthread_join(pid3, NULL);     
    return 0;
}

cat /proc/{pid}/maps,可以看到[heap]下面连续的7f341157f000-7f3411d7f000 rw-p,7f3411d80000-7f3412580000 rw-p,7f3412581000-7f3412d81000 rw-p,这三个位置对应于三个线程栈,大小都刚好是8*2^20=8192kb=8mb(细心的你会发现这三段都会穿插一小段边界值用于分割这三个线程栈),从这里我们可以看到这三个线程栈都是固定大小8192kb(ulimit a中查看一致),其是从heap的顶部附近向下进程分配的

00400000-00401000 r-xp 00000000 fd:01 1326537                            /tmp/c/test_pthread/main
00600000-00601000 r--p 00000000 fd:01 1326537                            /tmp/c/test_pthread/main
00601000-00602000 rw-p 00001000 fd:01 1326537                            /tmp/c/test_pthread/main
0212d000-0214e000 rw-p 00000000 00:00 0                                  [heap]
7f341157e000-7f341157f000 ---p 00000000 00:00 0 
7f341157f000-7f3411d7f000 rw-p 00000000 00:00 0 
7f3411d7f000-7f3411d80000 ---p 00000000 00:00 0 
7f3411d80000-7f3412580000 rw-p 00000000 00:00 0 
7f3412580000-7f3412581000 ---p 00000000 00:00 0 
7f3412581000-7f3412d81000 rw-p 00000000 00:00 0 
7f3412d81000-7f3412f41000 r-xp 00000000 fd:01 402248                     /lib/x86_64-linux-gnu/libc-2.23.so
7f3412f41000-7f3413141000 ---p 001c0000 fd:01 402248                     /lib/x86_64-linux-gnu/libc-2.23.so
7f3413141000-7f3413145000 r--p 001c0000 fd:01 402248                     /lib/x86_64-linux-gnu/libc-2.23.so
7f3413145000-7f3413147000 rw-p 001c4000 fd:01 402248                     /lib/x86_64-linux-gnu/libc-2.23.so
7f3413147000-7f341314b000 rw-p 00000000 00:00 0 
7f341314b000-7f3413163000 r-xp 00000000 fd:01 402247                     /lib/x86_64-linux-gnu/libpthread-2.23.so
7f3413163000-7f3413362000 ---p 00018000 fd:01 402247                     /lib/x86_64-linux-gnu/libpthread-2.23.so
7f3413362000-7f3413363000 r--p 00017000 fd:01 402247                     /lib/x86_64-linux-gnu/libpthread-2.23.so
7f3413363000-7f3413364000 rw-p 00018000 fd:01 402247                     /lib/x86_64-linux-gnu/libpthread-2.23.so
7f3413364000-7f3413368000 rw-p 00000000 00:00 0 
7f3413368000-7f341338e000 r-xp 00000000 fd:01 402246                     /lib/x86_64-linux-gnu/ld-2.23.so
7f341357f000-7f3413583000 rw-p 00000000 00:00 0 
7f341358d000-7f341358e000 r--p 00025000 fd:01 402246                     /lib/x86_64-linux-gnu/ld-2.23.so
7f341358e000-7f341358f000 rw-p 00026000 fd:01 402246                     /lib/x86_64-linux-gnu/ld-2.23.so
7f341358f000-7f3413590000 rw-p 00000000 00:00 0 
7fffa7bf4000-7fffa7c15000 rw-p 00000000 00:00 0                          [stack]
7fffa7d05000-7fffa7d08000 r--p 00000000 00:00 0                          [vvar]
7fffa7d08000-7fffa7d0a000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

3.3 在stack底附近分配,ulimit -s unlimited(这里虽然设置了stack size为无限,但是实际上其并不是无限的,而也是固定大小的线程栈,大小为1mb)

ulimit -s unlimited 

core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 15661
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 65535
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) unlimited
cpu time               (seconds, -t) unlimited
max user processes              (-u) 15661
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

cat /proc/{pid}/maps,在[stack]向上看,会有三个2b4f20012000-2b4f20212000 rw-p,2b4f20213000-2b4f20413000 rw-p,2b4f20414000-2b4f20614000 rw-p,这三个的大小均为1mb,大小是一致的(你可以通过在程序中分配一个接近1mb的本地变量数据进行测试,会发现当前的线程栈并不是无限的,还是固定大小接近1mb左右)。因此对于这种情况,线程栈是分配在stack底附近,自底向上生长的。

00400000-00401000 r-xp 00000000 fd:01 1326537                            /tmp/c/test_pthread/main
00600000-00601000 r--p 00000000 fd:01 1326537                            /tmp/c/test_pthread/main
00601000-00602000 rw-p 00001000 fd:01 1326537                            /tmp/c/test_pthread/main
0180a000-0182b000 rw-p 00000000 00:00 0                                  [heap]
2b4f1f802000-2b4f1f828000 r-xp 00000000 fd:01 402246                     /lib/x86_64-linux-gnu/ld-2.23.so
2b4f1f832000-2b4f1f835000 rw-p 00000000 00:00 0 
2b4f1fa27000-2b4f1fa28000 r--p 00025000 fd:01 402246                     /lib/x86_64-linux-gnu/ld-2.23.so
2b4f1fa28000-2b4f1fa29000 rw-p 00026000 fd:01 402246                     /lib/x86_64-linux-gnu/ld-2.23.so
2b4f1fa29000-2b4f1fa2a000 rw-p 00000000 00:00 0 
2b4f1fa2a000-2b4f1fa42000 r-xp 00000000 fd:01 402247                     /lib/x86_64-linux-gnu/libpthread-2.23.so
2b4f1fa42000-2b4f1fc41000 ---p 00018000 fd:01 402247                     /lib/x86_64-linux-gnu/libpthread-2.23.so
2b4f1fc41000-2b4f1fc42000 r--p 00017000 fd:01 402247                     /lib/x86_64-linux-gnu/libpthread-2.23.so
2b4f1fc42000-2b4f1fc43000 rw-p 00018000 fd:01 402247                     /lib/x86_64-linux-gnu/libpthread-2.23.so
2b4f1fc43000-2b4f1fc47000 rw-p 00000000 00:00 0 
2b4f1fc47000-2b4f1fe07000 r-xp 00000000 fd:01 402248                     /lib/x86_64-linux-gnu/libc-2.23.so
2b4f1fe07000-2b4f20007000 ---p 001c0000 fd:01 402248                     /lib/x86_64-linux-gnu/libc-2.23.so
2b4f20007000-2b4f2000b000 r--p 001c0000 fd:01 402248                     /lib/x86_64-linux-gnu/libc-2.23.so
2b4f2000b000-2b4f2000d000 rw-p 001c4000 fd:01 402248                     /lib/x86_64-linux-gnu/libc-2.23.so
2b4f2000d000-2b4f20011000 rw-p 00000000 00:00 0 
2b4f20011000-2b4f20012000 ---p 00000000 00:00 0 
2b4f20012000-2b4f20212000 rw-p 00000000 00:00 0 
2b4f20212000-2b4f20213000 ---p 00000000 00:00 0 
2b4f20213000-2b4f20413000 rw-p 00000000 00:00 0 
2b4f20413000-2b4f20414000 ---p 00000000 00:00 0 
2b4f20414000-2b4f20614000 rw-p 00000000 00:00 0 
7ffd283e1000-7ffd28402000 rw-p 00000000 00:00 0                          [stack]
7ffd2842c000-7ffd2842f000 r--p 00000000 00:00 0                          [vvar]
7ffd2842f000-7ffd28431000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

四、总结

不管线程栈是在堆分配还是在栈分配,其都是固定大小的,有边界的。又因为其是被具体的线程所使用,因此其也可以说是线程私有的(这是在正常使用情况,但是如果你将其中一个线程的局部变量地址传递给另一个线程的也是可以间接访问的,但是正常情况是不会访问到其他线程的局部变量)

发布了140 篇原创文章 · 获赞 28 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/qq_16097611/article/details/82592873