第十三章:守护进程
13.1:引言
守护进程也称精灵进程(daemon)是生存期较长的一种进程。它们常常在系统自举时启动,尽在系统关闭时才终止。因为它们没有控制终端,所以说它们是在后台运行的。Unixi有很多守护进程,它们执行日常事务活动。
13.2:守护进程的特征
查看守护进程: ps -axj
注意:
大多数守护进程都以超级用户特权运行。没有一个守护进程拥有控制终端,其终端名设置为问号(?),终端前台进程组ID设置为-1。内核守护进程以无控制终端方式启动。用户层守护进程缺少控制终端可能是守护进程调用了setsid的结果。所有用户层守护进程都是进程组的组长进程一会会话的首进程,而且是这些进程组和会话中的唯一进程。最后,应当引起注意的是大多数守护进程的父进程是init进程。
13.3:编程规则
在编写守护进程时需要遵循一些基本规则,以便防止产生并不需要的交互作用。下面先说明这些规则,然后给出按照这些规则编写的函数daemonize。
(1)首先要做的是调用umask将文件模式创建屏蔽字设置为0。
(2)调用fork,然后使父进程退出。
(3)调用setsid以创建一个新会话。
(4)将当前工作目录更改为根目录。
(5)关闭某些不需要的文件描述符。
(6)某些守护进程打开/dev/null使其具有文件描述符0、1、2,这样,任何一个试图读标准输入、写标准输出或标准出错的库例程都不会产生任何效果。
实例:
#include "apue.h" #include <syslog.h> #icnldue <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) { exit(0); } setsid(); // ensure future opens won't allocate controlling ttya sa.sa_handle = SIG_IGN; sigemptyset(&sa.sa_mask); sa.sa_flag = 0; if (sigaction(SIGHUB, &sa, NULL) < 0) { err_quit("%s: can't ignore SIGHUB"); } if ((pid = fork()) < 0) { err_quit("%s: can't fork", cmd); } else if (pid != 0) { 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); } }
13.4:出错记录
守护进程由于没有控制终端,所以输出错误信息需要特殊处理,可以使用syslog日志记录机制。
13.5:单实例守护进程
为了正常运作,某些守护进程实现为单实例的,也就是在任一时刻只运行该守护进程的一个副本。文件锁和记录锁机制是一种方法的基础,该方法用来保证一个守护进程只有一个副本在运行。如果每一个守护进程创建一个文件,并且在整个文件上加上一把写锁,那就只允许创建一把这样的写锁,所以在此之后如试图再创建一把这样的写锁就将失败,以此向后续守护进程副本指明已有一个副本正在运行。
文件和记录锁提供了一种方便的互斥机制。如果守护进程在整个文件上得到一把写锁,那么在该守护进程终止时,这把写锁将自动删除。这就简化了复原所需的处理,去除了对以前的守护进程实例需要进行清理的有关操作。
实例:函数说明了如果使用文件和记录锁以保证只运行某守护进程的一个副本。
#include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <syslog.h> #include <string.h> #include <errno.h> #include <stdio.h> #include <sys/stat.h> #define LOCKFILE "/var/run/daemon.pid" #define LOCKMODE (S_IRUSR | S_IWUSR | S_IRGRP | s_IPOTH) extern int lockfile(int); int already_running(void) { int fd; char buf[16]; fd = open(LOCKFILE, O_RDWR | O_CREAT, LOCKMODE); if (fd < 0) { syslog(LOG_ERR, "can't open %s: %s", LOCKFILE, strerror(errno)); exit(1); } if (lockfile(fd) < 0) { if (errno == EACCES || errno == EAGAIN) { close(fd); return 1; } syslog(LOG_ERR, "can't lock %s: %s", LOCKFILE, strerror(errno)); exit(1); } ftruncate(fd, 0); sprintf(buf, "%ld", (long)getpid()); write(fd, buf, strlen(buf) + 1); return 0; }
13.6:守护进程的惯例
在Unix系统中,守护进程遵循下列公共惯例:
- 若守护进程使用锁文件,那么该文件通常存放在/var/run目录中。
- 若守护进程支持配置选项,那么配置文件通常存放在/etc目录中。配置文件的名字通常是name.conf,其中name是该守护进程或服务的名字。例如,syslogd守护进程的配置文件在/etc/syslog.conf。
- 守护进程可用命令行启动,但通常它们是由系统初始化脚本之一(/etc/rc*或/etc/init.d/*)启动的。如果守护进程终止时,应当自动地重新启动它,则我们可在/etc/inittab中为该守护进程包括_respawn记录项,这样,init就将重新启动该进程。
- 若一守护进程有以配置文件,那么该守护进程启动时,它读该文件,但在此之后一般就不会再查看它。
实例:所示程序说明了守护进程可以重读其配置文件的一种方法。该程序使用sigwait以及多线程。