没有PID文件竞争条件的Linux守护进程

没有PID文件竞争条件的Linux守护进程,linux,daemon,race-condition,pid,Linux,Daemon,Race Condition,Pid,我已经做过几次让程序在Linux下作为守护进程运行的工作 在一个案例中,我刚刚使用了 还有一次,我编写了自己的守护程序代码(),因为我想对STDIN、STDOUT等进行更复杂的重定向 我还使用Busybox启动了一个C#Mono程序作为守护进程,并使用-m选项生成了一个PID文件 问题是,所有这些解决方案在PID文件创建上都有一个竞争条件,也就是说,PID文件是由程序通过其后台进程编写的,前台进程退出后的某个不确定时间。这是一个问题,例如,在嵌入式Linux中,如果程序由initscript

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

  • 在一个案例中,我刚刚使用了
  • 还有一次,我编写了自己的守护程序代码(),因为我想对STDIN、STDOUT等进行更复杂的重定向
  • 我还使用Busybox启动了一个C#Mono程序作为守护进程,并使用
    -m
    选项生成了一个PID文件
问题是,所有这些解决方案在PID文件创建上都有一个竞争条件,也就是说,PID文件是由程序通过其后台进程编写的,前台进程退出后的某个不确定时间。这是一个问题,例如,在嵌入式Linux中,如果程序由initscript启动,然后启动一个看门狗进程,该进程通过检查其PID文件来监控程序的运行。在C#Mono中使用
start-stop-daemon
,我曾经让这样一个系统在看门狗启动时偶尔重新启动,因为在看门狗进程开始监控时程序的PID文件还没有被写入(令人惊讶的是,在实际场景中可能会发生这种情况)

如果没有PID文件争用条件,如何对程序进行后台监控?也就是说,这样可以确保在前台进程退出时完全创建并使用有效的PID值写入PID文件


注意,Linux守护进程使这一点变得更加困难,因为父进程无法如此轻松地获取孙子的PID。

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

  • 第一个fork的父级等待子级退出
  • 第一个fork的子级执行各种后台程序设置,然后执行第二个fork。第二个fork的父级(获取其子级的PID)将PID写入PID文件,然后退出
因此,使用这种方法,前台进程在后台进程的PID被写入之前不会退出

<> >(注意代码< > EXTIONE)/<代码>和< C++ >代码> > />代码>。跳过所有这些。这允许后台进程保持PID文件打开和锁定(使用例如
flock()
),这允许“singleton”守护进程。因此,程序在调用此函数之前,应该打开PID文件并
flock()
它。如果它是C程序,它应该注册一个
atexit()函数,它将关闭和删除PID文件。如果是C++程序,它应该使用RAII样式类来创建PID文件,并在退出时关闭/删除它。
int-daemon_与_-pid(int-pid\u-fd)
{
int-fd;
pid_t pid;
等待;
int stat;
int文件字节;
char-pidfile_缓冲区[32];
pid=fork();
if(pid<0){
perror(“daemon fork”);
出口(20);
}
如果(pid>0){
/*我们是父母。
*等待子项退出。子项将执行第二个分叉,
*将孙子的PID写入PID文件,然后退出。
*我们等待它,以避免在写入文件时出现争用情况。
*即,当我们退出时,pidfile内容保证有效*/
对于(;;){
pid_wait=waitpid(pid和stat,0);
如果(pid_wait==1&&errno==EINTR)
继续;
如果(WIFSTOPED(stat)| | WIFCONTINUED(stat))
继续;
打破
}
如果(妻子退出(统计)){
如果(WEXITSTATUS(stat)!=0){
fprintf(stderr,“子进程中的错误”);
退出(WEXITSTATUS(stat));
}
_出口(0);
}
_出口(21);
}
/*我们是孩子。为守护进程设置,然后执行第二个fork*/
/*将当前目录设置为/*/
chdir(“/”);
/*将STDIN、STDOUT、STDERR重定向到/dev/null*/
fd=打开(“/dev/null”,O_RDWR);
如果(fd<0)
_出口(22);
stat=dup2(fd,标准文件号);
如果(统计值<0)
_出口(23);
stat=dup2(fd,标准文件号);
如果(统计值<0)
_出口(23);
stat=dup2(fd,标准文件号);
如果(统计值<0)
_出口(23);
/*启动守护进程的新会话*/
setsid();
/*做第二个叉子*/
pid=fork();
if(pid<0){
_出口(24);
}
如果(pid>0){
/*我们是第二个分支的父级;第一个分支的子级。
*将PID写入PID文件,然后退出*/
如果(pid\U fd>=0){
file_bytes=snprintf(pidfile_缓冲区,sizeof(pidfile_缓冲区),%d\n,pid);

如果(file_bytes我正在尝试以下代码。要点是:

  • 第一个fork的父级等待子级退出
  • 第一个fork的子级执行各种后台程序设置,然后执行第二个fork。第二个fork的父级(获取其子级的PID)将PID写入PID文件,然后退出
因此,使用这种方法,前台进程在后台进程的PID被写入之前不会退出

<> >(注意代码< > EXTIONE)/<代码>和< C++ >代码> > />代码>。
跳过所有这些。这允许后台进程保持PID文件打开和锁定(使用例如
flock()
),这允许“singleton”守护进程。因此,程序在调用此函数之前,应该打开PID文件并
flock()
它。如果它是C程序,它应该注册一个
atexit()将关闭和删除PID文件。如果是C++程序,则应该使用RAII风格的CL。
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;
}