轉載:http://www.cnitblog.com/tarius.wu/articles/2277.html
轉載:http://www.fansoo.com/blog/2011/kernel-threads-lightweight-processes-threads-and-linuxthreads-library-users/
內核線程
內核線程只運行在內核態,不受用戶態上下文的拖累。
- 處理器競爭:可以在全系統範圍內競爭處理器資源;
- 使用資源:唯一使用的資源是內核棧和上下文切換時保持寄存器的空間
- 調度:調度的開銷可能和進程自身差不多昂貴
- 同步效率:資源的同步和數據共享比整個進程的數據同步和共享要低一些。
輕量級進程
輕量級進程(LWP)是建立在內核之上並由內核支持的用戶線程,它是內核線程的高度抽象,每一個輕量級進程都與一個特定的內核線程關聯。內核線程只能由內核管理並像普通進程一樣被調度。
輕量級進程由clone()系統調用創建,參數是CLONE_VM,即與父進程是共享進程地址空間和系統資源。
與普通進程區別:LWP只有一個最小的執行上下文和調度程序所需的統計信息。
- 處理器競爭:因與特定內核線程關聯,因此可以在全系統範圍內競爭處理器資源
- 使用資源:與父進程共享進程地址空間
- 調度:像普通進程一樣調度
用戶線程
用戶線程是完全建立在用戶空間的線程庫,用戶線程的創建、調度、同步和銷燬全又庫函數在用戶空間完成,不需要內核的幫助。因此這種線程是極其低消耗和高效的。
- 處理器競爭:單純的用戶線程是建立在用戶空間,其對內核是透明的,因此其所屬進程單獨參與處理器的競爭,而進程的所有線程參與競爭該進程的資源。
- 使用資源:與所屬進程共享進程地址空間和系統資源。
- 調度:由在用戶空間實現的線程庫,在所屬進程內進行調度
Linux使用的線程庫
LinuxThreads是用戶空間的線程庫,所採用的是線程-進程1對1模型(即一個用戶線程對應一個輕量級進程,而一個輕量級進程對應一個特定的內核線程),將線程的調度等同於進程的調度,調度交由內核完成,而線程的創建、同步、銷燬由核外線程庫完成(LinuxThtreads已綁定到 GLIBC中發行)。
在LinuxThreads中,由專門的一個管理線程處理所有的線程管理工作。當進程第一次調用pthread_create()創建線程時就會先創建(clone())並啓動管理線程。後續進程pthread_create()創建線程時,都是管理線程作爲pthread_create()的調用者的子線程,通過調用clone()來創建用戶線程,並記錄輕量級進程號和線程id的映射關係,因此,用戶線程其實是管理線程的子線程。
LinuxThreads只支持調度範圍爲PTHREAD_SCOPE_SYSTEM的調度,默認的調度策略是SCHED_OTHER。
用戶線程調度策略也可修改成SCHED_FIFO或SCHED_RR方式,這兩種方式支持優先級爲0-99,而SCHED_OTHER只支持0。
- SCHED_OTHER 分時調度策略,
- SCHED_FIFO 實時調度策略,先到先服務
- SCHED_RR 實時調度策略,時間片輪轉
SCHED_OTHER是普通進程的,後兩個是實時進程的(一般的進程都是普通進程,系統中出現實時進程的機會很少)。SCHED_FIFO、 SCHED_RR優先級高於所有SCHED_OTHER的進程,所以只要他們能夠運行,在他們運行完之前,所有SCHED_OTHER的進程的都沒有得到執行的機會。
附: 基礎知識(線程和進程)
按照教科書上的定義,進程是資源管理的最小單位,線程是程序執行的最小單位。在操作系統設計上,從進程演化出線程,最主要的目的就是更好的支持SMP以及減小(進程/線程)上下文切換開銷。
無論按照怎樣的分法,一個進程至少需要一個線程作爲它的指令執行體,進程管理着資源(比如cpu、內存、文件等等),而將線程分配到某個cpu上執行。一個進程當然可以擁有多個線程,此時,如果進程運行在SMP機器上,它就可以同時使用多個cpu來執行各個線程,達到最大程度的並行,以提高效率;同時,即使是在單cpu的機器上,採用多線程模型來設計程序,正如當年採用多進程模型代替單進程模型一樣,使設計更簡潔、功能更完備,程序的執行效率也更高,例如採用多個線程響應多個輸入,而此時多線程模型所實現的功能實際上也可以用多進程模型來實現,而與後者相比,線程的上下文切換開銷就比進程要小多了,從語義上來說,同時響應多個輸入這樣的功能,實際上就是共享了除cpu以外的所有資源的。
針對線程模型的兩大意義,分別開發出了核心級線程和用戶級線程兩種線程模型,分類的標準主要是線程的調度者在覈內還是在覈外。前者更利於併發使用多處理器的資源,而後者則更多考慮的是上下文切換開銷。在目前的商用系統中,通常都將兩者結合起來使用,既提供核心線程以滿足smp系統的需要,也支持用線程庫的方式在用戶態實現另一套線程機制,此時一個核心線程同時成爲多個用戶態線程的調度者。正如很多技術一樣,”混合”通常都能帶來更高的效率,但同時也帶來更大的實現難度,出於”簡單”的設計思路,Linux從一開始就沒有實現混合模型的計劃,但它在實現上採用了另一種思路的”混合”。
在線程機制的具體實現上,可以在操作系統內核上實現線程,也可以在覈外實現,後者顯然要求核內至少實現了進程,而前者則一般要求在覈內同時也支持進程。核心級線程模型顯然要求前者的支持,而用戶級線程模型則不一定基於後者實現。這種差異,正如前所述,是兩種分類方式的標準不同帶來的。
當核內既支持進程也支持線程時,就可以實現線程-進程的”多對多”模型,即一個進程的某個線程由核內調度,而同時它也可以作爲用戶級線程池的調度者,選擇合適的用戶級線程在其空間中運行。這就是前面提到的”混合”線程模型,既可滿足多處理機系統的需要,也可以最大限度的減小調度開銷。絕大多數商業操作系統(如Digital Unix、Solaris、Irix)都採用的這種能夠完全實現POSIX1003.1c標準的線程模型。在覈外實現的線程又可以分爲”一對一”、”多對一”兩種模型,前者用一個核心進程(也許是輕量進程)對應一個線程,將線程調度等同於進程調度,交給核心完成,而後者則完全在覈外實現多線程,調度也在用戶態完成。後者就是前面提到的單純的用戶級線程模型的實現方式,顯然,這種核外的線程調度器實際上只需要完成線程運行棧的切換,調度開銷非常小,但同時因爲核心信號(無論是同步的還是異步的)都是以進程爲單位的,因而無法定位到線程,所以這種實現方式不能用於多處理器系統,而這個需求正變得越來越大,因此,在現實中,純用戶級線程的實現,除算法研究目的以外,幾乎已經消失了。
Linux內核只提供了輕量進程的支持,限制了更高效的線程模型的實現,但Linux着重優化了進程的調度開銷,一定程度上也彌補了這一缺陷。目前最流行的線程機制LinuxThreads所採用的就是線程-進程”一對一”模型,調度交給核心,而在用戶級實現一個包括信號處理在內的線程管理機制。 Linux-LinuxThreads的運行機制正是本文的描述重點。
Reference:
- http://blog.csdn.net/jack05/archive/2010/02/02/5281079.aspx
- http://www.ibm.com/developerworks/cn/linux/kernel/l-thread/index.html Linux 線程實現機制分析
- http://www.net.t-labs.tu-berlin.de/%7Egregor/tools/pthread-scheduling.html Thread Scheduling with pthreads under Linux and FreeBSD
- http://www.cnitblog.com/tarius.wu/articles/2277.html
- http://lwj8666.blog.163.com/blog/static/18966939200911295163799/ linux進程調度方法(SCHED_OTHER,SCHED_FIFO,SCHED_RR)
在現代操作系統中,進程支持多線程。進程是資源管理的最小單元;而線程是程序執行的最小單元。一個進程的組成實體可以分爲兩大部分:線程集合資源集。進程中的線程是動態的對象;代表了進程指令的執行。資源,包括地址空間、打開的文件、用戶信息等等,由進程內的線程共享。
線程有自己的私有數據:程序計數器,棧空間以及寄存器。
Why Thread?(傳統單線程進程的缺點)
<!--[if !supportLists]-->1. <!--[endif]-->現實中有很多需要併發處理的任務,如數據庫的服務器端、網絡服務器、大容量計算等。
<!--[if !supportLists]-->2. <!--[endif]-->傳統的UNIX進程是單線程的,單線程意味着程序必須是順序執行,不能併發;既在一個時刻只能運行在一個處理器上,因此不能充分利用多處理器框架的計算機。
<!--[if !supportLists]-->3.
<!--[endif]-->如果採用多進程的方法,則有如下問題:
a. fork 一個子進程的消耗是很大的,fork是一個昂貴的系統調用,即使使用現代的寫時複製(copy-on-write)技術。
b. 各個進程擁有自己獨立的地址空間,進程間的協作需要複雜的IPC技術,如消息傳遞和共享內存等。
多線程的優缺點
多線程的優點和缺點實際上是對立統一的。
支持多線程的程序(進程)可以取得真正的並行(parallelism),且由於共享進程的代碼和全局數據,故線程間的通信是方便的。它的缺點也是由於線程共享進程的地址空間,因此可能會導致競爭,因此對某一塊有多個線程要訪問的數據需要一些同步技術。
三種線程——內核線程、輕量級進程、用戶線程
內核線程
內核線程就是內核的分身,一個分身可以處理一件特定事情。這在處理異步事件如異步IO時特別有用。內核線程的使用是廉價的,唯一使用的資源就是內核棧和上下文切換時保存寄存器的空間。支持多線程的內核叫做多線程內核(Multi-Threads kernel )。
輕量級進程[*]
輕量級線程(LWP)是一種由內核支持的用戶線程。它是基於內核線程的高級抽象,因此只有先支持內核線程,纔能有LWP。每一個進程有一個或多個LWPs,每個LWP由一個內核線程支持。這種模型實際上就是恐龍書上所提到的一對一線程模型。在這種實現的操作系統中,LWP就是用戶線程。
由於每個LWP都與一個特定的內核線程關聯,因此每個LWP都是一個獨立的線程調度單元。即使有一個LWP在系統調用中阻塞,也不會影響整個進程的執行。
輕量級進程具有侷限性。首先,大多數LWP的操作,如建立、析構以及同步,都需要進行系統調用。系統調用的代價相對較高:需要在user mode和kernel mode中切換。其次,每個LWP都需要有一個內核線程支持,因此LWP要消耗內核資源(內核線程的棧空間)。因此一個系統不能支持大量的LWP。
注:
<!--[if !supportLists]-->1. <!--[endif]-->LWP的術語是借自於SVR4/MP和Solaris 2.x。
<!--[if !supportLists]-->2. <!--[endif]-->有些系統將LWP稱爲虛擬處理器。
<!--[if !supportLists]-->3. <!--[endif]-->將之稱爲輕量級進程的原因可能是:在內核線程的支持下,LWP是獨立的調度單元,就像普通的進程一樣。所以LWP的最大特點還是每個LWP都有一個內核線程支持。
用戶線程
LWP雖然本質上屬於用戶線程,但LWP線程庫是建立在內核之上的,LWP的許多操作都要進行系統調用,因此效率不高。而這裏的用戶線程指的是完全建立在用戶空間的線程庫,用戶線程的建立,同步,銷燬,調度完全在用戶空間完成,不需要內核的幫助。因此這種線程的操作是極其快速的且低消耗的。
上圖是最初的一個用戶線程模型,從中可以看出,進程中包含線程,用戶線程在用戶空間中實現,內核並沒有直接對用戶線程進程調度,內核的調度對象和傳統進程一樣,還是進程本身,內核並不知道用戶線程的存在。用戶線程之間的調度由在用戶空間實現的線程庫實現。
這種模型對應着恐龍書中提到的多對一線程模型,其缺點是一個用戶線程如果阻塞在系統調用中,則整個進程都將會阻塞。
加強版的用戶線程——用戶線程+LWP
這種模型對應着恐龍書中多對多模型。用戶線程庫還是完全建立在用戶空間中,因此用戶線程的操作還是很廉價,因此可以建立任意多需要的用戶線程。操作系統提供了LWP作爲用戶線程和內核線程之間的橋樑。LWP還是和前面提到的一樣,具有內核線程支持,是內核的調度單元,並且用戶線程的系統調用要通過LWP,因此進程中某個用戶線程的阻塞不會影響整個進程的執行。用戶線程庫將建立的用戶線程關聯到LWP上,LWP與用戶線程的數量不一定一致。當內核調度到某個LWP上時,此時與該LWP關聯的用戶線程就被執行。小結:
很多文獻中都認爲輕量級進程就是線程,實際上這種說法並不完全正確,從前面的分析中可以看到,只有在用戶線程完全由輕量級進程構成時,纔可以說輕量級進程就是線程。