写守护进程时, 需要fork两次吗?

glibc源码中我找到了daemon函数的实现:

int
daemon(nochdir, noclose)
    int nochdir, noclose;
{
    int fd;

    switch (fork()) {
    case -1:
        return (-1);
    case 0:
        break;
    default:
        _exit(0);
    }

    if (__setsid() == -1)
        return (-1);

    if (!nochdir)
        (void)__chdir("/");

    if (!noclose && (fd = __open(_PATH_DEVNULL, O_RDWR, 0)) != -1) {
        (void)__dup2(fd, STDIN_FILENO);
        (void)__dup2(fd, STDOUT_FILENO);
        (void)__dup2(fd, STDERR_FILENO);
        if (fd > 2)
            (void)__close (fd);
    }
    return (0);
}

这个把普通进程变成守护进程的函数,很明显只fork了一次. 同样的代码还有nginx:

/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) Nginx, Inc.
 */


#include <ngx_config.h>
#include <ngx_core.h>


ngx_int_t
ngx_daemon(ngx_log_t *log)
{
    int  fd;

    switch (fork()) {
    case -1:
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "fork() failed");
        return NGX_ERROR;

    case 0:
        break;

    default:
        exit(0);
    }

    ngx_pid = ngx_getpid();

    if (setsid() == -1) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "setsid() failed");
        return NGX_ERROR;
    }

    umask(0);

    fd = open("/dev/null", O_RDWR);
    if (fd == -1) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
                      "open(\"/dev/null\") failed");
        return NGX_ERROR;
    }

    if (dup2(fd, STDIN_FILENO) == -1) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDIN) failed");
        return NGX_ERROR;
    }

    if (dup2(fd, STDOUT_FILENO) == -1) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDOUT) failed");
        return NGX_ERROR;
    }

#if 0
    if (dup2(fd, STDERR_FILENO) == -1) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDERR) failed");
        return NGX_ERROR;
    }
#endif

    if (fd > STDERR_FILENO) {
        if (close(fd) == -1) {
            ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "close() failed");
            return NGX_ERROR;
        }
    }

    return NGX_OK;
}

也只fork了一次, 那为什么有的文章中却说要fork两次呢?

分析如下:

第一次fork的作用是为setsid服务的, 因为执行setsid的进程不能是session leader, 所以fork一个子进程, 在子进程里进行setsid动作.

而且第一次fork后, 我们已经结束掉了父进程, 子进程已经变成了孤儿进程, 挂靠在init进程下了. 那第二次fork还有必要吗?

那在unix高级环境编程 第13章是这样解释的:

Under System V–based systems, some people recommend calling fork again at this point and having the parent terminate. The second child continues as the daemon. This guarantees that the daemon is not a session leader, which prevents it from acquiring a controlling terminal under the System V rules (Section 9.6). Alternatively, to avoid acquiring a controlling terminal, be sure to specify O_NOCTTY
whenever opening a terminal device.

简单翻译一下:

在基于System V的系统中, 有些人推荐再fork一次, 这些fork产生的进程就不再是session leader了, 避免打开控制终端. 还有一种可选的方法,就是打开终端设备的时候指定O_NOCTTY来避免打开控制终端.

所以在写守护进程时, fork两次并不是必须的.

阅读 2.5k

推荐阅读
shuizhuniurou
用户专栏

shuizhuniurou

10 人关注
24 篇文章
专栏主页