本文来自个人博客:https://dunkwan.cn
编程规则
在编写守护进程时,需要遵循一些基本规则。下面就说明一下这些规则。
- 首先调用
umask
函数将文件模式创建屏蔽字设置为一个已知值。 - 调用
fork
函数,返货是父进程exit
。 - 调用
setsid
创建一个新会话。 - 将当前工作目录更改为根目录。
- 关闭不再需要的文件描述符。
- 某些守护进程打开
/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 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 /", 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, 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);
}
}
测试示例:
main
程序调用上面daemonize
函数,然后main
进入休眠状态。
#include "../../include/apue.h"
#include <syslog.h>
#include <fcntl.h>
#include <sys/resource.h>
void daemonize(const char *);
int main(void)
{
daemonize("13-1");
sleep(100);
return 0;
}
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 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 /", 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, 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);
}
}
结果如下:
出错处理
守护进程存在的一个问题是如何处理出错信息。因为守护进程本身不具备控制终端,无法将错误信息简单的写到标准错误上,随之就诞生了syslog设施来对守护进程产生的错误信息来进行集中的处理。
对于该设施的接口存在以下四个。
openlog
:用于创建一个对于系统日志程序的连接。
syslog
:产生一个日志信息。
closelog
:关闭一个被用于写系统日志的文件描述符。
setlogmask
:用于设置进程的记录优先级屏蔽字。
#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 naskpri);
返回值:前日志记录优先级屏蔽字值。
openlog
中option
参数有如下选项。
openlog
中facility
参数值情况如下:
syslog
中level
参数值的情况如下:
除了syslog
,许多平台还提供它的一种变体来处理可变参数列表。
#include <syslog.h>
#include <stdarg.h>
void vsyslog(int priority, const char *format, va_list arg);
大多数的syslog
的实现将使消息短时间处于队列中,如果在此段时间中有重复消息到达,那么syslog
守护进程不会把它写到日志记录中,而是会打印输出一条类似于“上一条消息重复了N次”的消息。
单实例守护进程
如何使用文件和记录锁来保证只运行一个守护进程的一个副本。
#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);
}
守护进程的惯例
守护进程可以读其配置文件。
#include "apue.h"
#include <pthread.h>
#include <syslog.h>
sigset_t mask;
extern int already_running(void);
void
reread(void)
{
/* ... */
}
void *
thr_fn(void *arg)
{
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 = strrchr(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 error");
/*
* 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.
*/
/* ... */
syslog(LOG_INFO, "hello dunkwan...");
pause();
//exit(0);
}
结果如下:
命令行下执行./reread ;ps -axj | grep reread
可得如下结果,由此得知PID
为11007。
然后再执行cat /var/run/daemon.pid
查看配置文件中进程ID。
最后通过执行kill -s HUP 11007
向守护进程发出信号,然后通过cat /var/log/syslog
查看产生的log信息。
源代码