您的位置:首页 > 产品设计 > UI/UE

APUE中为何创建守护进程时,为何需要2次fork?

2013-03-28 23:15 363 查看
1. 创建守护进程的一般步骤(参考链接:http://learn.akae.cn/media/ch34s03.html)
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>

void daemonize(void)
{
pid_t  pid;

/*
* Become a session leader to lose controlling TTY.
*/
if ((pid = fork()) < 0) {
perror("fork");
exit(1);
} else if (pid != 0) /* parent */
exit(0);
setsid();

/*
* Change the current working directory to the root.
*/
if (chdir("/") < 0) {
perror("chdir");
exit(1);
}

/*
* Attach file descriptors 0, 1, and 2 to /dev/null.
*/
close(0);
open("/dev/null", O_RDWR);
dup2(0, 1);
dup2(0, 2);
}

int main(void)
{
daemonize();
while(1);
}


1. 创建守护进程的一般步骤(参考链接:http://learn.akae.cn/media/ch34s03.html)

解释:
(1). 第一次调用fork,是为了保证后面的setsid能够成功,因为调用setsid成功的前提是当前进程不能是进程组的Leader,否则返回-1,要保证当前进程不是进程组首进程,只需要掉用fork在调用setsid就可以了。fork创建的子进程和父进程同属于一个进程组,进程组的leader必然是第一个进程,而子进程不可能是改组的第一个进程,在子进程中调用setsid就不会有问题了。

(2). 调用setsid()
调用该函数的的结果是:
使调用进程(1)成为新会话(session)的首进程,(2)成为一个新进程组的组长(leader),(3)没有控制终端。

在执行setsid()后,APUE提供的源码进行了第二次fork(),这次fork是必须的么?
APUE给的解释如下:
在基于系统V的系统中,有些人建议在此时再调用fork,并使父进程终止。第二个子进程作为精灵进程继续运行。这样就保证了该精灵进程不是对话期的首进程,于是按照SVR规则(见9.6节)可以防止它取得控制终端。另一方面,为了避免取得控制终端,无论何时打开一个终端设备都要指定O_NOCTTY。
回到APUE中第9.6节,原书给出的解释如下:





Systems derived from UNIX System V allocate the controlling terminal for a session when the session leader opens the first terminal device that is not already associated with a session. This assumes
that the call to open by the session leader does not specify the O_NOCTTY flag (Section 3.3).

BSD-based systems allocate the controlling terminal for a session when the session leader calls ioctl with a request argument of TIOCSCTTY (the third argument is a null pointer).
The session cannot already have a controlling terminal for this call to succeed. (Normally, this call to ioctl follows a call to setsid, which guarantees that the process is a session leader without a controlling terminal.) The POSIX.1 O_NOCTTY flag
to open is not used by BSD-based systems, except in compatibility-mode support for other systems.

参考链接:http://webcache.googleusercontent.com/search?q=cache:XGMOwrA0CNIJ:jessinio.blogspot.com/2010/05/daemonizefork.html+linux+daemon+%E4%B8%BA%E4%BD%95%E8%A6%812%E6%AC%A1fork&cd=9&hl=zh-CN&ct=clnk&gl=cn

fork第二次是防止被在打开一个terminal device时被系统分配到controlling terminal

只要不乱open(或者使用O_NOCTTY是不需要fork第二次的).

话又说回来, linux是怎么分配controlling terminal的呢?

APUE在这里没有提到linux, 只是提到了BSD和system-V的不同. 搜了一下, linux和BSD是一样的, 使用ioctl得到controlling terminal, URL: http://linux.die.net/man/4/tty_ioctl
TIOCSCTTY int arg Make the given tty the controlling tty of the current process.

总结如下:
第二次fork不是必须的,加上它是为了system-V系统兼容性的考虑。
(1)在System-V的系统下,在调用open时没有指定O_NOCTTY标志,当会话首进程打开第一个尚未与一个会话相关联的终端设备时,System-V系统将此终端作为控制终端分配此会话。
(2)在Free-BSD系统中,当会话首进程采用TIOCSSCTTY作为request参数(第3个参数是空指针)调用ioctl时,基于BSD的系统会为会话分配控制终端。

The first thing to do is call umask to set the file mode creation mask to 0. The file mode creation mask that's inherited could be set to deny certain permissions. If the daemon process is
going to create files, it may want to set specific permissions. For example, if it specifically creates files with group-read and group-write enabled, a file mode creation mask that turns off either of these permissions would undo its efforts.

Call fork and have the parent exit. This does several things. First, if the daemon was started as a simple shell command, having the parent terminate makes the shell think that the
command is done. Second, the child inherits the process group ID of the parent but gets a new process ID, so we're guaranteed that the child is not a process group leader. This is a prerequisite for the call to setsid that is done next.

Call setsid to create a new session. The three steps listed in Section 9.5 occur. The process (a) becomes a session leader of a new session, (b) becomes the process group leader
of a new process group, and (c) has no controlling terminal.

Under
System Vbased systems, some people recommend calling fork again at this point and having the parent terminate. The second child continues as the daemon. This guarantees that the daemon is not a session leader, which prevents it from acquiring a controlling
terminal under the System V rules (Section 9.6). Alternatively, to avoid acquiring a controlling terminal, be sure to specify O_NOCTTY whenever opening a terminal device.

Change the current working directory to the root directory. The current working directory inherited from the parent could be on a mounted file system. Since daemons normally exist until the system
is rebooted, if the daemon stays on a mounted file system, that file system cannot be unmounted.

Alternatively, some daemons might change the current working directory to some specific location, where they will do all their work. For example, line printer spooling daemons often change to their
spool directory.

Unneeded file descriptors should be closed. This prevents the daemon from holding open any descriptors that it may have inherited from its parent (which could be a shell or some other process). We
can use our open_max function (Figure 2.16) or the getrlimit function (Section 7.11) to determine the highest descriptor and close all descriptors up to that value.

Some daemons open file descriptors 0, 1, and 2 to /dev/null so that any library routines that try to read from standard input or write to standard output or standard error will have no effect.
Since the daemon is not associated with a terminal device, there is nowhere for output to be displayed; nor is there anywhere to receive input from an interactive user. Even if the daemon was started from an interactive session, the daemon runs in the background,
and the login session can terminate without affecting the daemon. If other users log in on the same terminal device, we wouldn't want output from the daemon showing up on the terminal, and the users wouldn't expect their input to be read by the daemon.

APUE的daemon例程:

#include "apue.h"
#include <syslog.h>
#include <fcntl.h>
#include <sys/resource.h>

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);
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");
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 /");

/*
* 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);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: