五、守护进程编程实例
守护进程实例先通过InitDaemon函数将普通进程改造成守护进程。然后,判断是否已有该守护进程的实例在运行,若有则退出运行;若无则先阻塞SIGHUP和SIGUSR1信号,然后为SIGHUP和SIGUSR1信号分别安装了信号处理函数,SIGHUP用于通知守护进程重新读取配置文件;SIGUSR1用于通知守护进程执行服务,也就是用echo打印从配置文件读到的信息。最后,进入一个死循环(守护进程主体):通过sigsuspend挂起守护进程等待信号发生,然后重新读取配置文件或执行服务,再挂起守护进程等待信号发生……。该实例通过sigprocmask、sigsuspend和sigaction的配合使用,避免了信号SIGHUP、SIGUSR1的丢失。该实例可以选择是否通过syslog输出信息,若用syslog输出信息,编译时加上-DUSE_SYSLOG。该实例也可以选择是否忽略子进程结束信号SIGCHLD,若忽略,编译时加上-DIGN_SIGCHLD。我们可以通过命令“kill -SIGHUP 守护进程pid”,通知守护进程重新读取配置文件;通过“kill -SIGUSR1 守护进程pid”,通知守护进程执行服务。
程序清单如下:
/*
* test.c
*
* Created on: 2011-04-23
* Author: lingdxuyan
*/
#include /* 标准输入输出定义 */
#include /* 标准函数库定义 */
#include /* Unix 标准函数定义 */
#include
#include
#include
#include /* 文件控制定义 */
#include /* 错误号定义 */
#include /* 信号定义 */
#include /* 定时器定义 */
#include /* 可变参数定义 */
#include /* syslog定义 */
#include
#include
#define true 1
#define false 0
typedef unsigned char BYTE;
typedef BYTE bool;
typedef BYTE byte;
#define MAX_BUF_SIZE 1024
#define CONFIG_FILE "/etc/daemon.conf"
#define LOG_FILE "/tmp/daemon.log"
#define LOCK_FILE "/var/run/daemon.pid"
#define LOCK_FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
static volatile sig_atomic_t g_nUpdateParameter = 1;
static volatile sig_atomic_t g_nServer = 0;
//static volatile int g_nUpdateParameter = 1;
//static volatile int g_nServer = 0;
/*
* 功能: 写日志
*/
void vWriteLog(int nPriority, const char *fmt, va_list va)
{
#ifdef USE_SYSLOG
vsyslog(LOG_DAEMON | nPriority, fmt, va);
#else
FILE *stream;
if (nPriority & LOG_ERR)
stream = stderr;
else
stream = stdout;
vfprintf(stream, fmt, va);
fflush(stream);
#endif
}
void WriteLog(int nPriority, const char *fmt, ...)
{
va_list va;
va_start(va, fmt);
vWriteLog(nPriority, fmt, va);
va_end(va);
}
/*
* 功能: 写错误日志,用法类似perror
*/
void ErrorLog(const char *str)
{
WriteLog(LOG_ERR, "%s: %s\n", str, strerror(errno));
}
/*
* 功能: 写错误日志,用法类似于printf
*/
void ErrorLogFmt(const char *fmt, ...)
{
va_list va;
va_start(va, fmt);
vWriteLog(LOG_ERR, fmt, va);
va_end(va);
}
/*
* 功能: 写日志,用法类似printf
*/
void InfoLog(const char *fmt, ...)
{
va_list va;
va_start(va, fmt);
vWriteLog(LOG_INFO, fmt, va);
va_end(va);
}
/*
* 功能: 重定向标准输入输出
*/
void RedirectStdIO(char *szInFile, char *szOutFile, char *szErrFile)
{
int fd;
if (NULL != szInFile)
{
fd = open(szInFile, O_RDONLY | O_CREAT, 0666);
if (fd > 0)
{
// 标准输入重定向
if (dup2(fd, STDIN_FILENO) <0)
{
ErrorLog("RedirectStdIO dup2 in");
exit(EXIT_FAILURE);
}
close(fd);
}
else
ErrorLogFmt("RedirectStdIO open %s: %s\n", szInFile, strerror(errno));
}
if (NULL != szOutFile)
{
fd = open(szOutFile, O_WRONLY | O_CREAT | O_APPEND /*| O_TRUNC*/, 0666);
if (fd > 0)
{
// 标准输出重定向
if (dup2(fd, STDOUT_FILENO) <0)
{
ErrorLog("RedirectStdIO dup2 out");
exit(EXIT_FAILURE);
}
close(fd);
}
else
ErrorLogFmt("RedirectStdIO open %s: %s\n", szOutFile, strerror(errno));
}
if (NULL != szErrFile)
{
fd = open(szErrFile, O_WRONLY | O_CREAT | O_APPEND /*| O_TRUNC*/, 0666);
if (fd > 0)
{
// 标准错误重定向
if (dup2(fd, STDERR_FILENO) <0)
{
ErrorLog("RedirectIO dup2 error");
exit(EXIT_FAILURE);
}
close(fd);
}
else
ErrorLogFmt("RedirectStdIO open %s: %s\n", szErrFile, strerror(errno));
}
}
/*
* 功能: 读取配置文件SIGHUP信号处理函数
*/
void UpdateHandler(int nSigNum)
{
g_nUpdateParameter = 1;
}
/*
* 功能: 折行服务SIG_USR1信号处理函数
*/
void ServerHandler(int nSigNum)
{
g_nServer = 1;
}
/*
* 功能:确保任一时刻系统中只有一个该守护进程实例在运行
*/
bool AlreadyRunning()
{
int fdLockFile;
char szPid[32];
struct flock fl;
/* 打开锁文件 */
fdLockFile = open(LOCK_FILE, O_RDWR | O_CREAT, LOCK_FILE_MODE);
if (fdLockFile <0)
{
ErrorLog("AlreadyRunning open");
exit(EXIT_FAILURE);
}
/* 对整个锁文件加写锁 */
fl.l_type = F_WRLCK; //记录锁类型:独占性写锁
fl.l_whence = SEEK_SET; //加锁区域起点:距文件开始处l_start个字节
fl.l_start = 0;
fl.l_len = 0; //加锁区域终点:0表示加锁区域自起点开始直至文件最大可能偏移量为止,不管写入多少字节在文件末尾,都属于加锁范围
if (fcntl(fdLockFile, F_SETLK, &fl) <0)
{
if (EACCES == errno || EAGAIN == errno) //系统中已有该守护进程的实例在运行
{
close(fdLockFile);
return true;
}
ErrorLog("AlreadyRunning fcntl");
exit(EXIT_FAILURE);
}
/* 清空锁文件,然后将当前守护进程pid写入锁文件 */
ftruncate(fdLockFile, 0);
sprintf(szPid, "%ld", (long)getpid());
write(fdLockFile, szPid, strlen(szPid) + 1);
return false;
}
/*
* 功能:设置信号nSigNum的处理函数为handler,在调用该信号处理函数前.若handler不为SIG_DEF或SIG_IGN,则系统会将该信号添加到信号屏蔽字中;信号处理函数返回后,信号屏蔽字恢复到原先值.这样,可保证在处理指定信号时,如果该信号再次发生,那么它会被阻塞到上一信号处理结束为止.不过,要是此时信号发生了多次,在对该信号解除阻塞后,也只会调用一次信号处理函数
*/
typedef void (*sighandler)(int);
sighandler MySignal(int nSigNum, sighandler handler)
//void ( *Signal(int nSigNum, void (*handler)(int)) )(int)
{
struct sigaction saNew, saOld;
saNew.sa_handler = handler;
sigemptyset(&saNew.sa_mask);
if (SIG_DFL != handler && SIG_IGN != handler)
sigaddset(&saNew.sa_mask, nSigNum);
saNew.sa_flags = 0;
if (SIGALRM == nSigNum)
{
//不重启该信号中断的系统调用
#ifdef SA_INTERRUPT
saNew.sa_flags |= SA_INTERRUPT;
#endif
}
else
{
//重启该信号中断的系统调用
#ifdef SA_RESTART
saNew.sa_flags |= SA_RESTART;
#endif
}
if (sigaction(nSigNum, &saNew, &saOld) <0)
return SIG_ERR;
return saOld.sa_handler;
}
/*
* 功能: 将普通进程改造成守护进程
*/
void InitDaemon()
{
pid_t pid;
int fd, fdTableSize;
/* 1、屏蔽控制终端操作信号
*/
MySignal(SIGTTOU, SIG_IGN);
MySignal(SIGTTIN, SIG_IGN);
MySignal(SIGTSTP, SIG_IGN);
MySignal(SIGHUP, SIG_IGN);
/* 2、忽略子进程结束信号
*/
#ifdef IGN_SIGCHLD
signal(SIGCHLD, SIG_IGN); //忽略子进程结束信号,避免僵死进程产生
#endif
/* 3、使守护进程后台运行
* 父进程直接退出,子进程继续运行(让守护进程在子进程中后台运行)
*/
pid = fork();
if (pid > 0) //父进程终止运行;子进程过继给init进程,其退出状态也由init进程处理,避免了产生僵死进程
exit(EXIT_SUCCESS);
else if (pid <0)
{
ErrorLog("InitDaemon fork(parent)");
exit(EXIT_FAILURE);
}
/* 4、脱离控制终端,登录会话和进程组
* 调用setsid()使子进程成为会话组长
*/
setsid();
/* 5、禁止进程重新打开控制终端
* 通过使守护进程不再成为会话组长来禁止进程重新打开控制终端
*/
pid = fork();
if (pid > 0) //子进程终止运行;孙进程过继给init进程,其退出状态也由init进程处理,避免了产生僵死进程
exit(EXIT_SUCCESS);
else if (pid <0)
{
ErrorLog("InitDaemon fork(child)");
exit(EXIT_FAILURE);
}
/* 6、重设文件创建掩模
*/
umask(0);
/* 7、关闭打开的文件描述符
*/
RedirectStdIO("/dev/null", LOG_FILE, LOG_FILE); //重定向标准输入输出
fdTableSize = getdtablesize();
for (fd=3; fd
close(fd);
/* 8、改变当前工作目录
*/
chdir("/tmp");
}
/*
* 功能: 读取守护进程的配置文件,并将获取到的信息保存在szParameter中
*/
void ReadConfigFile(char *szParameter)
{
FILE *stream;
int nRet;
InfoLog("------ ReadConfigFile ------\n");
stream = fopen(CONFIG_FILE, "r");
if (NULL != stream)
{
nRet = fread(szParameter, sizeof(char), MAX_BUF_SIZE, stream);
if (nRet >= 0)
szParameter[nRet - 1] = &#39;\0&#39;;
fclose(stream);
InfoLog("ReadConfigFile sucesss!\n");
}
else
ErrorLogFmt("ReadConfigFile fopen %s: %s\n", CONFIG_FILE, strerror(errno));
}
/*
* 功能: 执行守护进程的服务,也就是将szParameter用echo打印出来
*/
void Server(char *szParameter)
{
int nStatus;
pid_t pid;
InfoLog("------ Server ------\n");
pid = vfork(); //生成子进程
#ifdef IGN_SIGCHLD
InfoLog("ignore child SIGCHLD signal!\n");
if (0 == pid) //子进程
{
if (execlp("echo", "echo", szParameter, NULL) <0)
{
ErrorLog("Server execlp");
exit(EXIT_FAILURE);
}
}
else if (pid <0)
{
ErrorLog("Server vfork(parent)");
}
#else
if (pid > 0) //父进程
{
waitpid(pid, &nStatus, 0); //等待子进程结束,否则子进程会成为僵死进程,一直存在,即便子进程已结束执行
}
else if (0 == pid) //子进程
{
pid = vfork(); //生成孙进程
if (pid > 0)
{
exit(EXIT_SUCCESS); //子进程退出,孙进程过继给init进程,其退出状态也由init进程处理,与原有父进程无关
}
else if (0 == pid) //孙进程
{
if (execlp("echo", "echo", szParameter, NULL) <0)
{
ErrorLog("Server execlp");
exit(EXIT_FAILURE);
}
}
else
{
ErrorLog("Server vfork(child)");
}
}
else
{
ErrorLog("Server vfork(parent)");
}
#endif
}
int main()
{
time_t t;
sigset_t sigNewMask, sigOldMask;
char szParameter[MAX_BUF_SIZE];
//将普通进程改造成守护进程
InitDaemon();
if (AlreadyRunning()) //若系统中已有该守护进程的实例在运行,则退出
{
ErrorLogFmt("Daemon already running!\n");
exit(EXIT_FAILURE);
}
//阻塞SIGHUP信号和SIGUSR1信号
sigemptyset(&sigNewMask);
sigaddset(&sigNewMask, SIGHUP);
sigaddset(&sigNewMask, SIGUSR1);
if (sigprocmask(SIG_BLOCK, &sigNewMask, &sigOldMask) <0)
{
ErrorLog("main sigprocmask");
exit(EXIT_FAILURE);
}
//为SIGHUP信号和SIGUSR1信号添加信号处理函数
MySignal(SIGHUP, UpdateHandler);
MySignal(SIGUSR1, ServerHandler);
t = time(NULL);
InfoLog("Daemon %d start at %s", getpid(), ctime(&t));
//读取守护进程配置文件
ReadConfigFile(szParameter);
while(1)
{
sigsuspend(&sigOldMask);//将进程的信号屏蔽字暂时替代为sigOldMask并挂起进程,直到捕捉到一个信号并从其信号处理函数返回,sigsuspend才返回并将信号屏蔽字恢复为调用它之前的值;若捕捉到的是终止进程信号,sigsuspend不返回,进程直接终止
if (1 == g_nUpdateParameter) //读取配置文件
{
ReadConfigFile(szParameter);
g_nUpdateParameter = 0;
}
if (1 == g_nServer) //执行服务
{
Server(szParameter);
g_nServer = 0;
}
}
return 0;
}
1、不使用syslog记录信息,也不忽略子进程结束信息SIGCHLD
root@ubuntu:/home/lingd/arm/test# gcc -Wall test.c -o test
root@ubuntu:/home/lingd/arm/test# ./test
root@ubuntu:/home/lingd/arm/test# cat /tmp/daemon.log
Daemon 3472 start at Wed May 11 11:45:56 2011
------ ReadConfigFile ------
ReadConfigFile fopen /etc/daemon.conf: No such file or directory
root@ubuntu:/home/lingd/arm/test# echo 123 > /etc/daemon.conf
root@ubuntu:/home/lingd/arm/test# cat /etc/daemon.conf
123
root@ubuntu:/home/lingd/arm/test# pgrep test
3472
root@ubuntu:/home/lingd/arm/test# kill -SIGHUP 3472#让守护进程重新读取配置文件
root@ubuntu:/home/lingd/arm/test# cat /tmp/daemon.log
Daemon 3472 start at Wed May 11 11:45:56 2011
------ ReadConfigFile ------
ReadConfigFile fopen /etc/daemon.conf: No such file or directory
------ ReadConfigFile ------
ReadConfigFile sucesss!
root@ubuntu:/home/lingd/arm/test# kill -SIGUSR1 3472#让守护进程运行服务,也就是用echo打印先前从配置文件中读取到的信息
root@ubuntu:/home/lingd/arm/test# cat /tmp/daemon.log
Daemon 3472 start at Wed May 11 11:45:56 2011
------ ReadConfigFile ------
ReadConfigFile fopen /etc/daemon.conf: No such file or directory
------ ReadConfigFile ------
ReadConfigFile sucesss!
------ Server ------
123
root@ubuntu:/home/lingd/arm/test# ./test#再次运行守护进程
root@ubuntu:/home/lingd/arm/test# cat /tmp/daemon.log
Daemon 3472 start at Wed May 11 11:45:56 2011
------ ReadConfigFile ------
ReadConfigFile fopen /etc/daemon.conf: No such file or directory
------ ReadConfigFile ------
ReadConfigFile sucesss!
------ Server ------
123
Daemon already running!#提示已有守护进程实例在运行
2、使用syslog记录信息,同时忽略子进程结束信息SIGCHLD
root@ubuntu:/home/lingd/arm/test# gcc -Wall -DUSE_SYSLOG -DIGN_SIGCHLD test.c -o test
test.c: In function ‘Server’:
test.c:355: warning: unused variable ‘nStatus’
root@ubuntu:/home/lingd/arm/test# ./test
root@ubuntu:/home/lingd/arm/test# echo 345 > /etc/daemon.conf
root@ubuntu:/home/lingd/arm/test# cat /etc/daemon.conf
345
root@ubuntu:/home/lingd/arm/test# pgrep test
2548
root@ubuntu:/home/lingd/arm/test# kill -SIGHUP 2548
root@ubuntu:/home/lingd/arm/test# kill -SIGUSR1 2548
root@ubuntu:/home/lingd/arm/test# ./test
root@ubuntu:/home/lingd/arm/test# tail /var/log/daemon.log
May 11 16:21:42 ubuntu test: Daemon 2548 start at Wed May 11 16:21:42 2011
May 11 16:21:42 ubuntu test: ------ ReadConfigFile ------
May 11 16:21:42 ubuntu test: ReadConfigFile sucesss!
May 11 16:22:27 ubuntu test: ------ ReadConfigFile ------
May 11 16:22:27 ubuntu test: ReadConfigFile sucesss!
May 11 16:22:39 ubuntu test: ------ Server ------
May 11 16:22:39 ubuntu test: ignore child SIGCHLD signal!
May 11 16:22:39 ubuntu test: ignore child SIGCHLD signal!
May 11 16:22:49 ubuntu test: Daemon already running!
root@ubuntu:/home/lingd/arm/test# cat /tmp/daemon.log
345
注意,因为子进程echo继承了父进程(守护进程)的文件描述符,所以echo打印的信息会被输出到/tmp/daemon.log ,而不是终端(屏幕)上。我们调用syslog是设置了LOG_DAEMON,所以日志信息保存在/var/log/daemon.log(一开始以为是保存在/var/log/messages,调了半天程序都没有输出);若设为LOG_USER,日志则保存在/var/log/user.log
如果想要此程序在系统启动时自动运行,可以在/etc/rc.d/rc.local里面用su命令加上一行,比如:
su - lingdxuyan -c "/bin/test"
这个命令将以lingdxuyan用户身份运行/bin/test程序
查看进程:ps –ef
从输出可以发现test守护进程的各种特征满足上面的要求。
lingd@ubuntu:~/arm/test$ ps -ef
UID PID PPID C STIME TTY TIME CMD
lingd 5209 1 0 15:34 ? 00:00:00 ./test