linux中創建deamon進程的三種方法

什麼是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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章