本章将说明守护进程结构,以及如何编写守护进程程序。
守护进程,也就是通常说的Daemon进程,是Unix中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。
编程规则
在编写守护进程程序时需遵循一些基本规则,以防止产生不必要的交互作用。下面将说明这些规则。
1.调用umask将文件模式创建屏蔽字设置为一个已知值(通常是0)
2.调用fork,然后使父进程exit,保证了子进程不是进程组的组长进程,是下面进行setsid调用的先决条件
3.调用setsid创建一个新会话。然后它会执行3个步骤:(a)成为新会话的首进程 (b)成为一个新进程组的组长进程 (c)没有控制终端
4.将当前工作目录更改为根目录。
5.关闭不再需要的文件描述符
6.某些守护进程打开/dev/null使其具有文件描述符0、1、2
下面函数演示了创建守护进程的基本步骤
1 #include "apue.h" 2 #include <syslog.h> 3 #include <fcntl.h> 4 #include <sys/resource.h> 5 6 void 7 daemonize(const char *cmd) 8 { 9 int i, fd0, fd1, fd2; 10 pid_t pid; 11 struct rlimit rl; 12 struct sigaction sa; 13 14 /* 15 * Clear file creation mask. 16 */ 17 umask(0); 18 19 /* 20 * Get maximum number of file descriptors. 21 */ 22 if (getrlimit(RLIMIT_NOFILE, &rl) < 0) 23 err_quit("%s: can't get file limit", cmd); 24 25 /* 26 * Become a session leader to lose controlling TTY. 27 */ 28 if ((pid = fork()) < 0) 29 err_quit("%s: can't fork", cmd); 30 else if (pid != 0) /* parent */ 31 exit(0); 32 setsid(); 33 34 /* 35 * Ensure future opens won't allocate controlling TTYs. 36 */ 37 sa.sa_handler = SIG_IGN; 38 sigemptyset(&sa.sa_mask); 39 sa.sa_flags = 0; 40 if (sigaction(SIGHUP, &sa, NULL) < 0) 41 err_quit("%s: can't ignore SIGHUP", cmd); 42 if ((pid = fork()) < 0) 43 err_quit("%s: can't fork", cmd); 44 else if (pid != 0) /* parent */ 45 exit(0); 46 47 /* 48 * Change the current working directory to the root so 49 * we won't prevent file systems from being unmounted. 50 */ 51 if (chdir("/") < 0) 52 err_quit("%s: can't change directory to /", cmd); 53 54 /* 55 * Close all open file descriptors. 56 */ 57 if (rl.rlim_max == RLIM_INFINITY) 58 rl.rlim_max = 1024; 59 for (i = 0; i < rl.rlim_max; i++) 60 close(i); 61 62 /* 63 * Attach file descriptors 0, 1, and 2 to /dev/null. 64 */ 65 fd0 = open("/dev/null", O_RDWR); 66 fd1 = dup(0); 67 fd2 = dup(0); 68 69 /* 70 * Initialize the log file. 71 */ 72 openlog(cmd, LOG_CONS, LOG_DAEMON); 73 if (fd0 != 0 || fd1 != 1 || fd2 != 2) { 74 syslog(LOG_ERR, "unexpected file descriptors %d %d %d", 75 fd0, fd1, fd2); 76 exit(1); 77 } 78 }
出错记录
因为守护进程本就不应该有控制终端,所以不能简单地把出错信息写到标准错误上。
syslog设施是一个集中的守护进程出错记录设施,下图演示了syslog设施的详细组织结构:
有以下3种产生日志消息的方法:
1.内核例程可以调用log函数。任何一个用户进程都可以通过打开并读取/dev/klog设备来读取这些信息。
2.大多数用户进程(守护进程)调用syslog函数来产生日志消息。这使消息被发送至UNIX域数据报套接字/dev/log。
3.可将日志消息发向UDP端口514
通常,syslogd守护进程读取所有3种格式的日志消息。此守护进程在启动时读一个配置文件,其文件名一般为/etc/syslog.conf,该文件决定了不同类型的消息应送向何处。
该设施的接口是syslog函数
#include <syslog.h> void openlog(const char *ident,int option,int facility); void syslog(int priority,const char *format,...); void closelog(void); int setlogmask(int maskpri);
调用openlog是可选的。如果不调用openlog,则在第一次调用syslog时,自动调用openlog。
调用closelog也是可选的。因为它只是关闭增被用于与syslogd守护进程进行通信的描述符。
调用openlog使我们可以指定一个ident(一般是程序的名称),以后,此ident将被加至每则日志消息中。
option参数是指定各种选项的位屏蔽。下图介绍了可用的option选项:
facility参数值选取自下图,用来让配置文件说明来自不同设施的消息将以不同的方式进行处理。
调用syslog产生一个日志消息。其priority参数是facility和level的组合。其中level的值按优先级由最高到最低一次排列如下:
将format参数以及其他所有参数传至vsprintf函数以便进行格式化。其中,format中每个出现的%m字符都先被替换成与errno值相对应的出错消息字符串。
setlogmask函数用于设置进程的记录优先级屏蔽字。各条消息除非已在记录优先级屏蔽字中进行了设置,否则将不被记录。
除了syslog,很多平台还提供它的一种辩题来处理可变参数列表。
#include <syslog.h> #include <stdarg.h> void vsyslog(int priority,const char *format,va_list arg);
单实例守护进程
为了正常运作,某些守护进程会实现为在任一时刻只运行该守护进程的一个副本。
文件和记录锁机制(第十四章)为一种方法提供了基础,该方法保证了一个守护进程只有一个副本,下面函数将演示这一点。
1 #include <unistd.h> 2 #include <stdlib.h> 3 #include <fcntl.h> 4 #include <syslog.h> 5 #include <string.h> 6 #include <errno.h> 7 #include <stdio.h> 8 #include <sys/stat.h> 9 10 #define LOCKFILE "/var/run/daemon.pid" 11 #define LOCKMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) 12 13 extern int lockfile(int); 14 15 int 16 already_running(void) 17 { 18 int fd; 19 char buf[16]; 20 21 fd = open(LOCKFILE, O_RDWR|O_CREAT, LOCKMODE); 22 if (fd < 0) { 23 syslog(LOG_ERR, "can't open %s: %s", LOCKFILE, strerror(errno)); 24 exit(1); 25 } 26 if (lockfile(fd) < 0) { 27 if (errno == EACCES || errno == EAGAIN) { 28 close(fd); 29 return(1); 30 } 31 syslog(LOG_ERR, "can't lock %s: %s", LOCKFILE, strerror(errno)); 32 exit(1); 33 } 34 ftruncate(fd, 0); 35 sprintf(buf, "%ld", (long)getpid()); 36 write(fd, buf, strlen(buf)+1); 37 return(0); 38 }
其中lockfile函数如下
1 #include <unistd.h> 2 #include <fcntl.h> 3 4 int 5 lockfile(int fd) 6 { 7 struct flock fl; 8 9 fl.l_type = F_WRLCK; 10 fl.l_start = 0; 11 fl.l_whence = SEEK_SET; 12 fl.l_len = 0; 13 return(fcntl(fd, F_SETLK, &fl)); 14 }
守护进程实例
下面程序说明了守护进程可以重读其配置文件的一种方法
1 #include "apue.h" 2 #include <pthread.h> 3 #include <syslog.h> 4 5 sigset_t mask; 6 7 extern int already_running(void); 8 9 void 10 reread(void) 11 { 12 /* ... */ 13 } 14 15 void * 16 thr_fn(void *arg) 17 { 18 int err, signo; 19 20 for (;;) { 21 err = sigwait(&mask, &signo); 22 if (err != 0) { 23 syslog(LOG_ERR, "sigwait failed"); 24 exit(1); 25 } 26 27 switch (signo) { 28 case SIGHUP: 29 syslog(LOG_INFO, "Re-reading configuration file"); 30 reread(); 31 break; 32 33 case SIGTERM: 34 syslog(LOG_INFO, "got SIGTERM; exiting"); 35 exit(0); 36 37 default: 38 syslog(LOG_INFO, "unexpected signal %d ", signo); 39 } 40 } 41 return(0); 42 } 43 44 int 45 main(int argc, char *argv[]) 46 { 47 int err; 48 pthread_t tid; 49 char *cmd; 50 struct sigaction sa; 51 52 if ((cmd = strrchr(argv[0], '/')) == NULL) 53 cmd = argv[0]; 54 else 55 cmd++; 56 57 /* 58 * Become a daemon. 59 */ 60 daemonize(cmd); 61 62 /* 63 * Make sure only one copy of the daemon is running. 64 */ 65 if (already_running()) { 66 syslog(LOG_ERR, "daemon already running"); 67 exit(1); 68 } 69 70 /* 71 * Restore SIGHUP default and block all signals. 72 */ 73 sa.sa_handler = SIG_DFL; 74 sigemptyset(&sa.sa_mask); 75 sa.sa_flags = 0; 76 if (sigaction(SIGHUP, &sa, NULL) < 0) 77 err_quit("%s: can't restore SIGHUP default"); 78 sigfillset(&mask); 79 if ((err = pthread_sigmask(SIG_BLOCK, &mask, NULL)) != 0) 80 err_exit(err, "SIG_BLOCK error"); 81 82 /* 83 * Create a thread to handle SIGHUP and SIGTERM. 84 */ 85 err = pthread_create(&tid, NULL, thr_fn, 0); 86 if (err != 0) 87 err_exit(err, "can't create thread"); 88 89 /* 90 * Proceed with the rest of the daemon. 91 */ 92 /* ... */ 93 exit(0); 94 }
下面程序说明一个单线程守护进程然后捕捉SIGHUP并重读其配置文件
1 #include "apue.h" 2 #include <syslog.h> 3 #include <errno.h> 4 5 extern int lockfile(int); 6 extern int already_running(void); 7 8 void 9 reread(void) 10 { 11 /* ... */ 12 } 13 14 void 15 sigterm(int signo) 16 { 17 syslog(LOG_INFO, "got SIGTERM; exiting"); 18 exit(0); 19 } 20 21 void 22 sighup(int signo) 23 { 24 syslog(LOG_INFO, "Re-reading configuration file"); 25 reread(); 26 } 27 28 int 29 main(int argc, char *argv[]) 30 { 31 char *cmd; 32 struct sigaction sa; 33 34 if ((cmd = strrchr(argv[0], '/')) == NULL) 35 cmd = argv[0]; 36 else 37 cmd++; 38 39 /* 40 * Become a daemon. 41 */ 42 daemonize(cmd); 43 44 /* 45 * Make sure only one copy of the daemon is running. 46 */ 47 if (already_running()) { 48 syslog(LOG_ERR, "daemon already running"); 49 exit(1); 50 } 51 52 /* 53 * Handle signals of interest. 54 */ 55 sa.sa_handler = sigterm; 56 sigemptyset(&sa.sa_mask); 57 sigaddset(&sa.sa_mask, SIGHUP); 58 sa.sa_flags = 0; 59 if (sigaction(SIGTERM, &sa, NULL) < 0) { 60 syslog(LOG_ERR, "can't catch SIGTERM: %s", strerror(errno)); 61 exit(1); 62 } 63 sa.sa_handler = sighup; 64 sigemptyset(&sa.sa_mask); 65 sigaddset(&sa.sa_mask, SIGTERM); 66 sa.sa_flags = 0; 67 if (sigaction(SIGHUP, &sa, NULL) < 0) { 68 syslog(LOG_ERR, "can't catch SIGHUP: %s", strerror(errno)); 69 exit(1); 70 } 71 72 /* 73 * Proceed with the rest of the daemon. 74 */ 75 /* ... */ 76 exit(0); 77 }