SIGHUP信号处理可取消Unix系统编程中的命令

时间:2018-12-07 15:57:48

标签: c unix signals systems-programming sigaction

我正在读一本关于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:从定义来看,它似乎是在关闭Terminal窗口时生成的,但是从代码中的注释

/* *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在函数中被调用两次?

1 个答案:

答案 0 :(得分:1)

  

所以基本上...

是的,父进程派生了一个子进程,而那个子进程执行setsid(),因此它将成为新进程组中的进程组负责人(也是唯一的进程),并且没有控制权终端。最后一部分是关键。

(如果出于某种原因,子进程应该与父进程在同一进程组中运行,则可以使用int fd = open("/dev/tty", O_RDWR); if (fd != -1) ioctl(fd, TIOCNOTTY);与控制终端分离。setsid()更容易,而且通常最好还是让孩子在一个新的流程组中运行,因为可以向它及其孩子发送信号,而不会影响任何其他流程。)

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

无论何时断开控制端子,SIGHUP信号都会传递到以该端子为控制端子的每个过程。 (那只SIG_UP只是一个错字。信号名称没有下划线,只有特殊处理程序SIG_DFLSIG_IGNSIG_ERR才有。)

如果守护程序进程出于任何原因(例如,由于库要向控制台打印错误消息,并打开/dev/tty1或类似方式而打开终端设备),则守护程序将无意间获得了控制终端。除了插入open()fopen()opendir()等以确保其基础open()标记将包含O_NOCTTY之外,没有多少守护程序可以使用确保它不会无意中获得控制终端。取而代之,更简单的选择是仅假设可能,并简单地确保不会造成太多麻烦。为了避免最典型的问题(当控制终端断开连接时死于SIGHUP),守护进程可以简单地忽略SIGHUP信号的传递。

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