2016-04-08 79 views
0

我已經做了幾次工作,使得程序在Linux下作爲守護進程運行。沒有PID文件競爭條件的Linux守護進程

  • 在一個案例中,我剛剛使用了daemon()
  • 還有一次,我寫我自己的守護進程的代碼(based on something like this),因爲我想要做的STDIN,STDOUT等更復雜的重定向
  • 我也用了Busybox的start-stop-daemon啓動一個C#程序單作爲守護進程,並且還會生成一個帶有-m選項的PID文件。

問題是,所有這些解決方案都有PID文件創建的爭用條件,也就是說,PID文件是由程序通過其後臺進程寫入的,前臺進程退出後的某個不確定時間。這是一個問題,例如在嵌入式Linux中,如果程序是由initscript啓動的,則最後啓動一個監視程序進程,通過檢查其PID文件來監視程序是否正在運行。在使用start-stop-daemon的C#Mono案例中,我曾經在啓動時偶爾會看到這樣的系統重啓,因爲程序的PID文件在監視進程開始監視時尚未寫入(令人驚訝,因爲這可能是這在實際情況下會發生)。

程序如何在沒有PID文件競爭條件的情況下進行守護進程?也就是說,以這種方式保證當前臺進程退出時,PID文件被完全創建並用有效的PID值寫入。

注意,這對於Linux守護進程fork-setsid-fork idiom (to prevent the daemon from acquiring a controlling tty)來說有點困難,因爲父母不能輕易得到孫子的PID。

回答

1

我在試下面的代碼。要點如下:

  • 第一個分岔的父母等待,直到孩子退出。
  • 第一個叉子的孩子做了各種守護進程,然後做了第二個叉子。第二個分支的父(它獲得其子的PID)將PID寫入PID文件,然後退出。

所以用這種方法,前臺進程不會退出,直到後臺進程'PID已被寫入。

(注exit()_exit()之間的差。這個想法是,exit()確實正常關機,其可包括被C atexit()功能PID文件或者通過C++析構的或解鎖和刪除。但是_exit()跳過任何的。這允許後臺進程保持PID文件打開並鎖定(例如使用flock()),這允許一個「singleton」守護進程。因此,該程序在調用該函數之前應該打開PID文件並打開該文件,如果它是一個C程序,它應該註冊一個atexit()函數來關閉和刪除PID文件,如果它是一個C++程序,它應該使用RAII風格的類來創建PID文件並在退出時關閉/刪除它。)

int daemon_with_pid(int pid_fd) 
{ 
    int   fd; 
    pid_t  pid; 
    pid_t  pid_wait; 
    int   stat; 
    int   file_bytes; 
    char  pidfile_buffer[32]; 

    pid = fork(); 
    if (pid < 0) { 
     perror("daemon fork"); 
     exit(20); 
    } 
    if (pid > 0) { 
     /* We are the parent. 
     * Wait for child to exit. The child will do a second fork, 
     * write the PID of the grandchild to the pidfile, then exit. 
     * We wait for this to avoid race condition on pidfile writing. 
     * I.e. when we exit, pidfile contents are guaranteed valid. */ 
     for (;;) { 
      pid_wait = waitpid(pid, &stat, 0); 
      if (pid_wait == -1 && errno == EINTR) 
       continue; 
      if (WIFSTOPPED(stat) || WIFCONTINUED(stat)) 
       continue; 
      break; 
     } 
     if (WIFEXITED(stat)) { 
      if (WEXITSTATUS(stat) != 0) { 
       fprintf(stderr, "Error in child process\n"); 
       exit(WEXITSTATUS(stat)); 
      } 
      _exit(0); 
     } 
     _exit(21); 
    } 

    /* We are the child. Set up for daemon and then do second fork. */ 
    /* Set current directory to/*/ 
    chdir("/"); 

    /* Redirect STDIN, STDOUT, STDERR to /dev/null */ 
    fd = open("/dev/null", O_RDWR); 
    if (fd < 0) 
     _exit(22); 
    stat = dup2(fd, STDIN_FILENO); 
    if (stat < 0) 
     _exit(23); 
    stat = dup2(fd, STDOUT_FILENO); 
    if (stat < 0) 
     _exit(23); 
    stat = dup2(fd, STDERR_FILENO); 
    if (stat < 0) 
     _exit(23); 

    /* Start a new session for the daemon. */ 
    setsid(); 

    /* Do a second fork */ 
    pid = fork(); 
    if (pid < 0) { 
     _exit(24); 
    } 
    if (pid > 0) { 
     /* We are the parent in this second fork; child of the first fork. 
     * Write the PID to the pidfile, then exit. */ 
     if (pid_fd >= 0) { 
      file_bytes = snprintf(pidfile_buffer, sizeof(pidfile_buffer), "%d\n", pid); 
      if (file_bytes <= 0) 
       _exit(25); 
      stat = ftruncate(pid_fd, 0); 
      if (stat < 0) 
       _exit(26); 
      stat = lseek(pid_fd, 0, SEEK_SET); 
      if (stat < 0) 
       _exit(27); 
      stat = write(pid_fd, pidfile_buffer, file_bytes); 
      if (stat < file_bytes) 
       _exit(28); 
     } 
     _exit(0); 

    } 
    /* We are the child of the second fork; grandchild of the first fork. */ 
    return 0; 
} 
1

管理其自己的pid文件的守護程序本質上是活潑的,就像您發現的那樣。解決方案是不要守護進程,而是在前臺運行進程,然後使用進程管理器進行管理。示例是runit,supervisordsystemd's support for "new-style daemons"

+0

感謝您的替代解決方案。到目前爲止,我還沒有使用systemd的經驗,或者其他任何選項,所以我會檢查它們。但是請注意,管理其自己的PID文件的守護進程不是*固有的*活躍 - 它只是普遍實現的活躍程度。 –