概述
守护进程是在后台运行且不与任何控制终端关联的进程。
守护进程有多种启动方法:
(1)在系统启动阶段,许多守护进程由系统初始化脚本启动。这些脚本通常位于 /etc 目录或以 /etc/rc 开头的某个目录中,这些脚本启动的守护进程一开始时拥有超级用户特权。如 inetd 超级服务器,web 服务器、邮件服务器。
(2)许多网络服务器由 inetd 超级服务器启动。
(3)cron 守护进程本身由方法(1)启动,而由该进程启动执行的程序同样作为守护进程运行。
(4)at 命令用于指定将来某个时刻的程序执行。这些程序的执行时刻到来时通常由 cron 守护进程启动执行它们。
(5)守护进程还可以从用户终端或在前台或在后台启动。这么做往往是测试守护进程,或者重启因某种原因而终止的某个守护进程。
syslogd 守护进程
Unix 系统的 syslogd 守护进程是由系统的初始化脚本启动的。该进程在系统工作期间一直运行。
源自 Berkeley 的 syslogd 实现:
(1)读取配置文件:/etc/syslog.conf 的配置文件指定本守护进程可能收取的各种日志消息应该如何处理。
(2)创建一个 Unix 域数据报套接字,给它捆绑路径名 /var/run/log (在某些系统上是 /dev/log)。
(3)创建一个 UDP 套接字,给它捆绑端口 514(syslog 服务器使用的端口号)。较新的 syslogd 实现禁止创建 UDP 套接字,除非管理员明确要求。因为允许任何进程往这个套接字发送 UDP 数据报会让系统易遭拒绝服务攻击,其文件系统可能被填满,来自合法的进程的日志消息可能被排挤掉。
(4)打开路径名 /dev/klog。来自内核中的任何出错消息看着像是这个设备的输入。
此后 syslogd 守护进程在一个无限循环中运行:调用 select 以等待它的 3 个描述符(分别来自上面的 2,3,4)之一变为可读,读入日志消息,并按照配置文件进行处理。如果守护进程收到 SIGHUP 信号,那就重新读取配置文件。
syslog 函数
守护进程没有控制终端,它们就不能打印消息在 stderr 上。从守护进程中登记消息的常用技巧就是调用 syslog 函数。
#include <syslog.h>
void openlog(const char *ident, int option, int facility);
void syslog(int priority, const char *format, ...);
void closelog(void);
priority 参数是 级别(level)和设施(facility)的组合。message 参数类似 printf 的格式串,不过多了一个 m% 规范,它将被替换成与当前 errno 值对应的出错消息。如下:
syslog(LOG_INFO|LOG_LOCAL2, "rename(%s,%s):%m", file1, file2);
当 syslog 被应用进程首次调用时,它创建一个 Unix 域数据报套接字,然后调用 connect 连接到由 syslogd 守护进程创建的 Unix 域数据报套接字的众所周知路径名(如/var/run/log)。这个套接字一直保持打开,直到进程终止为止。进程也可以调用 openlog 和 closelog 来作为替换。
openlog 在首次调用 syslog 前调用,closelog 在进程不需要发送日志消息时调用。openlog 被调用时通常并不立即创建 Unix 域套接字。相反直到首次调用 syslog 时才打开。options 参数有 LOG_NDELAY 选项时迫使该套接字在 openlog 被调用时就创建。其 facility 参数为后续的 syslog 函数指定默认值。
守护进程用例
#include <syslog.h>
#include <unistd.h>
#include <signal.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <stdarg.h>
#define MAXFD 64
#define MAXLINE 1024
int daemon_proc; /* set nonzero by daemon_init() */
typedef void Sigfunc(int);
Sigfunc *signal(int signo, Sigfunc *func)
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if (signo == SIGALRM) {
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */
#endif
} else {
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */
#endif
}
if (sigaction(signo, &act, &oact) < 0)
return(SIG_ERR);
return(oact.sa_handler);
}
/* end signal */
Sigfunc *Signal(int signo, Sigfunc *func) /* for our signal() function */
{
Sigfunc *sigfunc;
if ( (sigfunc = signal(signo, func)) == SIG_ERR)
printf("signal error");
return(sigfunc);
}
/* Print message and return to caller
* Caller specifies "errnoflag" and "level" */
static void err_doit(int errnoflag, int level, const char *fmt, va_list ap)
{
int errno_save, n;
char buf[MAXLINE + 1];
errno_save = errno; /* value caller might want printed */
#ifdef HAVE_VSNPRINTF
vsnprintf(buf, MAXLINE, fmt, ap); /* safe */
#else
vsprintf(buf, fmt, ap); /* not safe */
#endif
n = strlen(buf);
if (errnoflag)
snprintf(buf + n, MAXLINE - n, ": %s", strerror(errno_save));
strcat(buf, "\n");
if (daemon_proc) {
syslog(level, buf);
} else {
fflush(stdout); /* in case stdout and stderr are the same */
fputs(buf, stderr);
fflush(stderr);
}
return;
}
void err_msg(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(0, LOG_INFO, fmt, ap);
va_end(ap);
return;
}
int daemon_init(const char *pname, int facility)
{
int i;
pid_t pid;
// 1、转变为后台进程
if((pid = fork()) < 0)
return -1;
else if(pid)
_exit(0); /* parent terminates */
/* child 1 continues... */
// 2、离开原先的进程组、会话,开启一个新会话,从而当前子进程1
// 变为新会话的会话头进程以及新进程组的进程组头进程,从而不再有
// 控制终端
if(setsid() < 0) /* become session leader */
return -1;
// 3、忽略 SIGHUP 信号,并且再次调用 fork 是确保本守护进程将来
// 即是打开了一个终端设备,也不会自动获得控制终端。因为这次 fork
// 后,产生的子进程2不再是会话头进程,即是有新的终端打开也是会话头进程获得。
// 同时:这里必须忽略 SIGHUP 信号,因为当会话头进程终止时(第一次fork产生
// 的子进程),其会话中的所有进程(即再次fork产生的子进程)都收到SIGHUP信号。
signal(SIGHUP, SIG_IGN);
if((pid=fork()) < 0)
return -1;
else if(pid)
_exit(0); /* child 1 terminates */
/* child 2 continues... */
daemon_proc = 1; /* for err_msg() functions */
// 4、改变当前的工作目录,避免卸载不了文件系统
chdir("/"); /* change working directory */
// 5、关闭打开的文件描述符,避免浪费系统资源
/* close off file descriptors */
for(i=0; i<MAXFD; i++)
close(i);
// 6、重定向标准输入,输出,错误流,因为守护进程没有控制终端
/* redirect stdin, stdout, and stderr to /dev/null */
int fd = open("dev/null", O_RDWR);
if (fd == -1) exit(1);
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
if(fd>2)
close(fd);
openlog(pname, LOG_PID, facility);
return 0;
}
int main(int argc, char **argv)
{
...
daemon_init(argv[0], 0);
...
if(error)
err_msg("..."); //在守护进程中就应避免调用诸如 printf fprintf
//之类的函数,daemon_init之后错误消息进入 syslog
}