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);
}
其中
SIGHUP
is 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_DFL
、SIG_IGN
和 SIG_ERR
有。)
如果守护进程出于任何原因打开终端设备——例如,因为库想要将错误消息打印到控制台,并打开 /dev/tty1
或类似的操作——,守护进程将无意中获得一个控制终端。除了插入 open()
、fopen()
、opendir()
等,以确保它们的底层 open()
标志将包括 O_NOCTTY
,没有太多守护进程可以确保它不会无意中获取控制终端。相反,更简单的选择是假设它可能会发生,并简单地确保这不会造成太多麻烦。为了避免最典型的问题,当控制终端断开连接时死于 SIGHUP
,守护进程可以简单地忽略 SIGHUP
信号的传递。
简而言之,这是一种belt-and-suspenders的方法。 setsid()
从控制终端分离进程;并且 SIGHUP
被忽略,以防守护进程在不使用 O_NOCTTY
标志的情况下通过打开 tty 设备无意中获取控制终端。
我正在看一本关于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
is 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_DFL
、SIG_IGN
和 SIG_ERR
有。)
如果守护进程出于任何原因打开终端设备——例如,因为库想要将错误消息打印到控制台,并打开 /dev/tty1
或类似的操作——,守护进程将无意中获得一个控制终端。除了插入 open()
、fopen()
、opendir()
等,以确保它们的底层 open()
标志将包括 O_NOCTTY
,没有太多守护进程可以确保它不会无意中获取控制终端。相反,更简单的选择是假设它可能会发生,并简单地确保这不会造成太多麻烦。为了避免最典型的问题,当控制终端断开连接时死于 SIGHUP
,守护进程可以简单地忽略 SIGHUP
信号的传递。
简而言之,这是一种belt-and-suspenders的方法。 setsid()
从控制终端分离进程;并且 SIGHUP
被忽略,以防守护进程在不使用 O_NOCTTY
标志的情况下通过打开 tty 设备无意中获取控制终端。