next up previous
Next: 當我要從 pipe 讀一行輸入時,要如何才能讓這行資料像是直接從鍵盤輸 入而非從一個大 block buffer Up: 中級的問題 Previous: 能不能把 shell 變數傳進 awk 程式當中呢?

要怎樣才能避免在記憶體中留下 zombie processes?

很不幸地,對於死掉的子 process 應有的行為特性並沒有辦法做一般化,因 為這些特定的機制會隨著 Unix 的種類不同而有所差異。

首先,在各種 Unix 上面您都必需使用 wait() 來處理子 process。也就是 說,我還沒看過有一種 Unix 會自動把結束的子 process 幹掉,即使您不告 訴它該怎麼做。

其次,在某些從 SysV 衍生的系統當中,如果您執行了 signal(SIGCHLD, SIG_IGN)",(嗯,事實上應該是 SIGCLD 而非 SIGCHLD,但大多數新出 爐的 SysV 系統都會在表頭檔當中加上 #define SIGCHLD SIGCLD),那 麼子 processes 都會自動被清除得乾乾淨淨,您什麼事都不用做。看看這個 方式是否可行的最佳做法就是自己在機器上試試看。如果您想試著寫出具可 攜性的程式碼,那麼依賴這種特殊處理方式可能不是好主意。不幸的是,在 POSIX 並不允許您這樣做;把 SIGCHLD 的行為特性設成 SIG_IGN 在 POSIX 當中並沒有定義,所以如果要讓您的程式合乎 POSIX 的要求時,就不可以 這樣做。

那麼怎樣才算是 POSIX 的做法呢?如同前面所述,您必需設定一個 signal 的處理函數,然後讓它去 wait。在 POSIX 當中 signal 處理函數是經由 sigaction 設定,由於您只對終止的子 process 感興趣,而不是那些 stopped 的子 process,所以可以在 sa_flags 當中加上 SA_NOCLDSTOP。如果要 wait 子 process 而本身不因此被擋 (block),可以使用 waitpid()。第一 個參數必需是 -1 (代表 wait 任何 pid),第三個參數必需是 WNOHANG,這是 最具可攜性的做法,也是可能會成為未來最具可攜性的寫法。

如果您的系統不支援 POSIX,那就有很多做法了。最簡單的方式就是先試 試 signal(SIGCHLD, SIG_IGN) 是否可行,可以的話那就好了。如果  SIG_IGN 無法用來強制自動收拾殘骸,那麼您就要自己寫一個 signal 處理 函數來收拾殘骸了。要寫出一個適用於每一種 Unix 的 singal 處理函數來 做這件事是不容易的事,因為有下列不一致的地方:

在一些 Unix 中,一個或一個以上的子 process 死時,會呼叫 SIGCHLD 的  signal 處理函數。也就是說,如果你的 signal 處理函數只有一個 wait() 時,並不會把所有的子 process 都收拾乾淨。不過還好,我相信這類的  Unix 都會有 wait3() 或 waitpid(),這兩者都有可在 option 參數中使用  WNOHNAG 可用來檢查是否有子 process 尚待收拾。所以在一個有  wait3()/waitpid() 的系統中,你可以一再重複使用 wait3()/waitpid() 以確定所有的子 process 都已收拾乾淨。最好是用 waitpid() 因為 它在 POSIX 標準中。

在一些 SysV-derived 的系統中,再 SIGCHLD 的 signal 處理函數結束後, 若還有子 process 等待清除,還是會產生 SIGCHLD signal。 因此,在大部 份的 SysV 系統中,在 signal 處理函數裡可以假設要處理的 signal 只有一 個, 反正若還有等待處理者,signal 會一再的產生,系統也會一再的呼叫 signal 處理函數。

在一些比較舊的系統中,無法防止 signal 處理函數在被呼叫後 signal 處理 函數被自動設為 SIG_DFL。在這類的系統中,要在你的signal 處理函數中 的最後加入 "signal(SIGCHLD,catcher_func)"("catcher_func" 是處理函數的 名字)。

還好新一點的系統中在 signal 處理函數被呼叫後並不會從設為 SIG_DFL。所以在沒有 wait3()/waitpid() 而有 SIGCLD 的系統中,要處理 此問題,當在處理函數呼叫了一次 wait() 以後就得用 signal() 從新設定 signal 處理函數。為了向前相容之故, System V 的 signal() 維與以前相同 的作法。所以,應該要用 sigaction() 或 sigset() 來安裝 signal 處理函數。

總結來說,在有 waitpid()(POSIX) 或wait3() 的系統了,你應該要用它們而 你的 signal 處理函數裡也要 loop,在沒有 waitpid() 與 wait3() 的系統 中,則每次呼叫 signal 處理函數都要有 wait()。

最後提供一個 portable 但是效率較差的做法。這個方法是 parent process 在  fork() 後要 wait() 它的 child process 的結束。而此 child process 馬上又  fork(),這時你就有一 child process與一 grandchild process。 將此  child process 馬上結束,則  parent process 也會跟著結束。 讓  grandchild 來做原先要  child 做的事情。此時  grandchild 的因為其  parent (child) 已死, parent 就變成了 init, init 就會幫你處理  wait() 相關事宜。這個方 法多用了一個 fork() 所以比較沒有效率,但這絕對是個 portable 的方法。


next up previous
Next: 當我要從 pipe 讀一行輸入時,要如何才能讓這行資料像是直接從鍵盤輸 入而非從一個大 block buffer Up: 中級的問題 Previous: 能不能把 shell 變數傳進 awk 程式當中呢?
Tan Koan-Sin
1999-03-02