進程是程序在計算機上的一次執行活動。當你運行一個程序,你就啟動了一個進程。顯然,程序是
死的(靜態的),進程是活的(動態的)。進程可以分為系統進程和用戶進程。凡是用於完成操作系統的各種
功能的進程就是系統進程,它們就是處於運行狀態下的操作系統本身;用戶進程就是所有由你啟動的進程。進程是操作系統進行資源分配的單位。
在Windows下,進程又被細化為線程,也就是一個進程下有多個能獨立運行的更小的單位。
有關linux下進程與線程看過很多文章,我覺的這篇可以說最經典
一.基礎知識:線程和進程
按照教科書上的定義,進程是資源管理的最小單位,線程是程序執行的最小單位。在操作系統設計上,從進程演化出線程,最主要的目的就是更好的支持SMP以及減小(進程/線程)上下文切換開銷。
無論按照怎樣的分法,一個進程至少需要一個線程作為它的指令執行體,進程管理著資源(比如cpu、內存、文件等等),而將線程分配到某個cpu上執行。一個進程當然可以擁有多個線程,此時,如果進程運行在SMP機器上,它就可以同時使用多個cpu來執行各個線程,達到最大程度的並行,以提高效率;同時,即使是在單cpu的機器上,采用多線程模型來設計程序,正如當年采用多進程模型代替單進程模型一樣,使設計更簡潔、功能更完備,程序的執行效率也更高,例如采用多個線程響應多個輸入,而此時多線程模型所實現的功能實際上也可以用多進程模型來實現,而與後者相比,線程的上下文切換開銷就比進程要小多了,從語義上來說,同時響應多個輸入這樣的功能,實際上就是共享了除cpu以外的所有資源的。
針對線程模型的兩大意義,分別開發出了核心級線程和用戶級線程兩種線程模型,分類的標准主要是線程的調度者在核內還是在核外。前者更利於並發使用多處理器的資源,而後者則更多考慮的是上下文切換開銷。在目前的商用系統中,通常都將兩者結合起來使用,既提供核心線程以滿足smp系統的需要,也支持用線程庫的方式在用戶態實現另一套線程機制,此時一個核心線程同時成為多個用戶態線程的調度者。正如很多技術一樣,"混合"通常都能帶來更高的效率,但同時也帶來更大的實現難度,出於"簡單"的設計思路,Linux從一開始就沒有實現混合模型的計劃,但它在實現上采用了另一種思路的"混合"。
在線程機制的具體實現上,可以在操作系統內核上實現線程,也可以在核外實現,後者顯然要求核內至少實現了進程,而前者則一般要求在核內同時也支持進程。核心級線程模型顯然要求前者的支持,而用戶級線程模型則不一定基於後者實現。這種差異,正如前所述,是兩種分類方式的標准不同帶來的。
當核內既支持進程也支持線程時,就可以實現線程-進程的"多對多"模型,即一個進程的某個線程由核內調度,而同時它也可以作為用戶級線程池的調度者,選擇合適的用戶級線程在其空間中運行。這就是前面提到的"混合"線程模型,既可滿足多處理機系統的需要,也可以最大限度的減小調度開銷。絕大多數商業操作系統(如DigitalUnix、Solaris、Irix)都采用的這種能夠完全實現POSIX1003.1c標准的線程模型。在核外實現的線程又可以分為"一對一"、"多對一"兩種模型,前者用一個核心進程(也許是輕量進程)對應一個線程,將線程調度等同於進程調度,交給核心完成,而後者則完全在核外實現多線程,調度也在用戶態完成。後者就是前面提到的單純的用戶級線程模型的實現方式,顯然,這種核外的線程調度器實際上只需要完成線程運行棧的切換,調度開銷非常小,但同時因為核心信號(無論是同步的還是異步的)都是以進程為單位的,因而無法定位到線程,所以這種實現方式不能用於多處理器系統,而這個需求正變得越來越大,因此,在現實中,純用戶級線程的實現,除算法研究目的以外,幾乎已經消失了。
Linux內核只提供了輕量進程的支持,限制了更高效的線程模型的實現,但Linux著重優化了進程的調度開銷,一定程度上也彌補了這一缺陷。目前最流行的線程機制LinuxThreads所采用的就是線程-進程"一對一"模型,調度交給核心,而在用戶級實現一個包括信號處理在內的線程管理機制。Linux-LinuxThreads的運行機制正是本文的描述重點。
二.Linux2.4內核中的輕量進程實現
最初的進程定義都包含程序、資源及其執行三部分,其中程序通常指代碼,資源在操作系統層面上通常包括內存資源、IO資源、信號處理等部分,而程序的執行通常理解為執行上下文,包括對cpu的占用,後來發展為線程。在線程概念出現以前,為了減小進程切換的開銷,操作系統設計者逐漸修正進程的概念,逐漸允許將進程所占有的資源從其主體剝離出來,允許某些進程共享一部分資源,例如文件、信號,數據內存,甚至代碼,這就發展出輕量進程的概念。Linux內核在2.0.x版本就已經實現了輕量進程,應用程序可以通過一個統一的clone()系統調用接口,用不同的參數指定創建輕量進程還是普通進程。在內核中,clone()調用經過參數傳遞和解釋後會調用do_fork(),這個核內函數同時也是fork()、vfork()系統調用的最終實現:
<linux-2.4.20/kernel/fork.c>
intdo_fork(unsignedlongclone_flags,unsignedlongstack_start,
structpt_regs*regs,unsignedlongstack_size)
其中的clone_flags取自以下宏的"或"值:
<linux-2.4.20/include/linux/sched.h>
#defineCSIGNAL 0x000000ff /*signalmasktobesentatexit*/
#defineCLONE_VM 0x00000100 /*setifVMsharedbetweenprocesses*/
#defineCLONE_FS0x00000200 /*setiffsinfosharedbetweenprocesses*/
#defineCLONE_FILES0x00000400 /*setifopenfilessharedbetweenprocesses*/
#defineCLONE_SIGHAND 0x00000800 /*setifsignalhandlersandblockedsignalsshared*/
#defineCLONE_PID 0x00001000 /*setifpidshared*/
#defineCLONE_PTRACE 0x00002000 /*setifwewanttolettracingcontinueonthechildtoo*/
#defineCLONE_VFORK 0x00004000 /*setiftheparentwantsthechildtowakeituponmm_release*/
#defineCLONE_PARENT 0x00008000 /*setifwewanttohavethesameparentasthecloner*/