- fork() 結束親行程,繼續的子行程呼叫 setsid() (TLPI §34.3) 成立新的行程群組。
- 假設 daemon 是從指令行執行,親行程結束讓 shell 得以繼續,留下子行程在背景執行。
- 親行程可能是行程群組領導,可能帶有其它子行程。fork() 後子行程就不會是領導,呼叫 setsid() 後成立新的獨立行程群組。
- 如果 daemon 之後需要開啟終端 device,需要動作確保不會變成控制終端,有兩種方式:
- open() 任何終端 device 指定 O_NOCTTY 旗標。
- 另一種較簡易的方式是進行第二次 fork(),一樣只讓子行程繼續執行。如此一來,子行程不會是行程群組領導,依據 Linux 跟隨的 the System V 只有領導取得控制終端機的傳統,可避免取得控制終端機 (TLPI §34.4)。如果是跟隨 BSD 傳統的系統,行程只有明確用 ioctl() TIOCSCTTY 設定後,才會得到控制終端機,第二次 fork() 是多餘的但也沒什麼傷害。
- 清除行程的 umask (TLPI §15.4.6),確保 daemon 建立檔案或目錄時,有需要的 permissions.
- 在 daemon 結束前,其工作目錄無法卸載 (TLPI §14.8.2),改變工作目錄到根目錄 ( / ) 或其它適當的目錄,可方便在 daemon 結束前卸載。
- 繼承的 file descriptor,沒用到的都關閉,以節省資源。 例如 daemon 已經失去控制終端,保留 file descriptors 0、1、和 2 並無意義。例外,開啟的檔案所在的檔案系統無法卸載。
- 將 file descriptors 0、1、和 2 都導到 /dev/null。
- 避免 daemon 呼叫函式庫有輸出無法輸出,造成非預期的錯誤。
- 避免 daemon 後續開啟檔案使用到 1 和 2,函式庫輸出而造成破壞。
#include <sys/stat.h>
#include <fcntl.h>
#include "tlpi_hdr.h"
int /* Returns 0 on success, -1 on error */
becomeDaemon(int flags)
{
int maxfd, fd;
switch (fork()) { /* Become background process */
case -1: return -1;
case 0: break; /* Child falls through... */
default: _exit(EXIT_SUCCESS); /* while parent terminates */
}
if (setsid() == -1) /* Become leader of new session */
return -1;
switch (fork()) { /* Ensure we are not session leader */
case -1: return -1;
case 0: break;
default: _exit(EXIT_SUCCESS);
}
if (!(flags & BD_NO_UMASK0))
umask(0); /* Clear file mode creation mask */
if (!(flags & BD_NO_CHDIR))
chdir("/"); /* Change to root directory */
if (!(flags & BD_NO_CLOSE_FILES)) { /* Close all open files */
maxfd = sysconf(_SC_OPEN_MAX);
if (maxfd == -1) /* Limit is indeterminate... */
maxfd = BD_MAX_CLOSE; /* so take a guess */
for (fd = 0; fd < maxfd; fd++)
close(fd);
}
if (!(flags & BD_NO_REOPEN_STD_FDS)) {
close(STDIN_FILENO); /* Reopen standard fd's to /dev/null */
fd = open("/dev/null", O_RDWR);
if (fd != STDIN_FILENO) /* 'fd' should be 0 */
return -1;
if (dup2(STDIN_FILENO, STDOUT_FILENO) != STDOUT_FILENO)
return -1;
if (dup2(STDIN_FILENO, STDERR_FILENO) != STDERR_FILENO)
return -1;
}
return 0;
}
寫一個程式呼叫 becomeDaemon(0),然後 sleep() 一陣子,可以用指令 ps 來看程式的一些屬性:
TT 下顯示 ? 表示沒有控制終端機。SID 不同於 PID,表示不是 session leader,不會再取得控制終端機。$ ./test_become_daemon $ ps -C test_become_daemon -o "pid ppid pgid sid tty command" PID PPID PGID SID TT COMMAND 24731 1 24730 24730 ? ./test_become_daemon
由於 daemons 是長時間執行。需要特別注意可能的記憶體洩漏 (TLPI §7.1.3) 和 file descriptor 洩漏 (沒有關閉所有開啟的 file descriptor。程式結束不是全部自動關閉嗎?)。
daemon 通常是系統結束時呼叫其 shutdown script,沒有的則由 init process 送 SIGTERM,5 秒後再送 SIGKILL。
許多 daemon 需要確保一個時間只有一個在執行,方法見 TLPI §55.6。
參考來源
- TLPI §37.2 & §37.3
沒有留言:
張貼留言