编码一个守护进程的一些规则避免不想要的交互发生。我们介绍这些规则,然后展示一个实现它们的函数,daemonize。
1、第一件
要做的事是调用umask来设置文件模式创建掩码为0。继承而来的文件模式创建掩码可以被设置为拒绝某些权限。如果守护进程要创建文件,它可以想设置特定
的权限。例如,如果它明确地用组读和组写来创建文件,那么一个关闭这些权限的文件模式创建掩码将会撤销它的努力。
2、调用fork并让父进
程exit。这里做了几件事。首先,如果守护进程被作为一个简单的外壳命令启动,那么让它的父进程终止会让外壳认为命令完成了。第二,子进程继承了父进程
的进程组ID但有一个新的进程ID,所以我们被保证子进程不是一个进程组长。这是接下来调用setsid的先决条件。
3、调用setsid
来创建新的会话。9.5节列出的三个步骤会生。进程a、变为一个新会话的会话领导;b、变为新进程组的进程组长;c、没有控制终端。在基于系统V的系统
下,一些人建议此时再次调用fork并让父进程终止。第二个子进程作为守护进程继续。这保证了守护进程不是一个会话领导,这样避免了在系统V规则下的控制
终端的申请(9.6节)。替代地,为了避免申请一个控制终端,确保无论何时打开一个终端设备时都要指明O_NOCTTY。
4、改变当前工作
目录为根目录。从父进程继承的当前工作目录可能在一个挂载的文件系统上。因为守护进程通过活到系统重启,所以如果守护进程保持在一个挂载的文件系统上,那
么系统文件可以被卸载。做为另一种选择,一个守护进程可能改变当前工作目录到一些特定的位置,在那里它们将执行它们的工作。例如,行打印机假脱机程序守护
进程经常改变它们的外部设备地址。
5、不需要的文件描述符应该被关闭。这阻止了守护进程握住任何可能从父进程(外壳或其它进程)继承的文件描述符。我们可以使用我们的open_max函数(2.5节)或getrlimit函数(7.11节)来决定最高的文件描述符并关闭所有不超过这个值的描述符。
6、
一些守护进程打开文件描述符0、1、2为/dev/null,以便任何尝试从标准输入读或向标准输出写的库例程都会没有效果。因为守护进程和一个终端设备
无关联,所以没有地方可以显示输出;也没有地方从一个交互的用户那接收输入。即使守护进程从一个交互的会话启动,守护进程也在后台运行,而登录会话可以终
止而不影响这个守护进程。如果其他用户在相同的终端设备上登录,那么我们不会想让守护进程的输出显示在终端上,而用户不会期望他们的输入被守护进程读。
下面展示了一个可以在一个想把自己初始化为一个守护进程的程序里调用的函数。
- #include <syslog.h>
- #include <fcntl.h>
- #include <sys/resource.h>
- #include <signal.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) {
- printf("%s: can't get file limit", cmd);
- exit(1);
- }
- /*
- * Become a session leader to lose controlling TTY.
- */
- if ((pid = fork()) < 0) {
- printf("%s: can't fork", cmd);
- exit(1);
- } 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) {
- printf("%s: can't ignore SIGHUP", cmd);
- exit(1);
- }
- if ((pid = fork()) < 0) {
- printf("%s: can't fork", cmd);
- exit(1);
- } 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) {
- printf("%s: can't change directory to /", cmd);
- exit(1);
- }
- /*
- * 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);
- }
- }
如果daemonize函数在一个main程序被调用后接着程序睡眠,那么我们可以用ps命令来检查这个守护进程的状态。
$ ps -axj | grep -E "(PID)|(a.out)"
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1 5065 5064 5064 ? -1 S 1000 0:00 ./a.out
我
们也可以使用ps来验证没有活动的ID
5064的进程。这意味着我们的守护进程在一个孤立进程组里(9.10节),并且没有一个会话领导,因此没有机会来分配一个控制终端。这是在
daemonize函数里执行第二个fork的结果。我们可以看到我们的守护进程被正确地初始化了。
阅读(522) | 评论(0) | 转发(0) |