進程有下面5種正常終止方式:
(1)在main函數內執行return語句。這等效于調用exit。
(2)調用exit函數。此函數有ISO C定義,其操作包括調用各終止處理程序(終止處理程序在調用atexit函數時登記),然后關閉所有標準I/O流等。
(3)調用_exit或_Exit函數。ISO C定義_Exit,其目的是為進程提供一種無需運行終止處理程序或信號處理程序而終止的方法。對標準I/O流是否進行沖洗,這取決于實現。在UNIX系統中,_Exit和_exit是同義的,并不清洗標準I/O流。_exit函數由exit調用,它處理UNIX特定的細節。
在大多數UNIX系統實現中,exit(3)是標準C庫中的一個函數,而_exit(2)則是一個系統調用。
(4)進程的最后一個線程在其啟動例程中執行返回語句。但是,該線程的返回值不會用作進程的返回值。當最后一個線程從其啟動例程返回時,該進程以終止狀態0返回。
(5)進程的最后一個線程調用pthread_exit函數。在這種情況下,進程終止狀態總是0,這與傳送給pthread_exit的參數無關。
三種異常終止方式如下:
(1)調用abort。它產生SIGABRT信號,這是下一種異常終止的特例。
(2)當進程接收到某些信號時。信號可由進程自身(例如調用abort函數)、其他進程或內核產生。
(3)最后一個線程對“取消”(cancellation)請求作出響應。按系統默認,“取消”以延遲方式發生:一個線程要求取消另一個線程,一段時間之后,目標線程終止。
不管進程如何終止,最后都會執行內核中的同一段代碼。這段代碼為相應進程關閉所有打開描述符,釋放它所使用的存儲器等。
對于上述任意一種終止情形,我們都希望終止進程能夠通知其父進程它是如何終止的。對于三個終止函數(exit、_exit和_Exit),實現這一點的方法是,將其退出狀態(exit status)作為參數傳遞給函數。在異常終止情況下,內核(不是進程本身)產生一個指示其異常終止原因的終止狀態(termination status)。在任意一種情況下,該終止進程的父進程都能用wait或waitpid函數取得其終止狀態。
注意,這里使用了“退出狀態”(它是傳向exit或_exit的參數,或main的返回值)和“終止狀態”兩個術語,以表示有所區別。在最后調用_exit時,內核將退出狀態轉換成終止狀態(回憶圖7-1http://www.CUOXin.com/nufangrensheng/p/3507921.html)。如果子進程正常終止,父進程可以獲得子進程的退出狀態。
在說明fork函數時,顯而易見,子進程是在父進程調用fork后生成的。上面又說了子進程將其終止狀態返回給父進程。但是如果父進程在子進程之前終止,則將如何呢?其回答是:對于父進程已經終止的所有進程,它們的父進程都改變為init進程。我們稱這些進程由init進程領養。其操作過程大致如下:在一個進程終止時,內核逐個檢查所有活動進程,以判斷它是否是正要終止進程的子進程,如果是,則將該進程的父進程ID更改為1(init進程的ID)。這種處理方法保證了每個進程都有一個父進程。
另一個我們關心的情況是如果子進程在父進程之前終止,那么父進程又如何能在做相應檢查時得到子進程的終止狀態呢?對此問題的回答是:內核為每個終止子進程保存了一定量的信息,所以當終止進程的父進程調用wait或waitpid時,可以得到這些信息。這些信息至少包括進程ID、該進程的終止狀態、以及該進程使用的CPU時間總量。內核可以釋放終止進程所使用的所有存儲區,關閉其所有打開文件。在UNIX術語中,一個已經終止,但是其父進程尚未對其進行善后處理(使用wait獲取終止子進程的有關信息,釋放它仍占用的資源)的進程稱為僵死進程(zombie)(更多關于僵尸進程可參考:http://www.CUOXin.com/bettercoder/p/3501086.html)。ps(1)命令將僵死進程的狀態打印為Z。如果編寫一個長期運行的程序,它調用fork產生了很多子進程,那么除非父進程等待取得子進程的終止狀態,否則這些子進程終止后就會變成僵死進程。
最后一個要考慮的問題是:一個由init進程領養的進程終止時會發生什么?它會不會變成一個僵死進程?對此問題的回答是:“否”,因為init被編寫成無論何時只要有一個子進程終止,init就會調用wait函數取得其終止狀態。這樣也就防止了在系統中有很多僵死進程。當提及“一個init的子進程”時,這指的可能是init直接產生的進程,也可能是其父進程已終止,由init領養的進程。
本篇博文內容摘自《UNIX環境高級編程》(第二版),僅作個人學習記錄所用。關于本書可參考:http://www.apuebook.com/。
新聞熱點
疑難解答