淺析Linux線程中數據
來源:程序員人生 發布時間:2014-12-25 08:08:39 閱讀次數:2542次
本文首先概述了線程中有哪些數據私有的,和進程中哪些數據線程是同享的,然后詳細分析了線程在用戶空間中的數據,最后通過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
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈