/*
* test.c
*
* Created on: 2011-04-23
* Author: lingdxuyan
*/
#include <stdio.h> /* 標準輸入輸出定義 */
#include <stdlib.h> /* 標準函數庫定義 */
#include <unistd.h> /* Unix 標準函數定義 */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h> /* 文件控制定義 */
#include <errno.h> /* 錯誤號定義 */
#include <signal.h> /* 信號定義 */
#include <time.h> /* 定時器定義 */
#include <stdarg.h> /* 可變參數定義 */
#include <syslog.h> /* syslog定義 */
#include <string.h>
#include <fcntl.h>
#define true 1
#define false 0
typedef unsigned char BYTE;
typedef BYTE bool;
typedef BYTE byte;
#define MAX_BUF_SIZE 1024
#define CONFIG_FILE "/run/shm/daemon.conf"
#define LOG_FILE "/run/shm/daemon.log"
#define LOCK_FILE "/run/shm/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<fdTableSize; 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] = '\0';
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)
{
// 將進程的信號屏蔽字暫時替代爲sigOldMask並掛起進程,
// 直到捕捉到一個信號並從其信號處理函數返回,sigsuspend
// 才返回並將信號屏蔽字恢復爲調用它之前的值;
// 若捕捉到的是終止進程信號,sigsuspend不返回,進程直接終止
sigsuspend(&sigOldMask);
if (1 == g_nUpdateParameter) //讀取配置文件
{
ReadConfigFile(szParameter);
g_nUpdateParameter = 0;
}
if (1 == g_nServer) //執行服務
{
Server(szParameter);
g_nServer = 0;
}
}
return 0;
}
應用編程之守護進程
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.