我正在读一本关于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 回答
是的,父进程分叉子进程,并且该子进程
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_IGN
和SIG_ERR
. )如果守护程序进程因任何原因打开终端设备 - 例如,因为库要将错误消息打印到控制台,并打开
/dev/tty1
或类似的操作 - ,守护程序将无意中获取控制终端 . 除了插入open()
,fopen()
,opendir()
等,以确保它们的基础open()
标志将包含O_NOCTTY
,守护程序可以做的事情并不多,以确保它不会无意中获取控制终端 . 相反,更简单的选择是假设它可能,并且只是确保这不会造成太多麻烦 . 为了避免最典型的问题,当控制终端断开连接时从SIGHUP
死亡,守护进程可以简单地忽略SIGHUP
信号的传递 .简而言之,它是一种腰带和吊带的方法 .
setsid()
将进程与控制终端分离;如果守护进程无意中通过打开tty设备而不使用O_NOCTTY
标志来获取控制终端,则忽略SIGHUP
.