0

我正在读一本关于 Unix 系统编程的书。书中有一个创建守护进程的函数。

部分代码对我来说不是很清楚,特别是以下内容:

struct sigaction    sa;
....
/* *Become a session leader to lose controlling TTY. */
if ((pid = fork()) < 0)
{
    err_quit("%s: can’t fork", cmd);
}
else if (pid != 0) /* parent */
{
    exit(0); //the parent will exit
}
setsid();

/* *Ensure future opens won’t allocate controlling TTYs. */
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGHUP, &sa, NULL) < 0)
{
    err_quit("%s: can’t ignore SIGHUP", cmd);
}

在哪里

SIGHUP如果终端接口检测到断开连接,则发送到与控制终端关联的控制进程(会话领导者)的信号。

所以基本上父进程调用fork然后退出。这样我们就可以保证孩子不是组长。孩子成为与setsid.

我不明白何时SIG_UP生成信号:从定义看来它是在关闭终端窗口时生成的,但从代码中的注释来看

/* *Ensure future opens won’t allocate controlling TTYs. */

似乎它是在不同的情况下生成的:它是什么时候生成的?

其次它想忽略这个信号,所以它设置sa.sa_handler = SIG_IGN然后调用sigaction。如果它忽略SIG_IGN作为其处理程序的信号设置,为什么将传递给的掩码设置sigactionsigemptyset(&sa.sa_mask);?我的意思是如果没有处理程序,则不会使用在执行处理程序之前设置的掩码:是吗?

完整的功能如下:

void daemonize(const char *cmd)
{
    int i, fd0, fd1, fd2;
    pid_t pid;
    struct rlimit       rl;
    struct sigaction    sa;
    /* *Clear file creation mask.*/
    umask(0);
    /* *Get maximum number of file descriptors. */
    if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
    {
        err_quit("%s: can’t get file limit", cmd);
    }
    /* *Become a session leader to lose controlling TTY. */
    if ((pid = fork()) < 0)
    {
        err_quit("%s: can’t fork", cmd);
    }
    else if (pid != 0) /* parent */
    {
        exit(0); //the parent will exit
    }
    setsid();
    /* *Ensure future opens won’t allocate controlling TTYs. */
    sa.sa_handler = SIG_IGN;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    if (sigaction(SIGHUP, &sa, NULL) < 0)
    {
        err_quit("%s: can’t ignore SIGHUP", cmd);
    }
    if ((pid = fork()) < 0)
    {
        err_quit("%s: can’t fork", cmd);
    }
    else if (pid != 0) /* parent */
    {
        exit(0);
    }
    /*
    *Change the current working directory to the root so
    * we won’t prevent file systems from being unmounted.
    */
    if (chdir("/") < 0)
    {
        err_quit("%s: can’t change directory to /", cmd);
    }
    /*
    *Close all open file descriptors.
    */
    if (rl.rlim_max == RLIM_INFINITY)
    {
        rl.rlim_max = 1024;
    }
    for (i = 0; i < rl.rlim_max; i++)
    {
        close(i);
    }
    /*
    *Attach file descriptors 0, 1, and 2 to /dev/null.
    */
    fd0 = open("/dev/null", O_RDWR);
    fd1 = dup(0);
    fd2 = dup(0);
    /*
    *Initialize the log file.
    */
    openlog(cmd, LOG_CONS, LOG_DAEMON);
    if (fd0 != 0 || fd1 != 1 || fd2 != 2) {
        syslog(LOG_ERR, "unexpected file descriptors %d %d %d", fd0, fd1, fd2);
        exit(1);
    }
}

编辑

另外我还有一个问题。为什么fork在函数中被调用两次?

4

1 回答 1

1

所以基本上...

是的,父进程派生出一个子进程,并且该子进程setsid()这样做是为了使其成为新进程组中的进程组领导(也是唯一的进程),并且没有控制终端。最后一部分是关键。

(如果子进程应该与父进程在同一进程组中运行是有原因的,则可以使用int fd = open("/dev/tty", O_RDWR); if (fd != -1) ioctl(fd, TIOCNOTTY);它与控制终端分离。setsid()更容易,通常最好让子进程在新进程组中运行无论如何,因为它和它的孩子可以在不影响任何其他进程的情况下发送信号。)

现在,只要没有控制终端的进程打开终端设备(tty 或伪 tty),该设备将成为其控制终端(除非O_NOCTTY在打开设备时使用了该标志)。

每当控制终端断开连接时,都会向每个以该终端作为控制终端的进程传递 SIGHUP 信号。(那个 SIG_UP 只是一个错字。信号名称没有下划线,只有特殊的处理程序SIG_DFL,SIG_IGNSIG_ERRdo。)

如果守护进程出于任何原因打开终端设备——例如,因为库想要将错误消息打印到控制台,并打开/dev/tty1或类似地这样做——,守护进程将无意中获取控制终端。除了插入open()fopen()opendir()等,以确保它们的基础open()标志将包括O_NOCTTY,守护进程无法确保它不会无意中获取控制终端。相反,更简单的选择是假设它可能,并简单地确保这不会造成太多麻烦。为了避免最典型的问题,SIGHUP即在控制终端断开连接时死亡,守护进程可以简单地忽略SIGHUP信号的传递。

简而言之,这是一种束手无策的方法。将setsid()进程与控制终端分离;SIGHUP如果守护程序通过打开 tty 设备而不使用该O_NOCTTY标志无意中获取了控制终端,则将被忽略。

于 2018-12-09T04:50:51.730 回答