前面提到進程和線程的區別,進程是資源分配的基本單位,線程是程序執行的基本單位。線程都屬於某個進程,而同一個進程下的不一樣線程分別有共享和獨享的數據,這裏再列舉一下:linux
同一進程內的全部線程除了共享全局變量外還共享:多線程
不過每一個線程有各自的:併發
errno
linux是遵循POSIX標準的操做系統,因此linux也須要提供遵循POSIX標準的線程實現。而最初linux系統中的線程機制則是LinuxThreads,在2.6版本以後又增長了NPTL(Native POSIX Thread Library)。函數
對於線程的實現機制來講,一般能夠選擇在內核內或者內核外實現,這兩種方式的區別在於線程是在覈內仍是核外調度。核內調度更利於併發使用多處理器的資源,內核能夠將同一個進程的不一樣線程調度到不一樣處理器上執行,當某個線程阻塞時,內核能夠將處理器調度到同一個進程的另外一個線程。而核外調度的上下文切換開銷更低,由於線程的切換不用陷入內核態。性能
當內核既支持進程也支持線程時,就能夠實現線程-進程的"多對多"模型,即一個進程的某個線程由核內調度,而同時它也能夠做爲用戶級線程池的調度者,選擇合適的用戶級線程在其空間中運行。這樣既可知足多處理機系統的須要,也能夠最大限度的減少調度開銷。優化
在內核外實現的線程又能夠分爲"一對一"、"多對一"兩種模型,前者用一個內核進程對應一個線程,將線程調度等同於進程調度,交給內核完成,然後者則徹底在覈外實現多線程,調度也在用戶態完成。後者就是前面提到的單純的用戶級線程模型的實現方式,顯然,這種核外的線程調度器實際上只須要完成線程運行棧的切換,調度開銷很是小,但同時由於內核信號都是以進程爲單位的,於是沒法定位到線程,因此這種實現方式不能用於多處理器系統。ui
linux內核只提供了輕量進程的支持,限制了更高效的線程模型的實現,但linux着重優化了進程的調度開銷,必定程度上也彌補了這一缺陷。目前linux的線程機制都採用的線程-進程"一對一"模型,調度交給內核,而在用戶級實現一個包括信號處理在內的線程管理機制。spa
linux內核在2.0.x版本就已經實現了輕量進程,應用程序能夠經過一個統一的clone
系統調用接口,用不一樣的參數指定建立輕量進程仍是普通進程。在內核中,clone
調用通過參數傳遞和解釋後會調用do_fork
,這個核內函數同時也是fork
、vfork
系統調用的最終實現:操作系統
intdo_fork(unsignedlongclone_flags,unsignedlongstack_start,structpt_regs*regs,unsignedlongstack_size);
複製代碼
在do_fork
中,不一樣的clone_flags將致使不一樣的行爲(共享不一樣的資源),下面列舉幾個flag的做用。線程
CLONE_VM 若是do_fork
時指定了CLONE_VM
開關,建立的輕量級進程的內存空間將會和父進程指向同一個地址,即建立的輕量級進程將與父進程共享內存地址空間。
CLONE_FS
若是do_fork
時指定了CLONE_FS
開關,對於輕量級進程則會與父進程共享相同的所在文件系統的根目錄和當前目錄信息。也就是說,輕量級進程沒有獨立的文件系統相關的信息,進程中任何一個線程改變當前目錄、根目錄等信息都將直接影響到其餘線程。
CLONE_FILES
若是do_fork
時指定了CLONE_FILES
開關,建立的輕量級進程與父進程將會共享已經打開的文件。這一共享使得任何線程都能訪問進程所維護的打開文件,對它們的操做會直接反映到進程中的其餘線程。
CLONE_SIGHAND
若是do_fork
時指定了CLONE_FILES
開關,輕量級進程與父進程將會共享對信號的處理方式。也就是說,子進程與父進程的信號處理方式徹底相同,並且能夠相互更改。
儘管linux支持輕量級進程,但並不能說它就支持內核線程,由於linux的"線程"和"進程"實際上處於一個調度層次,共享一個進程標識符空間,這種限制使得不可能在linux上實現徹底意義上的POSIX線程機制,所以衆多的linux線程庫實現嘗試都只能儘量實現POSIX的絕大部分語義,並在功能上儘量逼近。
LinuxThreads是linux平臺上使用過的一個線程庫。它所實現的就是基於內核輕量級進程的"一對一"線程模型,一個線程實體對應一個核心輕量級進程,而線程之間的管理在覈外函數庫中實現。對於LinuxThreads,它使用(CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND)
參數來調用clone
建立"線程",表示共享內存、共享文件系統訪問計數、共享文件描述符表,以及共享信號處理方式。
LinuxThreads最初的設計相信相關進程之間的上下文切換速度很快,所以每一個內核線程足以處理不少相關的用戶級線程。LinuxThreads很是出名的一個特性就是管理線程(manager thread)。在LinuxThreads中,專門爲每個進程構造了一個管理線程,負責處理線程相關的管理工做。當進程第一次調用pthread_create
建立一個線程的時候就會建立並啓動管理線程。
在一個進程空間內,管理線程與其餘線程之間經過一對"管理管道(manager_pipe[2])"來通信,該管道在建立管理線程以前建立,在成功啓動了管理線程以後,管理管道的讀端和寫端分別賦給兩個全局變量__pthread_manager_reader
和__pthread_manager_request
,以後,每一個用戶線程都經過__pthread_manager_request
向管理線程發請求,但管理線程自己並無直接使用__pthread_manager_reader
,管道的讀端(manager_pipe[0])是做爲__clone()
的參數之一傳給管理線程的,管理線程的工做主要就是監聽管道讀端,並對從中取出的請求做出反應。
管理線程在進行一系列初始化工做後,進入while(1)循環。在循環中,線程以2秒爲timeout查詢(__poll())管理管道的讀端。在處理請求前,檢查其父線程是否已退出,若是已退出就退出整個進程。若是有退出的子線程須要清理,則進行清理。而後纔是讀取管道中的請求,根據請求類型執行相應操做(switch-case)。
每一個LinuxThreads線程都同時具備線程id和進程id,其中進程id就是內核所維護的進程號,而線程id則由LinuxThreads分配和維護。
LinuxThreads的設計一般均可以很好地工做;可是在壓力很大的應用程序中,它的性能、可伸縮性和可用性都會存在問題。下面讓咱們來看一下LinuxThreads設計的一些侷限性:
clone
沒有實現對CLONE_PID參數的支持。按照POSIX定義,同一進程的全部線程應該共享一個進程id和父進程id,這在目前的"一對一"模型下是沒法實現的。NPTL(Native POSIX Thread Library)是linux線程的一個新實現,它克服了LinuxThreads的缺點,同時也符合POSIX的需求。與LinuxThreads相比,它在性能和穩定性方面都提供了重大的改進。與LinuxThreads同樣,NPTL也實現了一對一的模型。
NPTL出現的一部分緣由是對LinuxThreads進行改進,它設計目標以下:
NPTL總的來講採用了LinuxThreads相似的解決辦法,內核看到的依然是一個進程,新線程是經過clone()
系統調用產生的。與LinuxThreads相比,NPTL具備不少優勢:
futex
的新機制。futex
在共享內存區域上進行工做,所以能夠在進程之間進行共享,這樣就能夠提供進程間POSIX同步機制。咱們也能夠在進程之間共享一個futex
。這種行爲使得進程間同步成爲可能。實際上,NPTL包含了一個PTHREAD_PROCESS_SHARED
宏,使得開發人員可讓用戶級進程在不一樣進程的線程之間共享互斥鎖。LD_ASSUME_KERNEL
實現的。futex(Fast Userspace muTexes)意爲快速用戶區互斥,它是linux提供的一種同步(互斥)機制,特色是對於條件的判斷是發生在用戶空間的,在競爭不激烈的狀況下能有更好的性能表現。futex在2.6.x系列穩定版內核中出現。
futex由一塊可以被多個進程共享的內存空間(一個對齊後的整型變量)組成;這個整型變量的值可以經過彙編語言調用CPU提供的原子操做指令來增長或減小,而且一個進程能夠等待直到那個值變成正數。Futex 的操做幾乎所有在用戶空間完成;只有當操做結果不一致從而須要仲裁時,才須要進入操做系統內核空間執行。這種機制容許使用 futex 的鎖定原語有很是高的執行效率:因爲絕大多數的操做並不須要在多個進程之間進行仲裁,因此絕大多數操做均可以在應用程序空間執行,而不須要使用(相對高代價的)內核系統調用。