一直以來都很納悶為什麼寫 daemon 時需要 double fork, double fork 是必要的嗎? 最後才發覺我問錯問題了, 而問錯問題永遠不會得到對的答案。我該問的問題是, 成為一個 daemon, 必須該做那些事?
答案是:
- chdir("/"): 避免有任何目錄因為還有 process 在路徑內, 使得目錄已被移除, 卻無法釋放空間。
- 避免成為 zombie: 若 daemon 的 parent process 是 init 的話, daemon 結束時 init 會自動 wait 它, 避免成為 zombie。反之, 則有這點顧慮。
- 脫離 terminal: 因為關閉 terminal 的時候, kernel 會送 SIGHUP 給 session leader [*1]。以 shell 的實作來說, shell 是 session leader, 它在收到 SIGHUP 時, 會送 SIGHUP 給它所產生的所有 process group, 而 SIGHUP 預設行為是 terminate。但這不是 daemon 希望的行為, 相較於因此改變 SIGHUP 的行為, 不如脫離 terminal 更省事, 還可視需要保留 SIGHUP 做別的用途 [*2]
- 同上, 脫離 terminal 可避免不小心讀寫到 terminal 而收到 SIGTTIN 和 SIGTTOU。它們是 background process 讀寫 terminal 時觸發的, 藉由這樣告知 process 它目前是 background process 並嘗試要讀寫 terminal。雖然也可以改變這兩個 signal 的處理行為 (預設為 stopped), 但是不如完全不會發生來得省事。
有了以上需求, 就有不同的作法。POSIX 沒有定義 daemon() 函式, 不過 BSD 體系包含 Linux 裡有 daemon() 函式, 用來成為 daemon。eglibc 的實作方式如下:
- fork 一次, 讓 parent process 掛掉, 以滿足 (2)
- 呼叫 setsid() 成為新的 session leader
- chdir("/"), 以滿足 (1)
- 重導 0, 1, 2 到 /dev/null 以滿足 (3、4)
但 daemon() 沒有做第二次 fork(), 所以它仍是 session leader, 有機會開啟新的 terminal。若要避免這種可能, 呼叫完 daemon() 後再呼叫一次 fork(), 並讓原本的 parent process 離開, 這樣 daemon process 就會處在一個沒有 terminal 也沒有 session leader 的情況, 會更安全。
從再呼叫一次 fork() 來看, 會更能理解為什麼 daemon() 要開新的 session, 這樣在呼叫完 daemon() 後有比較大的主導權, 看是自己想開新的 terminal, 或是再做一次 fork() 確保不存在 session leader, 都沒有問題。
PS
- *1 看了一些文件, 感覺上 session 是針對 terminal 而有的概念。而 process group 則和使用 signal 有關。
- *2 有些 daemon 收到 SIGHUP 會重讀設定檔。由於 daemon 不可能觸發原本 SIGHUP 的使用情境, 可以安心拿它來做自己的用途。
謝謝分享
回覆刪除