上一篇博客谈到了普通程序在内存中的分布,这篇博客谈谈文艺程序在内存中的分布。
首先缕一下概念吧。在linux下,进程(process)和线程(thread)并没有什么区别,都是一种执行环境(context of execution, COE),在linux下统称为task。
每个执行环境都有自己的状态,包括CPU状态,内存映射状态,权限状态(uid,pid)和各种各样的通信状态(打开的文件描述符,管道,信号处理程序等)。
而线程和进程的不同在于线程这种执行环境之间共享了很多状态,至于共享哪些状态不取决于书上说了什么(书上只说了一点点),而是取决于创建这个执行环境的函数提供了哪些选择。
在linux下,线程通过clone这个函数创建,而这个函数和fork一样,其实是用来创建进程的,但是clone在创建进程的时候,会指定调用这个函数的进程和新创建的进程之间共享的内容,比如说pid,代码段,文件描述符等等,所以看上去好像一个进程里面有很多股进程在并行运行,这些并行运行的进程后来有了个新名字:线程。所以大家不必太在意他们叫什么名字,就好比给皇上吃的胡萝卜叫“宫廷胡萝卜”一样,换个名字都是唬人的,只要搞清楚实质就好了。
而我们创建新线程(进程)也可以不和老进程共享那些内容,比如不共享文件描述符,这样线程(子进程)可以打开的文件数就不会受到父进程的限制了(通常一个进程可以打开的文件数是有限制的,可以使用ulimit -n查看,一般是1024);再比如不共享pid,这样新线程也有自己的pid(进程id),从这也可以看出线程其实就是进程。
概念缕清楚了,后面的内容还是回到默认写法,大家看到线程、进程,心里清楚其实就是COE就好了。
多线程程序和普通程序在内存中的的不同主要表现在栈的不同,每个线程一个栈,所以线程的局部变量不会受其他线程影响。而.text, .data, .bss, 等部分各个线程是共享的,所以线程间通信非常简单,直接在这些共享的内存中存取就可以了,所谓祸福相依,线程内操作这些共享内存中的数据的时候,也需要非常小心,要确保只有当前线程在操作全局变量(通过锁或者其他一些操作)。
再就没什么好说的了,感觉上一篇博客已经讲的比较清楚了,最后就是一个示例程序了:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <pthread.h> void *thread_foo_func(void *); void *thread_bar_func(void *); int global = 4; int main(){ int local = 8; int foo, bar; pthread_t fthread, bthread; foo = pthread_create(&fthread, NULL, thread_foo_func, (void *)&local); bar = pthread_create(&bthread, NULL, thread_bar_func, (void *)&local); if (foo != 0 || bar != 0){ printf("thread creation failed.\n"); return -1; } foo = pthread_join(fthread, NULL); bar = pthread_join(bthread, NULL); if (foo != 0 || bar != 0){ printf("thread join failed.\n"); return -2; } char i; scanf("%c", &i); return 0; } void *thread_foo_func(void *arg){ int foo_local = 16; printf("address of global %d: %x\n", global, &global); printf("address of main local %d: %x\n", *(int *)arg, arg); printf("address of foo local: %x\n", &foo_local); } void *thread_bar_func(void *arg){ int bar_local = 32; printf("address of global %d: %x\n", global, &global); printf("address of main local %d: %x\n", *(int *)arg, arg); printf("address of bar local: %x\n", &bar_local); }
address of global 4: 601040 address of main local 8: 619417d0 address of foo local: ddb36ebc address of global 4: 601040 address of main local 8: 619417d0 address of bar local: dd335ebc
cat /proc/2806/maps 00400000-00401000 r-xp 00000000 08:03 4080124 /home/yuduo/Workspace/C/sandbox/threaded 00600000-00601000 r--p 00000000 08:03 4080124 /home/yuduo/Workspace/C/sandbox/threaded 00601000-00602000 rw-p 00001000 08:03 4080124 /home/yuduo/Workspace/C/sandbox/threaded 00699000-006ba000 rw-p 00000000 00:00 0 [heap] 7fe7dcb36000-7fe7dcb37000 ---p 00000000 00:00 0 7fe7dcb37000-7fe7dd337000 rw-p 00000000 00:00 0 7fe7dd337000-7fe7dd338000 ---p 00000000 00:00 0 7fe7dd338000-7fe7ddb38000 rw-p 00000000 00:00 0 7fe7ddb38000-7fe7ddccd000 r-xp 00000000 08:03 4460234 /lib/x86_64-linux-gnu/libc-2.13.so 7fe7ddccd000-7fe7ddecc000 ---p 00195000 08:03 4460234 /lib/x86_64-linux-gnu/libc-2.13.so 7fe7ddecc000-7fe7dded0000 r--p 00194000 08:03 4460234 /lib/x86_64-linux-gnu/libc-2.13.so 7fe7dded0000-7fe7dded1000 rw-p 00198000 08:03 4460234 /lib/x86_64-linux-gnu/libc-2.13.so 7fe7dded1000-7fe7dded7000 rw-p 00000000 00:00 0 7fe7dded7000-7fe7ddeef000 r-xp 00000000 08:03 4460294 /lib/x86_64-linux-gnu/libpthread-2.13.so 7fe7ddeef000-7fe7de0ee000 ---p 00018000 08:03 4460294 /lib/x86_64-linux-gnu/libpthread-2.13.so 7fe7de0ee000-7fe7de0ef000 r--p 00017000 08:03 4460294 /lib/x86_64-linux-gnu/libpthread-2.13.so 7fe7de0ef000-7fe7de0f0000 rw-p 00018000 08:03 4460294 /lib/x86_64-linux-gnu/libpthread-2.13.so 7fe7de0f0000-7fe7de0f4000 rw-p 00000000 00:00 0 7fe7de0f4000-7fe7de115000 r-xp 00000000 08:03 4460221 /lib/x86_64-linux-gnu/ld-2.13.so 7fe7de2f2000-7fe7de2f5000 rw-p 00000000 00:00 0 7fe7de310000-7fe7de314000 rw-p 00000000 00:00 0 7fe7de314000-7fe7de315000 r--p 00020000 08:03 4460221 /lib/x86_64-linux-gnu/ld-2.13.so 7fe7de315000-7fe7de317000 rw-p 00021000 08:03 4460221 /lib/x86_64-linux-gnu/ld-2.13.so 7fff61923000-7fff61944000 rw-p 00000000 00:00 0 [stack] 7fff619ff000-7fff61a00000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
foo的局部变量在foo的栈里面,内存范围是:7fe7dd338000-7fe7ddb38000。
bar的局部变量在bar的栈里面,内存范围是:7fe7dcb37000-7fe7dd337000。
顺便说一下,pthread库使用mmap函数为新线程创建栈空间,然后传给clone,大家可以参照pthread库的源码练练怎么自己创建线程。