Linux下一個簡單守護進程的實現 (Daemon)

轉自:Cise.ufl.edu/~sahni/
在Linux/UNIX系統引導的時候會開啓很多服務,這些服務稱爲守護進程(也叫Daemon進程)。守護進程是脫離於控制終端並且在後臺週期性地執行某種任務或等待處理某些事件的進程,脫離終端是爲了避免進程在執行過程中的信息在任何終端上顯示並且進程在Linux/UNIX系統引導的時候會開啓很多服務,這些服務稱爲守護進程(也叫Daemon進程)。守護進程是脫離於控制終端並且在後臺週期性地執行某種任務或等待處理某些事件的進程,脫離終端是爲了避免進程在執行過程中的信息在任何終端上顯示並且進程也不會被任何終端所產生的中斷信息所終止。

創建守護進程的一般步驟:

(1) 創建子進程,退出父進程
爲了脫離控制終端需要退出父進程,之後的工作都由子進程完成。在Linux中父進程先於子進程退出會造成子進程成爲孤兒進程,而每當系統發現一個孤兒進程時,就會自動由1號進程(init)收養它,這樣,原先的子進程就會變成init進程的子進程。
ps –ef | grep ProcName 通過PID/PPID查看進程的父子關係

(2) 在子進程中創建新的會話
使用系統函數setsid來完成。
man 2 setsid 查看關於setsid函數的說明
setsid – creates a session and sets theprocess group ID

 #include <unistd.h>
pid_t setsid(void);
setsid() creates a new session if thecalling process is not a process group leader. The calling process is theleader of the new session, the process group leader of the new process group,and has no controlling tty. The process group ID and session ID of the callingprocess are set to the PID of the calling process. The calling process will bethe only process in this new process group and in this new session.

進程組:是一個或多個進程的集合。進程組有進程組ID來唯一標識。除了進程號PID之外,進程組ID也是一個進程的必備屬性。每個進程組都有一個組長進程,其組長進程的進程號等於進程組ID,且該進程組ID不會因組長進程的退出而受到影響。
setsid函數作用:用於創建一個新的會話,並擔任該會話組的組長。調用setsid有3個作用
(a) 讓進程擺脫原會話的控制;
(b) 讓進程擺脫原進程組的控制;
(c) 讓進程擺脫原控制終端的控制;
使用setsid函數的目的:由於創建守護進程的第一步調用了fork函數來創建子進程再將父進程退出。由於在調用fork函數時,子進程拷貝了父進程的會話期、進程組、控制終端等,雖然父進程退出了,但會話期、進程組、控制終端等並沒有改變,因此,這還不是真正意義上的獨立開了。使用setsid函數後,能夠使進程完全獨立出來,從而擺脫其他進程的控制。

(3) 改變當前目錄爲根目錄
使用fork創建的子進程繼承了父進程的當前的工作目錄。由於在進程運行中,當前目錄所在的文件系統是不能卸載的,這對以後的使用會造成諸多的麻煩。因此,通常的做法是讓根目錄”/”作爲守護進程的當前工作目錄。這樣就可以避免上述的問題。如有特殊的需求,也可以把當前工作目錄換成其他的路徑。改變工作目錄的方法是使用chdir函數。

(4) 重設文件權限掩碼
文件權限掩碼:是指屏蔽掉文件權限中的對應位。例如,有個文件權限掩碼是050,它就屏蔽了文件組擁有者的可讀與可執行權限(對應二進制爲,rwx, 101)。由於fork函數創建的子進程繼承了父進程的文件權限掩碼,這就給子進程使用文件帶來了諸多的麻煩。因此,把文件權限掩碼設置爲0(即,不屏蔽任何權限),可以增強該守護進程的靈活性。設置文件權限掩碼的函數是umask。通常的使用方法爲umask(0)。

(5) 關閉文件描述符
用fork創建的子進程也會從父進程那裏繼承一些已經打開了的文件。這些被打開的文件可能永遠不會被守護進程讀寫,但它們一樣消耗系統資源,而且可能導致所在的文件系統無法卸載。在使用setsid調用之後,守護進程已經與所屬的控制終端失去了聯繫,因此從終端輸入的字符不可能達到守護進程,守護進程中用常規方法(如printf)輸出的字符也不可能在終端上顯示出來。所以,文件描述符爲0、1、2(即,標準輸入、標準輸出、標準錯誤輸出)的三個文件已經失去了存在的價值,也應該關閉。

(6) 守護進程退出處理
當用戶需要外部停止守護進程時,通常使用kill命令停止該守護進程。所以,守護進程中需要編碼來實現kill發出的signal信號處理,達到進程正常退出。

下面是一個簡單的實現:

#include<stdio.h>  
#include<stdlib.h>  
#include<string.h>  
#include<fcntl.h>// open  
#include<sys/types.h>  
#include<sys/stat.h>  
#include<unistd.h>  
#include<sys/wait.h>  
#include<signal.h>  

#define MAXFILE 65535  

volatile sig_atomic_t _running = 1;  
int fd;  

// signal handler  
void sigterm_handler(int arg)  
{  
    _running = 0;  
}  

int main()  
{  
    pid_t pid;  
    char *buf = "This is a Daemon, wcdj\n";  

    /* 屏蔽一些有關控制終端操作的信號 
     * 防止在守護進程沒有正常運轉起來時,因控制終端受到干擾退出或掛起 
     * */  
    signal(SIGINT,  SIG_IGN);// 終端中斷  
    signal(SIGHUP,  SIG_IGN);// 連接掛斷  
    signal(SIGQUIT, SIG_IGN);// 終端退出  
    signal(SIGPIPE, SIG_IGN);// 向無讀進程的管道寫數據  
    signal(SIGTTOU, SIG_IGN);// 後臺程序嘗試寫操作  
    signal(SIGTTIN, SIG_IGN);// 後臺程序嘗試讀操作  
    signal(SIGTERM, SIG_IGN);// 終止  

    // test  
    //sleep(20);// try cmd: ./test &; kill -s SIGTERM PID  


    // [1] fork child process and exit father process  
    pid = fork();  
    if(pid < 0)  
    {  
        perror("fork error!");  
        exit(1);  
    }  
    else if(pid > 0)  
    {  
        exit(0);  
    }  

    // [2] create a new session  
    setsid();  

    // [3] set current path  
    char szPath[1024];  
    if(getcwd(szPath, sizeof(szPath)) == NULL)  
    {  
        perror("getcwd");  
        exit(1);  
    }  
    else  
    {  
        chdir(szPath);  
        printf("set current path succ [%s]\n", szPath);  
    }  

    // [4] umask 0  
    umask(0);  

    // [5] close useless fd  
    int i;  
    //for (i = 0; i < MAXFILE; ++i)  
    for (i = 3; i < MAXFILE; ++i)  
    {  
        close(i);  
    }  

    // [6] set termianl signal  
    signal(SIGTERM, sigterm_handler);  

    // open file and set rw limit  
    if((fd = open("outfile", O_CREAT|O_WRONLY|O_APPEND, 0600)) < 0)  
    {  
        perror("open");  
        exit(1);  
    }  

    printf("\nDaemon begin to work..., and use kill -9 PID to terminate\n");  

    // do sth in loop  
    while(_running)  
    {  
        if (write(fd, buf, strlen(buf)) != strlen(buf))  
        {  
            perror("write");  
            close(fd);  
            exit(1);  
        }  

        usleep(1000*1000);// 1 s  
    }  
    close(fd);  


    // print data  
    if((fd = open("outfile", O_RDONLY)) < 0)  
    {  
        perror("open");  
        exit(1);  
    }  
    char szBuf[1024] = {0};  
    if(read(fd, szBuf, sizeof(szBuf)) == -1)  
    {  
        perror("read");  
        exit(1);  
    }  
    printf("read 1024 bytes:\n%s\n", szBuf);  

    close(fd);  

    return 0;  
}  

/* 
   gcc -Wall -g -o test test.c 
   ps ux | grep -v grep | grep test 
   tail -f outfile 
   kill -s SIGTERM PID 
 */  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章