我已经多次完成了在Linux下作为守护程序运行程序的工作。
daemon()
。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。
答案 0 :(得分:1)
我正在尝试以下代码。要点是:
因此,使用此方法,前台进程不会退出,直到后台进程'PID已写入。
(注意exit()
和_exit()
之间的区别。想法是exit()
执行正常关机,包括通过C ++析构函数或C解锁和删除PID文件atexit()
函数。但_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;
}
答案 1 :(得分:1)
正如您所发现的那样,管理自己的pid文件的守护进程本质上是有趣的。解决方案是不进行守护,而是在前台运行该进程,然后使用进程管理器来管理它。示例包括runit,supervisord和systemd's support for "new-style daemons"。