SIGHUP 信号处理以消除 Unix 系统编程中的命令

SIGHUP signal handling to deamonize a command in Unix system programming

我正在看一本关于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);
}

其中

SIGHUPis the signal sent to the controlling process (session leader) associated with a controlling terminal if a disconnect is detected by the terminal interface.

所以基本上 parent 进程调用 fork 然后退出。这样我们就可以保证 child 不是组长。 child 成为 setsid 的会话负责人。

我不明白信号 SIG_UP 是什么时候产生的:从定义来看它似乎是在关闭终端 window 时产生的,但是从代码中的注释

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

好像是在不同的情况下生成的:什么时候生成的?

其次它想忽略这个信号所以它设置sa.sa_handler = SIG_IGN然后调用sigaction。如果它忽略信号设置 SIG_IGN 作为它的处理程序,为什么它将传递给 sigaction 的掩码设置为 sigemptyset(&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在函数中调用了两次?

So basically ...

是的,parent 进程分叉了一个 child 进程,而 child 做了 setsid(),因此它将成为进程组组长(也是唯一的进程) 在新进程组中,并且没有控制终端。最后一部分是关键。

(如果有理由 child 进程应该 运行 与 parent 进程在同一个进程组中,可以使用 int fd = open("/dev/tty", O_RDWR); if (fd != -1) ioctl(fd, TIOCNOTTY); 从控制终端。setsid() 更容易,通常最好在新进程组中使用 child 运行,因为它和它的 children 可以发送一个不影响任何其他进程的信号。)

现在,每当没有控制终端的进程打开终端设备(tty 或 pseudo-tty)时,该设备将成为其控制终端(除非 O_NOCTTY 标志被使用打开设备)。

只要控制终端断开连接,SIGHUP 信号就会传送到以该终端作为其控制终端的每个进程。 (那个 SIG_UP 只是一个打字错误。信号名称没有下划线,只有特殊处理程序 SIG_DFLSIG_IGNSIG_ERR 有。)

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

简而言之,这是一种belt-and-suspenders的方法。 setsid() 从控制终端分离进程;并且 SIGHUP 被忽略,以防守护进程在不使用 O_NOCTTY 标志的情况下通过打开 tty 设备无意中获取控制终端。