1. 程式人生 > 實用技巧 >Linux中建立Daemon程序的三種方法【轉】

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退出後,

子程序也就退出了。而作為daemon程序,我們希望一旦啟動就能在後臺一直執行,不會隨著terminal的退出而結束。
那麼如何能做到這一點呢?有人說用下面的命令列嗎?

> make &

讓編譯命令make到後臺執行,這樣只是造成了make在後臺一直執行的假象,它依然沒有脫離和terminal之間的父子關係;
當terminal退出後,make依然會退出。所以針對daemon程序就要用特殊的步驟來編寫,以保證在terminal中執行後,
即使terminal退出,daemon程序仍然在後臺執行。

如何編寫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