首页 文章

用于在Unix系统编程中对命令进行deamonize的SIGHUP信号处理

提问于
浏览
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 作为其处理程序,为什么设置传递给 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);
    }
}

EDIT

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

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_DFLSIG_IGNSIG_ERR . )

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

    简而言之,它是一种腰带和吊带的方法 . setsid() 将进程与控制终端分离;如果守护进程无意中通过打开tty设备而不使用 O_NOCTTY 标志来获取控制终端,则忽略 SIGHUP .

相关问题