本文首先概述了线程中有哪些数据私有的,和进程中哪些数据线程是同享的,然后详细分析了线程在用户空间中的数据,最后通过1个多线程程序来分析线程中的数据散布。
概述
线程包括了表示进程内履行环境必须的信息,其中包括进程中标识的线程ID、1组寄存器值、栈、调度优先级和策略、信号屏蔽字(每一个线程有自己的信号屏蔽字,但对某个信号的处理方式是进程中所有线程同享的)、errno变量(每一个线程都自己的局部errno)和线程私有数据。进程的所有信息对该进程的所有线程都是同享的,包括可履行的程叙文本、程序的全局内存和堆内存和文件描写符。原则上线程的私有数据其实不是真的私有,由于线程的特点就是同享地址空间,只是线程的私有空间就是1般而言通过正常手段不会触及其它线程空间的数据而已,如果通过非正常途径固然也是可以访问的。在Linux中,默许情况下,创建1个线程,在用户空间中分配的空间大小为8M,另外还有4K作为线程警戒区,内核中还会分配1个task_struct结构用于相应的线程。
线程用户空间中数据
Linux线程由Linux内核、glibc和libpthread这3种共同支持实现。在用户空间,和线程关系本身比较密切的,有下面3个部份:
1是类似于线程控制块(TCB)的数据结构,在用户空间中代表着1个线程的存在;
2是线程私有堆栈;
3是线程局部数据(TLS);
在多线程程序中,各个线程之间的大部份数据都是同享的,但上面3部份数据是各个线程独有的。这3种数据位于同1块内存中,是在创建线程的时候,用mmap系统调用分配出来的,要访问这块地址,需要通过gs寄存器,对同1个进程内的每个线程,gs寄存器指向的地址都是不1样的,这样可以保证各个线程之间不会相互干扰。这3块数据在内存散布上,大致以下:
-----------
pthread
-----------
TLS
-----------
Stack
-----------
上面说到,这块内存通过gs寄存器访问,那末gs寄存器指向这块地址的哪一个地方呢?是指向pthread结构的首地址。在调用pthread创建线程时,会调用mmap来为线程分配空间,但在mmap之前,会尝试在进程中查找有无现成的可用空间,这是由于在通常情况下,我们创建了1个线程,当线程运行完后退出时,其占用的空间并没有释放,所以如果A线程退出后,我们又需要创建1个新线程B,那末我们就能够看看A线程的堆栈空间是不是满足要求,满足要求的话我们就直接用了。为了线程退出时,释放其所占用的空间,有两种方法,1种是在主线程中调用pthread_join;另外一种方法是线程创建时指定detach属性或创建后在新的线程中调用pthread_detach(pthread_self()),使得线程退出时,自动释放所占用的资源。注意这里释放的只是内核空间中所占用的资源(比如task_struct),而在用户空间中,线程所占用的资源(即在堆上用mmap分配的空间)依然是没有释放的。下面来看这3个部份分别包括甚么数据:
phtread部份保存的是1个类型为pthread的结构体,该结构体包括该线程控制块(Thread Control Block)字段、mutex相干字段、cleanup线程退出的善后工作相干字段、cancelhandling线程取消相干字段、援用线程私有数据相干字段、start_routine入口函数相干字段和寄存线程start_routine的返回值相干字段等线程属性相干信息。Linux中,返回的线程id就是这个pthread结构的地址,也就是gs寄存器中的值。
TLS是值线程本地存储,它主要保存了自定义_thread修饰符修饰的变量;1些库级别预定义的变量,比如errno;线程的私有数据实质也存在这部份。
Stack就是线程履行运行时所用的栈,比如线程中的局部变量就在这部份。下面通过1个多线程程序例子来看线程中数据散布,代码以下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/syscall.h>
#include <assert.h>
#define gettid() syscall(__NR_gettid) /*get LWP ID */
pthread_key_t key;
__thread int count = 42;
__thread unsigned long long count2 ;
static __thread int count3;
/*thread key destructor*/
void keydestr(void* string)
{
printf("destructor excuted in thread %p,address (%p) param=%s
",pthread_self(),string,string);
free(string);
}
void * thread1(void *arg)
{
int b;
pthread_t tid=pthread_self();
size_t size = 8;
int autovar = 0;
static staticvar = 1;
printf("In thread1, autovaraddress = %p, staticvaraddress = %p
", &autovar, &staticvar);
printf("In thread1, tid = %p, gettid = %d
",tid,gettid());
char* key_content = ( char* )malloc(size);
if(key_content != NULL)
{
strcpy(key_content,"maximus0");
}
pthread_setspecific(key,(void *)key_content);
count = 1024;
count2 = 2048;
count3 = 4096;
printf("In thread1, tid=%p, count(%p) = %8d, count2(%p) = %6llu, count3(%p) = %6d
",tid,&count,count,&count2,count2,&count3,count3);
sleep(2);
printf("thread1 %p keyselfaddress = %p, returns keyaddress = %p
",tid,&key, pthread_getspecific(key));
sleep(30);
printf("thread1 exit
");
}
void * thread2(void *arg)
{
int b;
pthread_t tid=pthread_self();
size_t size = 8;
int autovar = 0;
static staticvar = 1;
printf("In thread2, autovaraddress = %p, staticvaraddress = %p
", &autovar, &staticvar);
printf("In thread2, tid = %p, gettid = %d
",tid,gettid());
char* key_content = ( char* )malloc(size);
if(key_content != NULL)
{
strcpy(key_content,"ABCDEFG");
}
pthread_setspecific(key,(void *)key_content);
count = 1025;
count2 = 2049;
count3 = 4097;
printf("In thread2, tid=%p, count(%p) = %8d, count2(%p) = %6llu, count3(%p) = %6d
",tid,&count,count,&count2,count2,&count3,count3);
sleep(1);
printf("thread2 %p keyselfaddress = %p, returns keyaddress = %p
",tid,&key, pthread_getspecific(key));
sleep(50);
printf("thread2 exit
");
}
int main(void)
{
int b;
int autovar = 0;
static staticvar = 1;
pthread_t tid1,tid2;
printf("start,pid=%d
",getpid());
printf("In main, autovaraddress = %p, staticvaraddress = %p
", &autovar, &staticvar);
pthread_key_create(&key,keydestr);
pthread_create(&tid1,NULL,thread1,NULL);
pthread_create(&tid2,NULL,thread2,NULL);
printf("In main, pthread_create tid1 = %p
",tid1);
printf("In main, pthread_create tid2 = %p
",tid2);
if(pthread_join(tid2,NULL) == 0)
{
printf("In main,pthread_join thread2 success!
");
sleep(5);
}
pthread_key_delete(key);
printf("main thread exit
");
return 0;
}
编译并运行程序,结果以下:
$gcc -Wall -lpthread -o hack_thread_data hack_thread_data.c
$./hack_thread_data
start,pid=52168
In main, autovaraddress = 0x7fffd3eceea8, staticvaraddress = 0x601650
In main, pthread_create tid1 = 0x7ffe4baee700
In main, pthread_create tid2 = 0x7ffe4b2ed700
In thread2, autovaraddress = 0x7ffe4b2eceb0, staticvaraddress = 0x601654
In thread2, tid = 0x7ffe4b2ed700, gettid = 52170
In thread2, tid=0x7ffe4b2ed700, count(0x7ffe4b2ed6e8) = 1025, count2(0x7ffe4b2ed6f0) = 2049, count3(0x7ffe4b2ed6f8) = 4097
In thread1, autovaraddress = 0x7ffe4baedeb0, staticvaraddress = 0x601658
In thread1, tid = 0x7ffe4baee700, gettid = 52169
In thread1, tid=0x7ffe4baee700, count(0x7ffe4baee6e8) = 1024, count2(0x7ffe4baee6f0) = 2048, count3(0x7ffe4baee6f8) = 4096
thread2 0x7ffe4b2ed700 returns keyaddress = 0x1598270
thread1 0x7ffe4baee700 returns keyaddress = 0x1598290
thread1 exit
destructor excuted in thread 0x7ffe4baee700,address (0x1598290) param=maximus0
thread2 exit
destructor excuted in thread 0x7ffe4b2ed700,address (0x1598270) param=ABCDEFG
In main,pthread_join thread2 success!
main thread exit
在线程1结束之前、线程1结束以后、线程2结束之前和线程结束以后,使用命令cat /proc/52168/maps都得到进程的地址空间都是以下(这说明线程退出后(即便detach这个线程),线程在用户空间所占用的空间其实不会释放):
从地址空间可以看出:
1)创建的线程和主线程在地址空间的位置。线程中的局部变量都在相应线程的栈空间,而线程的静态变量都是在.data节,线程动态分配的空间都是在堆(heap)上。
2)__thread声明的变量每个线程有1份独立实体,各个线程的值互不干扰。可以用来修饰那些带有全局性且值可能变,但是又不值得用全局变量保护的变量。
3)通过在线程调用syscall(__NR_gettid),可以取得每一个线程在内核中对应的进程ID。如果直接在线程中调用getpid,则实质取得的是该线程所在的线程组tgid。线程在内核中对应的进程ID,也能够通过命令ps axj -L来查看,其当选项-L会增加显示LWP列,即线程对应的实质PID,或使用命令top,然后按下H(注意是大写)键,也是显示所有的线程。
4)线程id实质是线程栈中的某个地址。
5)线程在用户空间分两部份,权限---p部份的空间实质上是线程的警容缓冲区,这个缓冲区的大小,可以在创建线程时通过修改线程属性guardsize的值来指定,这个值控制着线程栈末尾以后用以免栈溢出的扩大内存大小,这个值默许值为PAGESIZE字节。当前线程的栈空间的大小也能够在创建线程时指定。默许线程栈空间的大小为8M,比如thread2就是7ffe4aaee000⑺ffe4b2ee000,即大小为8M,警容缓冲区默许大小为4KB,比如thread2就是7ffe4aaed000⑺ffe4aaee000。
6)在某个线程中使用sleep,只会让当前线程阻塞,不会影响其他线程。
参考资料
http://blog.csdn.net/liuxuejiang158blog/article/details/14100897
http://www.linuxsir.org/bbs/thread317267.html
http://javadino.blog.sohu.com/74292914.html
http://blog.chinaunix.net/uid⑵4774106-id⑶650136.html
http://blog.chinaunix.net/uid⑵4774106-id⑶651266.html
http://blog.csdn.net/dog250/article/details/7704898
http://www.longene.org/forum/viewtopic.php?f=17&t=414
http://www.longene.org/forum/viewtopic.php?f=17&t=429
http://www.longene.org/forum/viewtopic.php?f=17&t=441