如何创建一个守护进程

68

我想了解如何将我的程序变成守护进程。一般来说,一个程序要想成为守护进程,需要执行以下步骤:

  1. 调用 fork()

  2. 在父进程中调用 exit()。这样做可以确保原始父进程(即守护进程的祖先)满意其子进程已经终止,守护进程的父进程已经停止运行,并且守护进程不是进程组的领导者。最后一点是下一步成功完成的先决条件。

  3. 调用 setsid(),使守护进程成为新的进程组和会话的领导者。也因此该进程没有相关联的控制终端(因为该进程刚刚创建了一个新会话,并且不会分配一个控制终端)。

  4. 通过 chdir() 更改工作目录为根目录。这样做是因为继承的工作目录可以位于文件系统上的任何地方。守护进程往往在系统正常运行期间长时间运行,您不希望保持某个随机目录处于打开状态,从而阻止管理员卸载包含该目录的文件系统。

  5. 关闭所有文件描述符。

  6. 打开文件描述符 0、1 和 2(标准输入、标准输出和标准错误),并将它们重定向到 /dev/null

#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/fs.h>

int main (void)
{
    pid_t pid;
    int i;
    
    /* create new process */
    pid = fork ( );  
    if (pid == -1)  
        return -1;  
    else if (pid != 0)  
        exit (EXIT_SUCCESS);  

    /* create new session and process group */  
    if (setsid ( ) == -1)  
        return -1;  

    /* set the working directory to the root directory */  
    if (chdir ("/") == -1)  
        return -1;  

    /* close all open files--NR_OPEN is overkill, but works */  
    for (i = 0; i < NR_OPEN; i++)  
        close (i);  

    /* redirect fd's 0,1,2 to /dev/null */  
    open ("/dev/null", O_RDWR);  
    /* stdin */  
    dup (0);  
    /* stdout */  
    dup (0);  
    /* stderror */  
    
    /* do its daemon thing... */  
    
    return 0;  
}

有人能给我一个现有程序(如Apache)的源代码链接,以便我可以更深入地理解这个过程吗?


2
你可以在这里获取Apache源代码:http://httpd.apache.org/download.cgi。 - nmichaels
7
也许daemon(3)可以帮上忙。它基本上说所需的功能已经被实现了。 - Vlad
2
与https://dev59.com/mXA75IYBdhLWcg3wy8fr相关的内容。 - ninjalj
2
以上帖子几乎100%复制自书籍《Linux系统编程,第二版-直接与内核和C库交互》(作者Robert Love,页码173、174)。如果@RegisteredUser提到这一点会更好。 - patryk.beza
2004年,Devin Watson写了一篇关于这个主题的好文章:“Linux守护程序编写HOWTO”。它已经被归档在至少两个网站上:archive.org截至2006-06-03bibalex.org 截至2006-06-03 - Makyen
显示剩余4条评论
3个回答

22
如果你正在寻找一种简洁的方法,请考虑使用标准api-int daemon(int nochdir, int noclose);。该手册非常简单且自我解释。手册页面。在可移植性和稳定性方面,经过充分测试的API远胜于我们自己的实现。

1
我很高兴现在这是一个“答案”,而不是隐藏在评论中。也许你可以详细说明一下 - 链接到标准(或者更好的是,链接到教程或示例)。 - RJHunter
2
@deadbeef 这个回答比之前的纯链接回答好多了(参见http://meta.stackoverflow.com/questions/323508/what-to-do-with-broken-but-highly-upvoted-link-only-answers)。但是虽然它也很接近于一个“纯链接”(不过我认为给出的链接更加稳定)。我会尝试增强这个回答,给出一个简短的代码示例(特别是因为链接的手册中没有代码示例),或者引用摘要。 - πάντα ῥεῖ
2
请注意,daemon函数不符合POSIX标准。 - patryk.beza
2
在https://www.freedesktop.org/software/systemd/man/daemon.html#SysV%20Daemons上写道,“不应使用BSD的`daemon()`函数,因为它仅实现了这些[15]步骤的子集。” - oli_arborum

1
在 Linux 中,可以轻松实现以下操作:
int main(int argc, char* argv[])
{
    daemon(0,0);
    while(1)
    {
        sleep(10)
        /*do something*/
    }

    return 0;
}

0

关于关闭文件描述符的部分,在Linux下,GLIBC的deamon()服务仅仅使用dup2()将文件描述符0、1和2重定向到/dev/null。以下是GLIBC 2.34中daemon()的代码片段(文件misc/daemon.c):

    if (!noclose) {
        struct stat64 st;

        if ((fd = __open_nocancel(_PATH_DEVNULL, O_RDWR, 0)) != -1
            && (__builtin_expect (__fstat64 (fd, &st), 0)
            == 0)) {
            if (__builtin_expect (S_ISCHR (st.st_mode), 1) != 0
#if defined DEV_NULL_MAJOR && defined DEV_NULL_MINOR
                && (st.st_rdev
                == makedev (DEV_NULL_MAJOR, DEV_NULL_MINOR))
#endif
                ) {
                (void)__dup2(fd, STDIN_FILENO);
                (void)__dup2(fd, STDOUT_FILENO);
                (void)__dup2(fd, STDERR_FILENO);
                if (fd > 2)
                    (void)__close (fd);

使用NR_OPEN常量(通常等于1024)关闭所有可能打开的文件描述符并不可靠,因为打开的文件描述符数量的限制可以通过setrlimit(RLIMIT_NOFILE, ...)进行更改。
当前打开的文件描述符以符号链接名称的形式存在于/proc/pid/fd目录中。以下是我当前shell中此目录的内容:

$ ls -la /proc/$$/fd
total 0
dr-x------ 2 xxx xxx  0 sept.   4 09:32 .
dr-xr-xr-x 9 xxx xxx  0 sept.   4 09:32 ..
lrwx------ 1 xxx xxx 64 sept.   4 09:32 0 -> /dev/pts/2
lrwx------ 1 xxx xxx 64 sept.   4 09:32 1 -> /dev/pts/2
lrwx------ 1 xxx xxx 64 sept.   4 09:32 2 -> /dev/pts/2
lrwx------ 1 xxx xxx 64 sept.   4 09:41 255 -> /dev/pts/2

因此,一个为当前进程打开该目录的服务函数可以用于仅关闭实际打开的文件描述符:
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void close_opened_fds(void)
{
  DIR *dp;
  char proc_fd_dirname[256];
  int my_fd;
  struct dirent *d;
  int fd;

  // Open /proc/<mypid>/fd directory
  snprintf(proc_fd_dirname, sizeof(proc_fd_dirname), "/proc/%d/fd", getpid());
  dp = opendir(proc_fd_dirname);
  if (!dp) {
    return;
  }

  // Get the file descriptor associated to the preceding open
  my_fd = dirfd(dp);

  while((d = readdir(dp))) {

    // Skip '.' and '..' directories
    if (d->d_name[0] == '.') {
      continue;
    }

    fd = atoi(d->d_name);

    // Close the file except if it is the fd of the opened directory
    if (fd != my_fd) {
      close(fd);
    }

  }

  closedir(dp);

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接