2016-02-02
進程控制
進程標識
每個進程都有一個非負整型的唯一的進程id,因為進程id表示服總是唯一的,常將其用作其他標識符的一部分以保證其唯一性。
有某些專用的進程:進程id為0是調度進程,常常被稱為交換進程。該進程不執行任何磁盤上的程序,它是內核的一部分,因此也被稱為系統進程。進程id 1通常是init進程,在自舉過程結束時由內核調用。該進程的程序文件在早期unix版本中是/etc/init在較新版本中是/sbin/init。此進程負責在內核自居後啓動一個unix系統。init通常讀與系統有關的初始化文件/etc/rc*文件並將系統引導到一個狀態。init京城絕不會終止。它是一個普通用户,但是它以超級用户特權運行。某些unix的虛存視線中,進程id 2是頁精靈進程。此進程負責支持虛存系統的請頁操作。與交換進程一樣,頁精靈進程也是內核進程。
除了進程id,每個進程還有一些其他標識。
pid_t getpid(void);//返回調用進程的id
pid_t getppid(void);//返回進程的父進程id
uid_t getuid(void);//返回調用進程的實際用户id
uid_t geteuid(void);//返回調用進程的有效用户id
gid_t getgid(void);//返回調用進程的實際組id
gid_t getegid(void);//返回調用進程的有效組id
fork函數
一個現存進程調用fork函數是unix內核創建一個新進程的唯一方法。
pid_t fork(void)
由fork創建的新進程被稱為子進程。該函數被調用一次但返回兩次。兩次返回的區別是子進程的返回值時0,父進程的返回值時新子進程的進程id。子進程和父進程急促執行fork之後的指令。子進程是父進程的複製品。例如,子進程獲得父進程數據空間、堆和棧的複製品。注意這是子進程所擁有的靠背。子父進程並不共享這些存儲空間。或者是寫時複製(copy-on-write).一般來説,在fork之後是父進程還是子進程先執行不確定,取決於內核使用的調度算法。如果要求父子進程之間同步,則需要進行某種形式的進程間通信。
在fork之後處理文件描述符有兩種常見的情況:
父進程等待子進程完成。這種情況下,父進程無需對其描述符做任何處理。當子進程終止後,它曾進行過讀寫操作的任一共享描述符的文件位移量已做了相應的更新。
父子進程各自執行不同的程序段。在這種情況下,在fork後父子進程各自關閉他們不需要使用的文件描述符,並且不干擾對方使用的文件描述符。這種方法是網絡服務進程中經常使用的。
除了打開文件之外,很多父進程的而其他性子也由子進程繼承
實際用户id、實際組id、有效用户id、有效組id
添加組id
進程組id
對話期id
控制終端
設置用户id和設置組id
當前工作目錄
根目錄
文件方式創建屏蔽字
信號屏蔽和排列
對任意打開文件描述符的在執行時關閉標識
環境
連接的共享存儲段
資源限制
父子進程之間的區別是
fork的返回值
進程id
不同的父進程id
子進程的tms_utime,tms_stime,tms_cutime及tms_ustime設置為0
父進程設置的鎖,子進程不繼承
子進程的未決告警被清除
子進程的未決信號集設置為空
fork失敗的兩個主要原因:系統已經有了太多的進程或者該實際用id的進程總數超過了系統限制。
fork有兩種用法:
一個父進程希望複製自己,使父子進程同時執行不同的代碼段。這在網絡服務進程中是常見的--父進程等待委託者的服務請求。當這種請求到達時,父進程調用fork使子進程處理此請求。父進程則繼續等待下一個服務請求。
一個進程要執行一個不同的程序。這對shell是常見的情況。在這種情況下,子進程在從fork返回後立即調用exec.
vfork
vfork函數調用序列和返回值與fork相同。
vfork用於創建一個新進程,而該進程的目的是exec一個新程序。vfork與fork一樣都創建一個子進程,但是它並不將父進程的地址空間完全複製到子進程中,因為子進程會立即調用exec,於是也就不會存訪該地址空間。不過在子進程調用exec或者exit之前,它在父進程 的空間中運行。這種工作方式在某些unix的頁式虛存視線中提高了效率。
vfork保證子進程先運行,在它調用exec或exit之後父進程才能被調度運行。
exit函數
不管進程如何終止,都會執行內核中的同一段代碼。這段代碼為相應的繼承關閉所有打開描述符,釋放它所使用的存儲器等。
對於任意一種終止情形,我們都希望終止進程能夠通知其父進程它如何終止的。對於exit _exit 這是依靠傳遞給他們退出狀態參數來實現的。在異常終止情況,內核產生一個指示其異常終止的終止狀態。在任意一種情況下,該終止進程的父進程都能用wait或waitpid
取得終止狀態。注意,這裏使用了退出狀態和終止狀態,他們是有區別的。在最後調用_exit時內核將其退出狀態轉化為終止狀態。如果子進程正常終止,則父進程可以獲得子進程的退出狀態。
對於其父進程已經終止的所有進程,他們的父進程都改變為init進程。我們稱這些進程由init進程領養。其操作過程大致是:在一個進程終止時,內核諸葛檢查所有的活動進程,以判斷它是否是正要終止的進程的子進程,如果是,則該進程的父進程id就更改為1.這種處理方法保證了每個進程都有一個父進程。
內核為每個終止子進程保存了一定量的信息,所以當終止進程的父進程調用wait或waitpid時,可以得到有關信息。這種信息至少包括進程id,進程的終止狀態以及該進程使用的CPU時間重量。內核可以釋放終止進程的所有存儲器,關閉其所打開文件。
unix術語中一個已經終止但其父進程尚未對其進行善後處理的進程被稱為僵死進程。ps命令將僵死進程的狀態打印為Z.如果編寫了一個長期運行的程序,它fork了很多子進程,那麼除非父進程等待取得子進程的終止狀態,否則這些子進程就會變成僵死進程。當一個進程被init領養後就不會變成僵死進程。
wait和waitpid函數
當一個進程正常或異常終止時,內核就向其父進程發送SIGCHLD信號。因為子進程終止是一個異步事件,所以這種信號也是內核向父進程發的異步通知,父進程可以忽略該信號,或者提供一個該信號發生時被調用執行的函數。對於這種信號的系統默認動作是忽略它。現在需要知道的是調用wait或者waitpid的進程可能會:
阻塞(如果其所有子進程都還在運行)
帶子進程的終止狀態立即返回
出錯立即返回
如果進程由於接收到SIGCHLD信號而調用wait則可期望wait會立即返回。但是如果在任意時刻調用wait則進程可能阻塞。
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options)
這兩個函數區別是:
在一個子進程終止前,wait使其調用者阻塞,而waitpid有一選這項,可使調用者不阻塞。
waitpid 並不等待第一個終止的子進程,他有若干個選擇項,可以控制它所等待的進程。
如果一個子進程已經終止,是一個僵死進程,則wait立即返回並取得該進程的狀態,否則wait使其調用者阻塞知道一個子進程終止。如果調用者阻塞而他有多個子進程,則在其一個子進程終止時,wait就立即返回。因為wait返回終止子進程的ID,所以它總能瞭解是哪個子進程終止了。
這兩個函數參數statloc是一個整型指針,如果statloc不是一個空指針,則終止進程的終止狀態就放在它所指向的單元內。如果不關心終止狀態,則可以將參數指定為空指針。
根據傳統這兩個函數返回的整型狀態字是由實現定義的。其中某些位表示退出狀態,其他為則表示信號編號。有一位指示是否產生了一個core文件等等。POSIX規定終止狀態用定義在中的各個宏來查看。
WIFIEXITED(status) 若為正常終止子進程返回的狀態則為真。若為這種情況可以執行WEXITSTATUS(status)取子進程傳給exit
WIFISIGNALED(status)若為異常終止子進程返回的狀態則為真。對於這種情況可以執行WTERMSIG(status)取是子進程終止的信號編號
WIFISTOPPED(status)若為當前展廳子進程的返回狀態,則為真。對於這種情況,可執行WSTOPSIG(status)取使子進程暫停的信號編號。
waitpid提供等待特定進程的方法
如果pid==-1等待任一子進程,於是在這一功能方面waitpid與wait等效
如果pid>0等待期進程id與pid相等的子進程。
pid==0等待期組id等於調用進程的組id的任意子進程
pid
waitpid返回終止子進程的進程id,而該子進程的終止狀態則通過statloc返回。對於wait其唯一的出錯時調用進程沒有子進程,但對於waitpid如果指定的進程或進程組不存在或者調用進程沒有子進程都能出錯。
options參數使我們能進一步控制waitpid的操作。此參數或者是0,或者是WNOHANG及WUNTRACED的位運算
WNOHANG 若由pid指定的子進程不立即可用則waitpid不阻塞,此時其返回值為0
WUNTRACED 若實現支持作業控制,則由pid指定的任意子進程狀態已暫停,且其狀態自暫停以來還未報告過則返回器狀態。
waitpid提供了wait函數沒提供的三個功能:
waitpid可以等待某特定的進程
waitpid提供了一個wait的非阻塞版本
waitpid支持作業控制