守护进程也称精灵进程,是生存期较长的一种进程。它们常常在系统自举时启动,仅在系统关闭时才终止。因为它们没有
控制终端,所以说它们是在后台运行的。
编写守护进程规则
1、调用umask将文件模式创建屏蔽字设置为0。
2、调用fork,然后使父进程退出。
3、调用setsid创建一个新会话。使调用进程:a、成为新会话的首进程,b、成为一个新进程组的组长进程,c、没有控制终端。
4、将当前工作目录改为根目录。
5、关闭不再需要的文件描述符。
6、某些守护进程打开/dev/null使其具有文件描述符0、1、2,这样,任何一个试图读标准输入、写标准输出或标准出错的库 例程都不会产生任何效果。
#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", cmd);
if((pid = fork()) < 0)
err_quit("%s: can't fork", cmd);
else if(pid != 0) /* parent */
exit(0);
/* Change the current working diretory to the root so
we won't prevent file systems from being unmounted */
if(chdir("/") < 0)
err_quit("%s: can't change directory to /", cmd);
/* 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, 2 to /dev/null */
fd0 = open("/dev/null", O_RDWR);
fd1 = dup(0);
fd2 = dup(0);
/* Initialize the log file */
/*
void openlog(const char *ident, int option, int facility);
ident:程序名称。
option:指定许多项的位屏蔽。
LOG_CONS---若日志消息不能通过UNIX域数据报送至syslogd,
则将该消息写至控制台。
facility:目的是可以让配置文件说明,来自不同设施的消息将以不同方式进行处理。
LOG_DAEMON---系统守护进程。
*/
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);
}
}
int main(void)
{
daemonize("cron");
pause();
return 0;
}
结果
用ps命令验证,没有一个活动线程的ID是2625,说明守护进程在一个孤儿进程组中,它不是一个会话首进程,于是不会有机会分配到
一个控制终端。这是由第二个fork造成的。可见此守护进程已被正确初始化
保证只运行某个守护进程的一个副本
文件锁和记录锁时可用来保证一个守护进程只有一个副本在运行。如果每个守护进程创建一个文件,并且在整个文件上加上一把写锁,那就只允许创建一把这样的写锁,所以在此之后如试图创建一把这样的写锁就将失败。当该守护进程终止时,这把锁将被自动删除。
single.h
#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 "/val/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)
{
/*
void openlog(const char *ident, int option, int facility);
void syslog(int priority, const char *fromat, ...);
如果不调用openlog,第一次调用syslog时,自动调用openlog
priority:facility和level的组合。level--LOG_ERR--出错状态。
*/
syslog(LOG_ERR, "can't open %s: %s", LOCKFILE, strerror(errno));
exit(1);
}
/*
int lockf(int fd, int cmd, off_t len);
如果 len 为0,则锁定从当前偏移量到文件结尾的区域
#define F_LOCK 1 //互斥锁定区域
*/
if(lockf(fd, F_TLOCK, 0) < 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目录中,文件名通常为name.pid。
2、若守护进程支持配置选项,那么配置文件通常存放在/etc目录中,文件名通常为name.conf。
3、守护进程可用命令行启动,但通常它们是由系统初始化脚本之一启动的(/etc/rc*或/etc/init.d/*)。
4、若一守护进程有一配置文件,那么当该守护进程启动时,它读该文件,但此之后一般不会再查看它。
守护进程重读配置文件
若管理员更改了配置文件,那么守护进程可能需要被停止,然后再启动,以使配置文件的更改生效。为避免此种麻烦,某些守护进程将捕捉SIGHUP信号,当接收到该信号时,重读配置文件。因为守护进程并不与终端结合,可能是无控制终端的会话首进程,可能是孤儿进程组的成员,所以守护进程不期望接收SIGHUP。
使用sigwait及多线程重读配置文件。
1、调用daemonize初始化守护进程。
2、调用alread_runing确保该守护进程只有一个副本在运行。
3、SIGHUP信号仍被忽略,需恢复对信号SIGHUP的系统默认处理方式,否则调用sigwait的线程不会见到该信号。
4、阻塞所有信号,然后创建一线程来处理信号,该线程只需等待SIGHUP和SIGTERM。当收到SIGHUP信号时,调用reread重读配置文件。当收到SIGTERM时,记录一消息,然后终止。
#include "apue.h"
#include <pthread.h>
#include <syslog.h>
#include "single.h"
sigset_t mask;
void reread(void)
{
/* ... */
}
void *thr_fn(void *args)
{
int err, signo;
for(;;)
{
err = sigwait(&mask, &signo);
if(err != 0)
{
syslog(LOG_ERR, "sigwait failed");
exit(1);
}
switch(signo)
{
case SIGHUP:
syslog(LOG_INFO, "Re-reading configuration 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 = strchr(argv[0], '/')) == NULL)
cmd = argv[0];
else
cmd++;
/* Become a daemon */
daemonize(cmd);
/* Make sure only one copy of the daemon is running */
if(already_running())
{
syslog(LOG_ERR, "daemon already running");
exit(1);
}
/* Restore SIGHUP default and block all signals */
sa.sa_handler = SIG_DFL;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if(sigaction(SIGHUP, &sa, NULL) < 0)
err_quit("%s: can't restore SIGHUP default");
sigfillset(&mask);
if((err = pthread_sigmask(SIG_BLOCK, &mask, NULL)) != 0)
err_exit(err, "SIG_BLOCK err");
/* Create a thread to handle SIGHUP and SIGTERM */
err = pthread_create(&tid, NULL, thr_fn, 0);
if(err != 0)
err_exit(err, "can't create thread");
/* Proceed with the rest of the daemon */
/* ... */
exit(0);
}
守护进程重读配置文件的另一种实现
守护进程无需使用多线程也可以捕捉SIGHUP并重读其配置文件。
在初始化守护进程后,为SIGHUP和SIGTERM配置信号处理程序。可将重读逻辑放在信号处理程序中,也可只在其中设置一个标志,有守护进程的主线程做所需的工作。
#include "apue.h"
#include <syslog.h>
#include <errno.h>
#include "single.h"
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 = strchr(argv[0], '/')) == NULL)
cmd = argv[0];
else
cmd++;
/* Become a daemon */
daemonize(cmd);
/* Make sure only one copy of the daemon is running */
if(already_running())
{
syslog(LOG_ERR, "daemon already running");
exit(1);
}
/* Handle signals of interest */
sa.sa_handler = sigterm;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGHUP);
sa.sa_flags = 0;
if(sigaction(SIGTERM, &sa, NULL) < 0)
{
syslog(LOG_ERR, "can't catch SIGTERM: %s", strerror(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)
{
syslog(LOG_ERR, "can't catch SIGHUP: %s", strerror(errno));
exit(1);
}
/* Proceed with the rest of the daemon */
/* ... */
exit(0);
}