線程(LWP,light weight process)是輕量級的進程,本質仍是進程(在類unix環境下)。進程有獨立地址空間,擁有PCB;線程也有PCB,但沒有獨立的地址空間(共享)。在Linux下,線程是最小的執行單位;進程是最小分配資源單位,可看成是只有一個線程的進程。
(1)Linux內核線程實現原理
類Unix系統中,早期是沒有“線程”概念的,80年代才引入,借助進程機制實現出了線程的概念。因此在這類系統中,進程和線程關系密切。類unix操作系統和Windows操作系統的線程實現原理不一樣,類unix操作系統的線程是依賴于進程實現的。
輕量級進程(light-weight process),也有PCB,創建線程使用的底層函數和進程一樣,都是clone,即fork和pthread_creat調用的底層函數都是clone。剛fork之后,子進程與父進程的大多數內容都是相同的,遵從讀時共享,寫時復制的原則,即子進程有開辟屬于自己空間的能力,擁有自己獨立的進程地址空間;而進程在pthread_creat后,產生的線程卻無法從該地址空間中獨立出去,它們(同一個進程中的所有線程)共享這一個地址空間,當然它們所屬于自己的PCB,PCB是相互獨立的。同一個進程的所有線程的PCB都保存有相同的頁表,因此線程共享虛擬地址空間。
從內核里看進程和線程是一樣的,都有各自不同的PCB,但是PCB中指向內存資源的三級頁表是相同的,因此同一個進程中的多個線程在對同一個虛擬地址(線性地址)經過MMU變換為的物理地址都是同樣的,則它們共享同一個虛擬地址空間。但是,每個線程又必須有屬于自己的任務(指令集合,這些是各自獨立的),這些都保存在寄存器和棧中,它們又保存在各自的PCB中。所有的函數占據的地址空間都為棧空間(向下增長),整個棧空間都是由一個個棧幀組成。每一個函數有屬于自己的棧幀空間,棧幀空間里面存儲了函數的局部變量、函數的形參和臨時值。如進程中的某一個線程有屬于自己執行的函數:
int main ( )????????????
{
?? myprint( );
?? return 0;
}
void myprint( )
{
?? func( );
}
則main函數、myprint函數和func函數都位于棧空間(stack),每個函數都占據一個棧幀,如下:
三個棧幀空間連續分配,每個棧幀空間由ebp(頭)和esp(尾)共同決定,它們各自存儲在一個寄存器中,當main函數調用myprint函數時,此時兩個寄存器的值變為第二個棧幀的頭和尾,但是需要該函數調用完需要返回到main函數中,因此還需要把main函數的棧幀空間的頭尾保存起來,這個就是臨時值,保存在main函數的棧幀空間中。myprint函數調用func函數時同理,myprint棧幀空間的頭尾作為臨時值保存到myprint棧幀空間中。綜上線程可以看做是寄存器和棧的集合。
在內核空間也有一個棧空間,稱為內核棧,用于保存寄存器的值。如進程或者線程在切換時,需要保存寄存器的值,而這些值都保存在內核的棧空間中,因此每個線程也都有自己獨立的內核棧空間。局部變量都存儲于棧空間中(用戶棧空間),而全局變量存儲在寄存器中,切換時保存到了內核棧空間中。
進程可以蛻變成線程。一個進程創建一個線程時,自己本身也蛻變成為了一個線程。
在linux下,線程最是小的執行單位;進程是最小的分配資源單位。
三級映射:進程PCB→頁目錄(可看成數組,首地址位于PCB中)→頁表→物理頁面 →內存單元。
實際上,無論是創建進程的fork,還是創建線程的pthread_create,底層實現都是調用同一個內核函數clone。如果復制對方的地址空間,那么就產出一個“進程”;如果共享對方的地址空間,就產生一個“線程”。因此Linux內核是不區分進程和線程的。只在用戶層面上進行區分。所以,線程所有操作函數 pthread_* 是庫函數,而非系統調用。
(2)線程號與線程ID
查看某一個進程對應的所有線程的線程號(LWP號):ps –Lf pid;如下圖所示。線程號是Linux內核劃分給線程CPU時間輪片的依據。UID為線程所屬用戶ID;PID為線程對應的進程ID,PPID進程的父進程ID,LWP為線程號,NLWP為該進程中線程的數目(下面3291號進程的線程數為3)。注意區分線程號與線程ID,兩者不是一個概念。Linux內核就是根據線程號來劃分CPU時間輪片的,而線程的ID用于區分線程。
線程ID是進程內部識別標志(兩個進程間,線程ID允許相同,但是線程號必須不同,因為線程號是CPU時間片劃分依據);另外,線程ID明顯比線程號和進程ID大得多(可通過ps –Lf pid查看),而線程號和進程ID數量級一樣,且線程號和進程ID都不能相同,線程ID在不同進程中可以相同。
[root@localhost 01_pthread_test]# ps -Lf 3291
UID?? ?PID? ??PPID ?LWP? ?C ?NLWP ?STIME ??TTY???? STAT?? TIME CMD
gdm??? 3291?? 3283?? 3291? ??0? ?3 14:18 ??? ?Sl?? ?0:00 /usr/libexec/ibus-dconf
gdm?? ?3291?? 3283?? 3297? ??0?? 3 14:18 ??? ?Sl??? 0:00 /usr/libexec/ibus-dconf
gdm?? ?3291?? 3283?? 3298? ??0 ??3 14:18 ???? Sl??? 0:00 /usr/libexec/ibus-dconf
(3)線程共享資源
1.文件描述符表(共享打開的文件);
2.每種信號的處理方式;
3.當前工作目錄;
4.用戶ID和組ID;
5.內存地址空間 (.text/.data/.bss/heap(堆)/共享庫),即用戶空間中除了棧空間stack部分;
6.共享頁表。
(4)線程非共享資源
1.線程id;
2.處理器現場(寄存器)和棧指針(指向內核棧);
3.獨立的棧空間(用戶空間棧),即函數運行所需要的空間;
4.errno變量(全局變量),注意雖然位于.data段,但是非共享;
5.信號屏蔽字;
6.調度優先級。
(5)線程優缺點
優點:提高程序并發性;開銷小;數據通信、共享數據方便。
缺點:庫函數,不穩定;調試、編寫困難、gdb不支持;對信號支持不好。
優點相對突出,缺點均不是硬傷。Linux下由于實現方法導致進程、線程差別不是很大。