應用編程之守護進程

參考博客

/*
 * 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;
}
發佈了41 篇原創文章 · 獲贊 10 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章