Linux中建立Daemon程序的三種方法【轉】
轉自:https://www.cnblogs.com/minico/p/7702020.html
什麼是daemon程序?
Unix/Linux中的daemon程序類似於Windows中的後臺服務程序,一直在後臺執行執行,例如http服務程序nginx,ssh服務程序sshd等。
注意,其英文拼寫為daemon而不是deamon。
為什麼daemon程序需要特殊的編寫步驟?
daemon程序和普通程序不一樣嗎?為什麼要單獨提出如何編寫daemon程序呢?
不知道你是否有過這樣的經歷,在Linux上面開啟一個terminal,輸入編譯命令進行編譯,編譯的時間可能比較長,
這時候你不小心關閉了這個terminal,編譯就中斷了。因為編譯指令碼是作為當前terminal的一個子程序來執行的,當terminal退出後,
那麼如何能做到這一點呢?有人說用下面的命令列嗎?
> make &
讓編譯命令make到後臺執行,這樣只是造成了make在後臺一直執行的假象,它依然沒有脫離和terminal之間的父子關係;
當terminal退出後,make依然會退出。所以針對daemon程序就要用特殊的步驟來編寫,以保證在terminal中執行後,
即使terminal退出,daemon程序仍然在後臺執行。
如何編寫daemon程序?
對於可以用多種方法解決的問題,我們一般只需熟練掌握其中一種最適合自己的即可;
1. 首先給出經典名著APUE中的方法:
#include "apue.h"
#include <syslog.h>
#include <fcntl.h>
#include <sys/resource.h>
void daemonize(const char *cmd){
int i, fd0, fd1, fd2;
pid_t pid;
struct rlimit rl;
struct sigaction sa;
/* * Clear file creation mask. */
umask(0);//註釋1
/* * Get maximum number of file descriptors. */
if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
err_quit("%s: can't get file limit", cmd);
/* * Become a session leader to lose controlling TTY. */
if ((pid = fork()) < 0)//註釋2
err_quit("%s: can't fork", cmd);
else if (pid != 0) /* parent */
exit(0);
setsid();//註釋3
/* * Ensure future opens won't allocate controlling TTYs. */
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGHUP, &sa, NULL) < 0)
err_quit("%s: can't ignore SIGHUP", cmd);
if ((pid = fork()) < 0)//註釋4
err_quit("%s: can't fork", cmd);
else if (pid != 0) /* parent */
exit(0);
/* * Change the current working directory to the root so * we won't prevent file systems from being unmounted. */
if (chdir("/") < 0)//註釋5
err_quit("%s: can't change directory to /", cmd);
/* * Close all open file descriptors. */
if (rl.rlim_max == RLIM_INFINITY)
rl.rlim_max = 1024;
for (i = 0; i < rl.rlim_max; i++)
close(i);//註釋6
/* * Attach file descriptors 0, 1, and 2 to /dev/null. */
fd0 = open("/dev/null", O_RDWR);//註釋7
fd1 = dup(0);//註釋7
fd2 = dup(0);//註釋7
/* * Initialize the log file. */
openlog(cmd, LOG_CONS, LOG_DAEMON);
if (fd0 != 0 || fd1 != 1 || fd2 != 2) {
syslog(LOG_ERR, "unexpected file descriptors %d %d %d",fd0, fd1, fd2);
exit(1);
}
}
下面是針對上面例子的詳細解釋:
* 註釋1:因為我們從shell建立的daemon子程序,所以daemon子程序會繼承shell的umask,如果不清除的話,會導致daemon程序建立檔案時遮蔽某些許可權。
* 註釋2:fork後讓父程序退出,子程序獲得新的pid,肯定不為程序組組長,這是setsid前提。
* 註釋3:呼叫setsid來建立新的程序會話。這使得daemon程序成為會話首程序,脫離和terminal的關聯。
* 註釋4:最好在這裡再次fork。這樣使得daemon程序不再是會話首程序,那麼永遠沒有機會獲得控制終端。如果這裡不fork的話,會話首程序依然可能開啟控制終端。
* 註釋5:將當前工作目錄切換到根目錄。父程序繼承過來的當前目錄可能mount在一個檔案系統上,如果不切換到根目錄,那麼這個檔案系統不允許unmount。
* 註釋6:在子程序中關閉從父程序中繼承過來的那些不需要的檔案描述符。可以通過_SC_OPEN_MAX來判斷最高檔案描述符(不是很必須).
* 註釋7:開啟/dev/null複製到0,1,2,因為dameon程序已經和terminal脫離了,所以需要重新定向標準輸入,標準輸出和標準錯誤(不是很必須).
針對這個例子,首先要說明的是,不管在Unix還是Linux上按照這個例子寫的daemon肯定沒問題。
不過我對其中的一些步驟的必要性一直持懷疑態度:
1) 第二個fork是必須的嗎?
根據APUE中的說法是,這是為了防止後面開啟終端的時候又關聯到了daemon程序上,這樣當終端關閉後,daemon程序就退出了,
不過個人感覺這種說法有可能已經不再適用了,畢竟大名鼎鼎的nginx也沒有fork兩次。不過目前我還不知道怎麼用實驗來證明這個結論。
2)setsid()是必須的嗎?
按照書上說的是每個程序都屬於一個程序組(Process Group),每個程序組都屬於一個程序會話(Process Session)。
這三者的關係如下圖所示,當terminal退出的時候,以最初login shell為首的程序回話就結束了。
這時候,屬於這個session的所有程序都會收到SIGHUP訊號,導致程序退出。
執行了第一次fork(),父程序退出了,子程序變成孤兒程序過繼給了1號init程序,但是它仍然屬於當前登入shell所控制的session,
呼叫setsid()的目的是讓daemon程序形成獨立的Session,這樣當terminal退出的時候就影響不到這個daemon程序了。
但是我在各種Unix,Linux系統上做了實驗,不呼叫setsid(), 並且只fork()一次,然後將當前終端關閉,重新開啟一個新的終端,
發現daemon程序仍然存在,並沒有像書中所說會隨著terminal的退出而退出,請高人指點迷津。
2. 利用系統庫函式daemon()建立daemon程序
Linux系統還專門提供了一個用來建立daemon程序的系統函式:
int daemon(int nochdir, int noclose);
從api的文件描述看該api也呼叫了fork(),估計內部實現和上面的程式碼邏輯類似,從其引數作用也可以看出這一點,
這個api有兩個引數,其作用分別對應上面程式碼中的註釋5和註釋7。下面是用這個api建立daemon程序的簡單示例:
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
if(daemon(0,0) == -1)
exit(EXIT_FAILURE);
while(1)
{
sleep(60);
}
return 0;
}
3. 使用第三方工具supervisor
簡單的說supervisor是一個python工具,可以通過編寫配置檔案來對指定的程序進行管理,比如啟動程序,停止程序以及程序退出後自動重啟等;
這樣一來,即使一個普通程序通過supervisor管理之後也會變成和daemon程序一樣的行為,不會隨著terminal的關閉而退出。
後續我會寫一篇關於supervisor的文章專門介紹其具體使用方法。
參考資料
http://www.cnblogs.com/mickole/p/3188321.html
https://dirtysalt.github.io/apue.html#orgheadline174