2016-04-08 53 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文件的守护进程不是*固有的*活跃 - 它只是普遍实现的活跃程度。 –