【问题标题】:Linux daemonize without PID file race condition没有 PID 文件竞争条件的 Linux 守护进程
【发布时间】:2016-07-29 02:56:46
【问题描述】:

我已经做了好几次让程序在 Linux 下作为守护进程运行的工作。

  • 在一种情况下,我刚刚使用了daemon()
  • 在另一种情况下,我编写了自己的守护程序代码 (based on something like this),因为我想对 STDIN、STDOUT 等进行更复杂的重定向。
  • 我还使用 Busybox start-stop-daemon 作为守护程序启动了 C# Mono 程序,并使用 -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。

【问题讨论】:

    标签: linux daemon race-condition pid


    【解决方案1】:

    正如您所发现的,管理自己的 pid 文件的守护进程本质上是活泼的。解决方案是不守护进程,而是在前台运行进程,然后使用进程管理器来管理它。例如runitsupervisordsystemd's support for "new-style daemons"

    【讨论】:

    • 感谢您提供此替代解决方案。到目前为止,我还没有使用 systemd 或任何其他选项的经验,所以我会检查一下。但请注意,管理自己的 PID 文件的守护进程并不是固有 racy——它只是通常实现的racy。
    【解决方案2】:

    我正在尝试以下代码。要点是:

    • 第一个分叉的父级等待子级退出。
    • 第一个 fork 的子进程设置各种守护程序,然后执行第二个 fork。第二个 fork 的父级(获取其子级的 PID)将 PID 写入 PID 文件,然后退出。

    所以使用这种方法,前台进程在后台进程的 PID 被写入之前不会退出。

    (注意exit()_exit()之间的区别。这个想法是exit()可以正常关闭,这可以包括通过C++析构函数或Catexit()函数解锁和删除PID文件。但是_exit() 跳过任何一个。这允许后台进程保持 PID 文件打开和锁定(使用例如flock()),这允许“单例”守护进程。所以程序在调用这个函数之前,应该打开PID文件和flock()它。如果是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;
    }
    

    【讨论】:

      猜你喜欢
      • 2013-06-17
      • 2014-10-20
      • 2014-08-30
      • 2018-03-10
      • 2010-09-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多