第十三章 守护进程
守护进程也称精灵进程是生存期较长的一种进程,它们常常在系统自举时启动,关闭时才终止,因为它们没有控制终端,所以说它们是在后台运行的。
系统进程依赖于操作系统实现。父进程ID为0的进程通常是内核进程,它们作为系统自举过程的一部分而启动。
一、编程规则
在编写守护进程程序时需遵循一些基本规则,以便防止产生并不需要的交互作用:
(1)首先要做的是调用umask将文件模式创建屏蔽字设置为0。由继承得来的文件模式创建屏蔽字可能会拒绝设置 某些权限。
(2)调用fork,然后使父进程退出。
(3)调用setsid以创建一个新会话。
(4)将当前工作目录更改为新目录。
(5)关闭不在需要的文件描述符。
(6)
实例:初始化一个守护进程
#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;
umask(0);
if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
{
err_quit("%s: can't get file limit", cmd);
}
if ((pid == fork()) < 0)
{
err_quit("%s: can't fork",cmd);
}
else if(pid != 0)
{
exit(0);
}
setsid();
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)
{
exit(0);
}
if (rl.rlim_max == RLIM_INFINITY)
{
rl.rlim_max = 1024;
}
for (i = 0; i < rl.rlim_max; i++)
{
close(i);
}
fd0 = open("/dev/null", O_RDWR);
fd1 = dup(0);
fd2 = dup(0);
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);
}
}
二、出错记录
有三种方法产生日志消息:
(1)内核例程可以调用log函数。
(2)大多数用户进程(守护进程)调用syslog(3)函数以产生日志信息。
(3)在此主机上的一个用户进程,或通过TCP/IP网络链接到此主机的其它主机上的一个用户进程可将日志消息发向UDP端口514.
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也是可选择的——它只是关闭曾被用于与syslog守护进程通信的描述符。
调用syslog产生一个日志消息。
setlogmask函数用于设置进程的记录优先级屏蔽字。
三、单实例守护进程
为了正常运作,某些守护进程实现为单实例的,也就是在任一时刻只运行该守护进程的一个副本。
文件锁和记录锁机制是一种方法的基础,该方法用来保护一个守护进程只有一个副本在运行。
文件锁个记录锁提供了一种方便的互斥机制。如果守护进程在整个文件上得到一把写锁,那么在该守护进程终止时,这把锁将被自动删除,这就简化了复原所需的处理,去除了对以前守护进程实例需要进行清理的有关操作。
实例:保证只运行某个守护进程的一个副本
#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_IROTH)
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);
}
四、守护进程的惯例
1.若守护进程使用锁文件,那么该文件通常存放在/var/run目录中。
2.若守护进程支持配置选项,那么配置文件通常放在/etc目录中。
3.守护进程可用命令行启动,但通常它们是由系统初始化脚本之一(/etc/rc* 或 /etc/init.d/*)启动的。
4.若一守护进程有一配置文件,那么当该守护进程启动时,它读改文件,但在此后一般就不会再查看它.
实例:守护进程重读配置文件
#include "apue.h"
#include <pthread.h>
#include <syslog.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.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_IROTH)
extern int lockfile(int);
static int already_running(void);
sigset_t mask;
void
reread(void)
{
}
void *
thr_fn(void *arg)
{
int err,signo;
err = sigwait(&mask, &signo);
for (;;)
{
if (err != 0)
{
syslog(LOG_ERR, "sigwait failed");
exit(1);
}
switch (signo)
{
case SIGHUP:
syslog(LOG_INFO, "Re-reading configuratttion file");
reread();
break;
case SIGTERM:
syslog(LOG_INFO, "got SIGTERM; exiting");
exit(0);
default:
syslog(LOG_INFO, "unexpected signal %d\n", signo);
}
}
return(0);
}
int
main(int argc, char *argv[])
{
int err;
pthread_t tid;
char *cmd;
struct sigaction sa;
if (cmd = strrchr(argv[0], '/') == NULL)
{
cmd = argv[0];
}
else
{
cmd++;
}
daemonize(cmd);
if (alreadyrunning())
{
syslog(LOG_ERR, "daemon already running");
exit(1);
}
sa.sa_handler = SIG_DFL;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGHUP, &sa, NULL) < 0)
{
err_quit("%s: can't reatore SIGHUP default");
}
sigfillset(&mask);
if ((err = pthread_sigmask(SIG_BLOCK, &mask, NULL)) != 0)
{
err_exit(err, "SIG_BLOCK error");
}
err = pthread_create(&tid, NULL, thr_fn, 0);
if (err != 0)
{
err_exit(err, "can't create thread");
}
exit(0);
}
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);
}
实例:守护进程重读配置文件的另一种实现
#include "apue.h"
#include <pthread.h>
#include <syslog.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <sys/stat.h>
extern int lockfile(int);
static int already_running(void);
void
reread(void)
{
}
void
sigterm(int signo)
{
syslog(LOG_INFO, "got SIGTERM; exiting");
exit(0);
}
void
sighup(int signo)
{
syslog(LOG_INFO, "Re-reading configuration file");
reread();
}
int
main(int argc, char *argv[])
{
char *cmd;
struct sigaction sa;
if (cmd = strrchr(argv[0], '/') == NULL)
{
cmd = argv[0];
}
else
{
cmd++;
}
daemonize(cmd);
if (alreadyrunning())
{
syslog(LOG_ERR, "daemon already running");
exit(1);
}
sa.sa_handler = sigterm;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGHUP);
sa.sa_flags = 0;
if (sigaction(SIGTERM, &sa, NULL) < 0)
{
err_quit(LOG_ERR, "can't catch SIGTERM: %s", strerron(errno));
exit(1);
}
sa.sa_handler = sighup;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGTERM);
sa.sa_flags = 0;
if (sigaction(SIGHUP, &sa, NULL) < 0)
{
err_quit(LOG_ERR, "can't catch SIGHUP: %s", strerron(errno));
exit(1);
}
exit(0);
}
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);
}