物聯網教程Linux系統程式設計——特殊程式之守護程式

千鋒教育官方發表於2019-08-28


什麼是守護程式?

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

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

 

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

 

如何檢視守護程式

在終端敲:ps axj

 

a 表示不僅列當前使用者的程式,也列出所有其他使用者的程式

x 表示不僅列有控制終端的程式,也列出所有無控制終端的程式

j 表示列出與作業控制相關的資訊


 

 

從上圖可以看出守護進行的一些特點:

 

守護程式基本上都是以超級使用者啟動( UID 0

沒有控制終端( TTY 為 ?)

終端程式組 ID -1 TPGID 表示終端程式組 ID

 

 

一般情況下,守護程式可以透過以下方式啟動:

 

在系統啟動時由啟動指令碼啟動,這些啟動指令碼通常放在 /etc/rc.d 目錄下;

利用 inetd 超級伺服器啟動,如 telnet 等;

cron 定時啟動以及在終端用 nohup 啟動的程式也是守護程式。

 

如何編寫守護程式?

下面是編寫守護程式的基本過程:

1 )遮蔽一些控制終端操作的訊號

 

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

 

signal(SIGTTOU,SIG_IGN);   

signal(SIGTTIN,SIG_IGN);   

signal(SIGTSTP,SIG_IGN);   

signal(SIGHUP ,SIG_IGN);  

 

2 )在後臺執行

 

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

 

if( pid = fork() ){ // 父程式  

    exit(0);        //結束父程式,子程式繼續  

}  

 

3 )脫離控制終端、登入會話和程式組

 

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

setsid();  

 

setsid() 呼叫成功後,程式成為新的會話組長和新的程式組長,並與原來的登入會話和程式組脫離。由於會話過程對控制終端的獨佔性,程式同時與控制終端脫離。  

 

4 )禁止程式重新開啟控制終端

 

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

 

if( pid=fork() ){ // 父程式  

    exit(0);      // 結束第一子程式,第二子程式繼續(第二子程式不再是會話組長)   

}  

 

5 )關閉開啟的檔案描述符

 

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

 

// NOFILE 為 <sys/param.h> 的宏定義  

// NOFILE 為檔案描述符最大個數,不同系統有不同限制  

for(i=0; i< NOFILE; ++i){// 關閉開啟的檔案描述符  

    close(i);  

}  

 

 

6 )改變當前工作目錄

 

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

 

chdir("/");  

 

7 )重設檔案建立掩模

 

程式從建立它的父程式那裡繼承了檔案建立掩模。它可能修改守護程式所建立的檔案的存取許可權。為防止這一點,將檔案建立掩模清除:

 

umask(0);  

 

8 )處理 SIGCHLD 訊號

 

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

 

signal(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;  

}  

 

執行結果如下:



來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69914734/viewspace-2655283/,如需轉載,請註明出處,否則將追究法律責任。

相關文章