一.基礎知識:線(xiàn)程和進(jìn)程
按照教科書(shū)上的定義,進(jìn)程是資源管理的最小單位,線(xiàn)程是程序執行的最小單位。在操作系統設計上,從進(jìn)程演化出線(xiàn)程,最主要的目的就是更好的支持SMP以及減?。ㄟM(jìn)程/線(xiàn)程)上下文切換開(kāi)銷(xiāo)。
無(wú)論按照怎樣的分法,一個(gè)進(jìn)程至少需要一個(gè)線(xiàn)程作為它的指令執行體,進(jìn)程管理著(zhù)資源(比如cpu、內存、文件等等),而將線(xiàn)程分配到某個(gè)cpu上執行。一個(gè)進(jìn)程當然可以擁有多個(gè)線(xiàn)程,此時(shí),如果進(jìn)程運行在SMP機器上,它就可以同時(shí)使用多個(gè)cpu來(lái)執行各個(gè)線(xiàn)程,達到最大程度的并行,以提高效率;同時(shí),即使是在單cpu的機器上,采用多線(xiàn)程模型來(lái)設計程序,正如當年采用多進(jìn)程模型代替單進(jìn)程模型一樣,使設計更簡(jiǎn)潔、功能更完備,程序的執行效率也更高,例如采用多個(gè)線(xiàn)程響應多個(gè)輸入,而此時(shí)多線(xiàn)程模型所實(shí)現的功能實(shí)際上也可以用多進(jìn)程模型來(lái)實(shí)現,而與后者相比,線(xiàn)程的上下文切換開(kāi)銷(xiāo)就比進(jìn)程要小多了,從語(yǔ)義上來(lái)說(shuō),同時(shí)響應多個(gè)輸入這樣的功能,實(shí)際上就是共享了除cpu以外的所有資源的。
針對線(xiàn)程模型的兩大意義,分別開(kāi)發(fā)出了核心級線(xiàn)程和用戶(hù)級線(xiàn)程兩種線(xiàn)程模型,分類(lèi)的標準主要是線(xiàn)程的調度者在核內還是在核外。前者更利于并發(fā)使用多處理器的資源,而后者則更多考慮的是上下文切換開(kāi)銷(xiāo)。在目前的商用系統中,通常都將兩者結合起來(lái)使用,既提供核心線(xiàn)程以滿(mǎn)足smp系統的需要,也支持用線(xiàn)程庫的方式在用戶(hù)態(tài)實(shí)現另一套線(xiàn)程機制,此時(shí)一個(gè)核心線(xiàn)程同時(shí)成為多個(gè)用戶(hù)態(tài)線(xiàn)程的調度者。正如很多技術(shù)一樣,"混合"通常都能帶來(lái)更高的效率,但同時(shí)也帶來(lái)更大的實(shí)現難度,出于"簡(jiǎn)單"的設計思路,Linux從一開(kāi)始就沒(méi)有實(shí)現混合模型的計劃,但它在實(shí)現上采用了另一種思路的"混合"。
在線(xiàn)程機制的具體實(shí)現上,可以在操作系統內核上實(shí)現線(xiàn)程,也可以在核外實(shí)現,后者顯然要求核內至少實(shí)現了進(jìn)程,而前者則一般要求在核內同時(shí)也支持進(jìn)程。核心級線(xiàn)程模型顯然要求前者的支持,而用戶(hù)級線(xiàn)程模型則不一定基于后者實(shí)現。這種差異,正如前所述,是兩種分類(lèi)方式的標準不同帶來(lái)的。
當核內既支持進(jìn)程也支持線(xiàn)程時(shí),就可以實(shí)現線(xiàn)程-進(jìn)程的"多對多"模型,即一個(gè)進(jìn)程的某個(gè)線(xiàn)程由核內調度,而同時(shí)它也可以作為用戶(hù)級線(xiàn)程池的調度者,選擇合適的用戶(hù)級線(xiàn)程在其空間中運行。這就是前面提到的"混合"線(xiàn)程模型,既可滿(mǎn)足多處理機系統的需要,也可以最大限度的減小調度開(kāi)銷(xiāo)。絕大多數商業(yè)操作系統(如Digital Unix、Solaris、Irix)都采用的這種能夠完全實(shí)現POSIX1003.1c標準的線(xiàn)程模型。在核外實(shí)現的線(xiàn)程又可以分為"一對一"、"多對一"兩種模型,前者用一個(gè)核心進(jìn)程(也許是輕量進(jìn)程)對應一個(gè)線(xiàn)程,將線(xiàn)程調度等同于進(jìn)程調度,交給核心完成,而后者則完全在核外實(shí)現多線(xiàn)程,調度也在用戶(hù)態(tài)完成。后者就是前面提到的單純的用戶(hù)級線(xiàn)程模型的實(shí)現方式,顯然,這種核外的線(xiàn)程調度器實(shí)際上只需要完成線(xiàn)程運行棧的切換,調度開(kāi)銷(xiāo)非常小,但同時(shí)因為核心信號(無(wú)論是同步的還是異步的)都是以進(jìn)程為單位的,因而無(wú)法定位到線(xiàn)程,所以這種實(shí)現方式不能用于多處理器系統,而這個(gè)需求正變得越來(lái)越大,因此,在現實(shí)中,純用戶(hù)級線(xiàn)程的實(shí)現,除算法研究目的以外,幾乎已經(jīng)消失了。
Linux內核只提供了輕量進(jìn)程的支持,限制了更高效的線(xiàn)程模型的實(shí)現,但Linux著(zhù)重優(yōu)化了進(jìn)程的調度開(kāi)銷(xiāo),一定程度上也彌補了這一缺陷。目前最流行的線(xiàn)程機制LinuxThreads所采用的就是線(xiàn)程-進(jìn)程"一對一"模型,調度交給核心,而在用戶(hù)級實(shí)現一個(gè)包括信號處理在內的線(xiàn)程管理機制。Linux-LinuxThreads的運行機制正是本文的描述重點(diǎn)。
二.Linux 2.4內核中的輕量進(jìn)程實(shí)現
最初的進(jìn)程定義都包含程序、資源及其執行三部分,其中程序通常指代碼,資源在操作系統層面上通常包括內存資源、IO資源、信號處理等部分,而程序的執行通常理解為執行上下文,包括對cpu的占用,后來(lái)發(fā)展為線(xiàn)程。在線(xiàn)程概念出現以前,為了減小進(jìn)程切換的開(kāi)銷(xiāo),操作系統設計者逐漸修正進(jìn)程的概念,逐漸允許將進(jìn)程所占有的資源從其主體剝離出來(lái),允許某些進(jìn)程共享一部分資源,例如文件、信號,數據內存,甚至代碼,這就發(fā)展出輕量進(jìn)程的概念。Linux內核在2.0.x版本就已經(jīng)實(shí)現了輕量進(jìn)程,應用程序可以通過(guò)一個(gè)統一的clone()系統調用接口,用不同的參數指定創(chuàng )建輕量進(jìn)程還是普通進(jìn)程。在內核中,clone()調用經(jīng)過(guò)參數傳遞和解釋后會(huì )調用do_fork(),這個(gè)核內函數同時(shí)也是fork()、vfork()系統調用的最終實(shí)現:
int do_fork(unsigned long clone_flags, unsigned long stack_start,
struct pt_regs *regs, unsigned long stack_size)
其中的clone_flags取自以下宏的"或"值:
#define CSIGNAL 0x000000ff /* signal mask to be sent at exit */
#define CLONE_VM 0x00000100 /* set if VM shared between processes */
#define CLONE_FS 0x00000200 /* set if fs info shared between processes */
#define CLONE_FILES 0x00000400 /* set if open files shared between processes */
#define CLONE_SIGHAND 0x00000800 /* set if signal handlers and blocked signals shared */
#define CLONE_PID 0x00001000 /* set if pid shared */
#define CLONE_PTRACE 0x00002000 /* set if we want to let tracing continue on the child too */
#define CLONE_VFORK 0x00004000 /* set if the parent wants the child to wake it up on mm_release */
#define CLONE_PARENT 0x00008000 /* set if we want to have the same parent as the cloner */
#define CLONE_THREAD 0x00010000 /* Same thread group? */
#define CLONE_NEWNS 0x00020000 /* New namespace group? */
#define CLONE_SIGNAL (CLONE_SIGHAND | CLONE_THREAD)
在do_fork()中,不同的clone_flags將導致不同的行為,對于LinuxThreads,它使用(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND)參數來(lái)調用clone()創(chuàng )建"線(xiàn)程",表示共享內存、共享文件系統訪(fǎng)問(wèn)計數、共享文件描述符表,以及共享信號處理方式。本節就針對這幾個(gè)參數,看看Linux內核是如何實(shí)現這些資源的共享的。
1.CLONE_VM
do_fork()需要調用copy_mm()來(lái)設置task_struct中的mm和active_mm項,這兩個(gè)mm_struct數據與進(jìn)程所關(guān)聯(lián)的內存空間相對應。如果do_fork()時(shí)指定了CLONE_VM開(kāi)關(guān),copy_mm()將把新的task_struct中的mm和active_mm設置成與current的相同,同時(shí)提高該mm_struct的使用者數目(mm_struct::mm_users)。也就是說(shuō),輕量級進(jìn)程與父進(jìn)程共享內存地址空間,由下圖示意可以看出mm_struct在進(jìn)程中的地位:
2.CLONE_FS
task_struct中利用fs(struct fs_struct *)記錄了進(jìn)程所在文件系統的根目錄和當前目錄信息,do_fork()時(shí)調用copy_fs()復制了這個(gè)結構;而對于輕量級進(jìn)程則僅增加fs->count計數,與父進(jìn)程共享相同的fs_struct。也就是說(shuō),輕量級進(jìn)程沒(méi)有獨立的文件系統相關(guān)的信息,進(jìn)程中任何一個(gè)線(xiàn)程改變當前目錄、根目錄等信息都將直接影響到其他線(xiàn)程。
3.CLONE_FILES
一個(gè)進(jìn)程可能打開(kāi)了一些文件,在進(jìn)程結構task_struct中利用files(struct files_struct *)來(lái)保存進(jìn)程打開(kāi)的文件結構(struct file)信息,do_fork()中調用了copy_files()來(lái)處理這個(gè)進(jìn)程屬性;輕量級進(jìn)程與父進(jìn)程是共享該結構的,copy_files()時(shí)僅增加files->count計數。這一共享使得任何線(xiàn)程都能訪(fǎng)問(wèn)進(jìn)程所維護的打開(kāi)文件,對它們的操作會(huì )直接反映到進(jìn)程中的其他線(xiàn)程。
4.CLONE_SIGHAND
每一個(gè)Linux進(jìn)程都可以自行定義對信號的處理方式,在task_struct中的sig(struct signal_struct)中使用一個(gè)struct k_sigaction結構的數組來(lái)保存這個(gè)配置信息,do_fork()中的copy_sighand()負責復制該信息;輕量級進(jìn)程不進(jìn)行復制,而僅僅增加signal_struct::count計數,與父進(jìn)程共享該結構。也就是說(shuō),子進(jìn)程與父進(jìn)程的信號處理方式完全相同,而且可以相互更改。
do_fork()中所做的工作很多,在此不詳細描述。對于SMP系統,所有的進(jìn)程fork出來(lái)后,都被分配到與父進(jìn)程相同的cpu上,一直到該進(jìn)程被調度時(shí)才會(huì )進(jìn)行cpu選擇。
盡管Linux支持輕量級進(jìn)程,但并不能說(shuō)它就支持核心級線(xiàn)程,因為L(cháng)inux的"線(xiàn)程"和"進(jìn)程"實(shí)際上處于一個(gè)調度層次,共享一個(gè)進(jìn)程標識符空間,這種限制使得不可能在Linux上實(shí)現完全意義上的POSIX線(xiàn)程機制,因此眾多的Linux線(xiàn)程庫實(shí)現嘗試都只能盡可能實(shí)現POSIX的絕大部分語(yǔ)義,并在功能上盡可能逼近。
三.LinuxThread的線(xiàn)程機制
LinuxThreads是目前Linux平臺上使用最為廣泛的線(xiàn)程庫,由Xavier Leroy (Xavier.Leroy@inria.fr)負責開(kāi)發(fā)完成,并已綁定在GLIBC中發(fā)行。它所實(shí)現的就是基于核心輕量級進(jìn)程的"一對一"線(xiàn)程模型,一個(gè)線(xiàn)程實(shí)體對應一個(gè)核心輕量級進(jìn)程,而線(xiàn)程之間的管理在核外函數庫中實(shí)現。
1.線(xiàn)程描述數據結構及實(shí)現限制
LinuxThreads定義了一個(gè)struct _pthread_descr_struct數據結構來(lái)描述線(xiàn)程,并使用全局數組變量__pthread_handles來(lái)描述和引用進(jìn)程所轄線(xiàn)程。在__pthread_handles中的前兩項,LinuxThreads定義了兩個(gè)全局的系統線(xiàn)程:__pthread_initial_thread和__pthread_manager_thread,并用__pthread_main_thread表征__pthread_manager_thread的父線(xiàn)程(初始為_(kāi)_pthread_initial_thread)。
struct _pthread_descr_struct是一個(gè)雙環(huán)鏈表結構,__pthread_manager_thread所在的鏈表僅包括它一個(gè)元素,實(shí)際上,__pthread_manager_thread是一個(gè)特殊線(xiàn)程,LinuxThreads僅使用了其中的errno、p_pid、p_priority等三個(gè)域。而__pthread_main_thread所在的鏈則將進(jìn)程中所有用戶(hù)線(xiàn)程串在了一起。經(jīng)過(guò)一系列pthread_create()之后形成的__pthread_handles數組將如下圖所示:
圖2 __pthread_handles數組結構
新創(chuàng )建的線(xiàn)程將首先在__pthread_handles數組中占據一項,然后通過(guò)數據結構中的鏈指針連入以__pthread_main_thread為首指針的鏈表中。這個(gè)鏈表的使用在介紹線(xiàn)程的創(chuàng )建和釋放的時(shí)候將提到。
LinuxThreads遵循POSIX1003.1c標準,其中對線(xiàn)程庫的實(shí)現進(jìn)行了一些范圍限制,比如進(jìn)程最大線(xiàn)程數,線(xiàn)程私有數據區大小等等。在LinuxThreads的實(shí)現中,基本遵循這些限制,但也進(jìn)行了一定的改動(dòng),改動(dòng)的趨勢是放松或者說(shuō)擴大這些限制,使編程更加方便。這些限定宏主要集中在sysdeps/unix/sysv/linux/bits/local_lim.h(不同平臺使用的文件位置不同)中,包括如下幾個(gè):
每進(jìn)程的私有數據key數,POSIX定義_POSIX_THREAD_KEYS_MAX為128,LinuxThreads使用PTHREAD_KEYS_MAX,1024;私有數據釋放時(shí)允許執行的操作數,LinuxThreads與POSIX一致,定義PTHREAD_DESTRUCTOR_ITERATIONS為4;每進(jìn)程的線(xiàn)程數,POSIX定義為64,LinuxThreads增大到1024(PTHREAD_THREADS_MAX);線(xiàn)程運行棧最小空間大小,POSIX未指定,LinuxThreads使用PTHREAD_STACK_MIN,16384(字節)。
2.管理線(xiàn)程
"一對一"模型的好處之一是線(xiàn)程的調度由核心完成了,而其他諸如線(xiàn)程取消、線(xiàn)程間的同步等工作,都是在核外線(xiàn)程庫中完成的。在LinuxThreads中,專(zhuān)門(mén)為每一個(gè)進(jìn)程構造了一個(gè)管理線(xiàn)程,負責處理線(xiàn)程相關(guān)的管理工作。當進(jìn)程第一次調用pthread_create()創(chuàng )建一個(gè)線(xiàn)程的時(shí)候就會(huì )創(chuàng )建(__clone())并啟動(dòng)管理線(xiàn)程。
在一個(gè)進(jìn)程空間內,管理線(xiàn)程與其他線(xiàn)程之間通過(guò)一對"管理管道(manager_pipe[2])"來(lái)通訊,該管道在創(chuàng )建管理線(xiàn)程之前創(chuàng )建,在成功啟動(dòng)了管理線(xiàn)程之后,管理管道的讀端和寫(xiě)端分別賦給兩個(gè)全局變量__pthread_manager_reader和__pthread_manager_request,之后,每個(gè)用戶(hù)線(xiàn)程都通過(guò)__pthread_manager_request向管理線(xiàn)程發(fā)請求,但管理線(xiàn)程本身并沒(méi)有直接使用__pthread_manager_reader,管道的讀端(manager_pipe[0])是作為_(kāi)_clone()的參數之一傳給管理線(xiàn)程的,管理線(xiàn)程的工作主要就是監聽(tīng)管道讀端,并對從中取出的請求作出反應。
創(chuàng )建管理線(xiàn)程的流程如下所示:
(全局變量pthread_manager_request初值為-1)
圖3 創(chuàng )建管理線(xiàn)程的流程
初始化結束后,在__pthread_manager_thread中記錄了輕量級進(jìn)程號以及核外分配和管理的線(xiàn)程id,2*PTHREAD_THREADS_MAX+1這個(gè)數值不會(huì )與任何常規用戶(hù)線(xiàn)程id沖突。管理線(xiàn)程作為pthread_create()的調用者線(xiàn)程的子線(xiàn)程運行,而pthread_create()所創(chuàng )建的那個(gè)用戶(hù)線(xiàn)程則是由管理線(xiàn)程來(lái)調用clone()創(chuàng )建,因此實(shí)際上是管理線(xiàn)程的子線(xiàn)程。(此處子線(xiàn)程的概念應該當作子進(jìn)程來(lái)理解。)
__pthread_manager()就是管理線(xiàn)程的主循環(huán)所在,在進(jìn)行一系列初始化工作后,進(jìn)入while(1)循環(huán)。在循環(huán)中,線(xiàn)程以2秒為timeout查詢(xún)(__poll())管理管道的讀端。在處理請求前,檢查其父線(xiàn)程(也就是創(chuàng )建manager的主線(xiàn)程)是否已退出,如果已退出就退出整個(gè)進(jìn)程。如果有退出的子線(xiàn)程需要清理,則調用pthread_reap_children()清理。
然后才是讀取管道中的請求,根據請求類(lèi)型執行相應操作(switch-case)。具體的請求處理,源碼中比較清楚,這里就不贅述了。
3.線(xiàn)程棧
在LinuxThreads中,管理線(xiàn)程的棧和用戶(hù)線(xiàn)程的棧是分離的,管理線(xiàn)程在進(jìn)程堆中通過(guò)malloc()分配一個(gè)THREAD_MANAGER_STACK_SIZE字節的區域作為自己的運行棧。
用戶(hù)線(xiàn)程的棧分配辦法隨著(zhù)體系結構的不同而不同,主要根據兩個(gè)宏定義來(lái)區分,一個(gè)是NEED_SEPARATE_REGISTER_STACK,這個(gè)屬性?xún)H在IA64平臺上使用;另一個(gè)是FLOATING_STACK宏,在i386等少數平臺上使用,此時(shí)用戶(hù)線(xiàn)程棧由系統決定具體位置并提供保護。與此同時(shí),用戶(hù)還可以通過(guò)線(xiàn)程屬性結構來(lái)指定使用用戶(hù)自定義的棧。因篇幅所限,這里只能分析i386平臺所使用的兩種棧組織方式:FLOATING_STACK方式和用戶(hù)自定義方式。
在FLOATING_STACK方式下,LinuxThreads利用mmap()從內核空間中分配8MB空間(i386系統缺省的最大??臻g大小,如果有運行限制(rlimit),則按照運行限制設置),使用mprotect()設置其中第一頁(yè)為非訪(fǎng)問(wèn)區。該8M空間的功能分配如下圖:
圖4 棧結構示意
低地址被保護的頁(yè)面用來(lái)監測棧溢出。
對于用戶(hù)指定的棧,在按照指針對界后,設置線(xiàn)程棧頂,并計算出棧底,不做保護,正確性由用戶(hù)自己保證。
不論哪種組織方式,線(xiàn)程描述結構總是位于棧頂緊鄰堆棧的位置。
4.線(xiàn)程id和進(jìn)程id
每個(gè)LinuxThreads線(xiàn)程都同時(shí)具有線(xiàn)程id和進(jìn)程id,其中進(jìn)程id就是內核所維護的進(jìn)程號,而線(xiàn)程id則由LinuxThreads分配和維護。
__pthread_initial_thread的線(xiàn)程id為PTHREAD_THREADS_MAX,__pthread_manager_thread的是2*PTHREAD_THREADS_MAX+1,第一個(gè)用戶(hù)線(xiàn)程的線(xiàn)程id為PTHREAD_THREADS_MAX+2,此后第n個(gè)用戶(hù)線(xiàn)程的線(xiàn)程id遵循以下公式:
tid=n*PTHREAD_THREADS_MAX+n+1
這種分配方式保證了進(jìn)程中所有的線(xiàn)程(包括已經(jīng)退出)都不會(huì )有相同的線(xiàn)程id,而線(xiàn)程id的類(lèi)型pthread_t定義為無(wú)符號長(cháng)整型(unsigned long int),也保證了有理由的運行時(shí)間內線(xiàn)程id不會(huì )重復。
從線(xiàn)程id查找線(xiàn)程數據結構是在pthread_handle()函數中完成的,實(shí)際上只是將線(xiàn)程號按PTHREAD_THREADS_MAX取模,得到的就是該線(xiàn)程在__pthread_handles中的索引。
5.線(xiàn)程的創(chuàng )建
在pthread_create()向管理線(xiàn)程發(fā)送REQ_CREATE請求之后,管理線(xiàn)程即調用pthread_handle_create()創(chuàng )建新線(xiàn)程。分配棧、設置thread屬性后,以pthread_start_thread()為函數入口調用__clone()創(chuàng )建并啟動(dòng)新線(xiàn)程。pthread_start_thread()讀取自身的進(jìn)程id號存入線(xiàn)程描述結構中,并根據其中記錄的調度方法配置調度。一切準備就緒后,再調用真正的線(xiàn)程執行函數,并在此函數返回后調用pthread_exit()清理現場(chǎng)。
6.LinuxThreads的不足
由于Linux內核的限制以及實(shí)現難度等等原因,LinuxThreads并不是完全POSIX兼容的,在它的發(fā)行README中有說(shuō)明。
1)進(jìn)程id問(wèn)題
這個(gè)不足是最關(guān)鍵的不足,引起的原因牽涉到LinuxThreads的"一對一"模型。
Linux內核并不支持真正意義上的線(xiàn)程,LinuxThreads是用與普通進(jìn)程具有同樣內核調度視圖的輕量級進(jìn)程來(lái)實(shí)現線(xiàn)程支持的。這些輕量級進(jìn)程擁有獨立的進(jìn)程id,在進(jìn)程調度、信號處理、IO等方面享有與普通進(jìn)程一樣的能力。在源碼閱讀者看來(lái),就是Linux內核的clone()沒(méi)有實(shí)現對CLONE_PID參數的支持。
在內核do_fork()中對CLONE_PID的處理是這樣的:
if (clone_flags & CLONE_PID) {
if (current->pid)
goto fork_out;
}
這段代碼表明,目前的Linux內核僅在pid為0的時(shí)候認可CLONE_PID參數,實(shí)際上,僅在SMP初始化,手工創(chuàng )建進(jìn)程的時(shí)候才會(huì )使用CLONE_PID參數。
按照POSIX定義,同一進(jìn)程的所有線(xiàn)程應該共享一個(gè)進(jìn)程id和父進(jìn)程id,這在目前的"一對一"模型下是無(wú)法實(shí)現的。
2)信號處理問(wèn)題
由于異步信號是內核以進(jìn)程為單位分發(fā)的,而LinuxThreads的每個(gè)線(xiàn)程對內核來(lái)說(shuō)都是一個(gè)進(jìn)程,且沒(méi)有實(shí)現"線(xiàn)程組",因此,某些語(yǔ)義不符合POSIX標準,比如沒(méi)有實(shí)現向進(jìn)程中所有線(xiàn)程發(fā)送信號,README對此作了說(shuō)明。
如果核心不提供實(shí)時(shí)信號,LinuxThreads將使用SIGUSR1和SIGUSR2作為內部使用的restart和cancel信號,這樣應用程序就不能使用這兩個(gè)原本為用戶(hù)保留的信號了。在Linux kernel 2.1.60以后的版本都支持擴展的實(shí)時(shí)信號(從_SIGRTMIN到_SIGRTMAX),因此不存在這個(gè)問(wèn)題。
某些信號的缺省動(dòng)作難以在現行體系上實(shí)現,比如SIGSTOP和SIGCONT,LinuxThreads只能將一個(gè)線(xiàn)程掛起,而無(wú)法掛起整個(gè)進(jìn)程。
3)線(xiàn)程總數問(wèn)題
LinuxThreads將每個(gè)進(jìn)程的線(xiàn)程最大數目定義為1024,但實(shí)際上這個(gè)數值還受到整個(gè)系統的總進(jìn)程數限制,這又是由于線(xiàn)程其實(shí)是核心進(jìn)程。
在kernel 2.4.x中,采用一套全新的總進(jìn)程數計算方法,使得總進(jìn)程數基本上僅受限于物理內存的大小,計算公式在kernel/fork.c的fork_init()函數中:
max_threads = mempages / (THREAD_SIZE/PAGE_SIZE) / 8
在i386上,THREAD_SIZE=2*PAGE_SIZE,PAGE_SIZE=2^12(4KB),mempages=物理內存大小/PAGE_SIZE,對于256M的內存的機器,mempages=256*2^20/2^12=256*2^8,此時(shí)最大線(xiàn)程數為4096。
但為了保證每個(gè)用戶(hù)(除了root)的進(jìn)程總數不至于占用一半以上物理內存,fork_init()中繼續指定:
init_task.rlim[RLIMIT_NPROC].rlim_cur = max_threads/2;
init_task.rlim[RLIMIT_NPROC].rlim_max = max_threads/2;
這些進(jìn)程數目的檢查都在do_fork()中進(jìn)行,因此,對于LinuxThreads來(lái)說(shuō),線(xiàn)程總數同時(shí)受這三個(gè)因素的限制。
4)管理線(xiàn)程問(wèn)題
管理線(xiàn)程容易成為瓶頸,這是這種結構的通??;同時(shí),管理線(xiàn)程又負責用戶(hù)線(xiàn)程的清理工作,因此,盡管管理線(xiàn)程已經(jīng)屏蔽了大部分的信號,但一旦管理線(xiàn)程死亡,用戶(hù)線(xiàn)程就不得不手工清理了,而且用戶(hù)線(xiàn)程并不知道管理線(xiàn)程的狀態(tài),之后的線(xiàn)程創(chuàng )建等請求將無(wú)人處理。
5)同步問(wèn)題
LinuxThreads中的線(xiàn)程同步很大程度上是建立在信號基礎上的,這種通過(guò)內核復雜的信號處理機制的同步方式,效率一直是個(gè)問(wèn)題。
6)其他POSIX兼容性問(wèn)題
Linux中很多系統調用,按照語(yǔ)義都是與進(jìn)程相關(guān)的,比如nice、setuid、setrlimit等,在目前的LinuxThreads中,這些調用都僅僅影響調用者線(xiàn)程。
7)實(shí)時(shí)性問(wèn)題
線(xiàn)程的引入有一定的實(shí)時(shí)性考慮,但LinuxThreads暫時(shí)不支持,比如調度選項,目前還沒(méi)有實(shí)現。不僅LinuxThreads如此,標準的Linux在實(shí)時(shí)性上考慮都很少。
聯(lián)系客服