守護進程

什麼是守護進程?

守護進程(Daemon Process),也就是通常說的 Daemon 進程(精靈進程),是 Linux 中的後臺服務進程。它是一個生存期較長的進程,通常獨立於控制終端並且週期性地執行某種任務或等待處理某些發生的事件。


守護進程是個特殊的孤兒進程,這種進程脫離終端,爲什麼要脫離終端呢?之所以脫離於終端是爲了避免進程被任何終端所產生的信息所打斷,其在執行過程中的信息也不在任何終端上顯示。由於 linux 中,每一個系統與用戶進行交流的界面稱爲終端,每一個從此終端開始運行的進程都會依附於這個終端,這個終端就稱爲這些進程的控制終端,當控制終端被關閉時,相應的進程都會自動關閉


Linux 的大多數服務器就是用守護進程實現的。比如,Internet 服務器 inetd,Web 服務器 httpd 等。

如何編寫守護進程?

下面是編寫守護進程的基本過程:
1)屏蔽一些控制終端操作的信號

這是爲了防止守護進行在沒有運行起來前,控制終端受到干擾退出或掛起。關於信號的更詳細用法,請看《信號中斷處理》
  

2)在後臺運行

這是爲避免掛起控制終端將守護進程放入後臺執行。方法是在進程中調用 fork() 使父進程終止, 讓守護進行在子進程中後臺執行。 


3)脫離控制終端、登錄會話和進程組

有必要先介紹一下 Linux 中的進程與控制終端,登錄會話和進程組之間的關係:進程屬於一個進程組,進程組號(GID)就是進程組長的進程號(PID)。登錄會話可以包含多個進程組。這些進程組共享一個控制終端。這個控制終端通常是創建進程的 shell 登錄終端。 控制終端、登錄會話和進程組通常是從父進程繼承下來的。我們的目的就是要擺脫它們 ,使之不受它們的影響。因此需要調用 setsid() 使子進程成爲新的會話組長,示例代碼如下:
 

setsid() 調用成功後,進程成爲新的會話組長和新的進程組長,並與原來的登錄會話和進程組脫離。由於會話過程對控制終端的獨佔性,進程同時與控制終端脫離。 

4)禁止進程重新打開控制終端

現在,進程已經成爲無終端的會話組長,但它可以重新申請打開一個控制終端。可以通過使進程不再成爲會話組長來禁止進程重新打開控制終端,採用的方法是再次創建一個子進程,示例代碼如下:


5)關閉打開的文件描述符

進程從創建它的父進程那裏繼承了打開的文件描述符。如不關閉,將會浪費系統資源,造成進程所在的文件系統無法卸下以及引起無法預料的錯誤。按如下方法關閉它們:



6)改變當前工作目錄

進程活動時,其工作目錄所在的文件系統不能卸下。一般需要將工作目錄改變到根目錄。對於需要轉儲核心,寫運行日誌的進程將工作目錄改變到特定目錄如 /tmp。示例代碼如下:


7)重設文件創建掩模

進程從創建它的父進程那裏繼承了文件創建掩模。它可能修改守護進程所創建的文件的存取權限。爲防止這一點,將文件創建掩模清除:


8)處理 SIGCHLD 信號

但對於某些進程,特別是服務器進程往往在請求到來時生成子進程處理請求。如果父進程不等待子進程結束,子進程將成爲殭屍進程(zombie)從而佔用系統資源(關於殭屍進程的更多詳情,請看《殭屍進程》)。如果父進程等待子進程結束,將增加父進程的負擔,影響服務器進程的併發性能。在 Linux 下可以簡單地將 SIGCHLD 信號的操作設爲 SIG_IGN 。關於信號的更詳細用法,請看《信號中斷處理》

這樣,內核在子進程結束時不會產生殭屍進程。
#include <unistd.h>   
#include <signal.h>   
#include <fcntl.h>   #include <sys/syslog.h>   #include <sys/param.h>    #include <sys/types.h>    #include <sys/stat.h>    #include <stdio.h>   #include <stdlib.h>   #include <time.h>      int init_daemon(void)   {        int pid;        int i;              // 1)屏蔽一些控制終端操作的信號       signal(SIGTTOU,SIG_IGN);        signal(SIGTTIN,SIG_IGN);        signal(SIGTSTP,SIG_IGN);        signal(SIGHUP ,SIG_IGN);           // 2)在後臺運行       if( pid=fork() ){ // 父進程           exit(0); //結束父進程,子進程繼續       }else if(pid< 0){ // 出錯           perror("fork");           exit(EXIT_FAILURE);       }              // 3)脫離控制終端、登錄會話和進程組       setsid();                // 4)禁止進程重新打開控制終端       if( pid=fork() ){ // 父進程           exit(0);      // 結束第一子進程,第二子進程繼續(第二子進程不再是會話組長)        }else if(pid< 0){ // 出錯           perror("fork");           exit(EXIT_FAILURE);       }                // 5)關閉打開的文件描述符       // NOFILE 爲 <sys/param.h> 的宏定義       // NOFILE 爲文件描述符最大個數,不同系統有不同限制       for(i=0; i< NOFILE; ++i){           close(i);       }              // 6)改變當前工作目錄       chdir("/tmp");               // 7)重設文件創建掩模       umask(0);                // 8)處理 SIGCHLD 信號       signal(SIGCHLD,SIG_IGN);              return 0;    }       int main(int argc, char *argv[])    {       init_daemon();              while(1);          return 0;   }  


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章