我正在读一本关于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
在函数中被调用两次?
答案 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_DFL
,SIG_IGN
和SIG_ERR
才有。)
如果守护程序进程出于任何原因(例如,由于库要向控制台打印错误消息,并打开/dev/tty1
或类似方式而打开终端设备),则守护程序将无意间获得了控制终端。除了插入open()
,fopen()
,opendir()
等以确保其基础open()
标记将包含O_NOCTTY
之外,没有多少守护程序可以使用确保它不会无意中获得控制终端。取而代之,更简单的选择是仅假设可能,并简单地确保不会造成太多麻烦。为了避免最典型的问题(当控制终端断开连接时死于SIGHUP
),守护进程可以简单地忽略SIGHUP
信号的传递。
简而言之,这是一种束腰带的方法。 setsid()
将过程与控制终端分离;如果守护程序在不使用SIGHUP
标志的情况下通过打开tty设备无意中获得了控制终端,则将忽略O_NOCTTY
。