1. 程式人生 > >linux 守護程序編寫

linux 守護程序編寫

                             linux程式設計-守護程序編寫

    守護程序(Daemon)是執行在後臺的一種特殊程序。它獨立於控制終端並且週期性地執行某種任務或等待
    處理某些發生的事件。守護程序是一種很有用的程序。 
    Linux的大多數伺服器就是用守護程序實現的。比如,Internet伺服器inetd,Web伺服器httpd等。
    同時,守護程序完成許多系統任務。比如,作業規劃程序crond,列印程序lpd等。

  守護程序的程式設計本身並不複雜,複雜的是各種版本的Unix的實現機制不盡相同,
    造成不同 Unix環境下守護程序的程式設計規則並不一致。
    需要注意,照搬某些書上的規則(特別是BSD4.3和低版本的System V)到Linux會出現錯誤的。
    下面結合一些前輩的文件和自己的例子說說守護程序的程式設計。


.基本概念
.程序
    .每個程序都有一個父程序
    .當子程序終止時,父程序會得到通知並能取得子程序的退出狀態。
.程序組
    .每個程序也屬於一個程序組
    .每個程序主都有一個程序組號,該號等於該程序組組長的PID號
    .一個程序只能為它自己或子程序設定程序組ID號
.會話期
    .對話期(session)是一個或多個程序組的集合。
    .setsid()函式可以建立一個對話期:
    如果,呼叫setsid的程序不是一個程序組的組長,此函式建立一個新的會話期。
    (1)此程序變成該對話期的首程序
    (2)此程序變成一個新程序組的組長程序。
    (3)此程序沒有控制終端,如果在呼叫setsid前,該程序有控制終端,那麼與該終端的聯絡被解除。
    如果該程序是一個程序組的組長,此函式返回錯誤。
    (4)為了保證這一點,我們先呼叫fork()然後exit(),此時只有子程序在執行,
    子程序繼承了父程序的程序組ID,但是程序PID卻是新分配的,所以不可能是新會話的程序組的PID。
    從而保證了這一點。

    if((pid=fork())>0)  //parent
        exit(0);
    else if(pid==0){        //th1 child
        setsid();           //th1是成為會話期組長
        if(fork() ==0){     //th2不會是會話期組長(變成孤兒程序組)
            ...
        }
    }


一. 守護程序及其特性
 
    (1)守護程序最重要的特性是後臺執行。在這一點上DOS下的常駐記憶體程式TSR與之相似。
    (2)其次,守護程序必須與其執行前的環境隔離開來。這些環境包括未關閉的檔案描述符,控制終端,
    會話和程序組,工作目錄以及檔案建立掩模等。這些環境通常是守護程序從執行它的父程序(特別是shell)
    中繼承下來的。
    (3)最後,守護程序的啟動方式有其特殊之處。它可以在Linux系統啟動時從啟動指令碼/etc/rc.d中啟動,
    可以由作業規劃程序crond啟動,還可以由使用者終端(通常是 shell)執行。
  總之,除開這些特殊性以外,守護程序與普通程序基本上沒有什麼區別。
    因此,編寫守護程序實際上是把一個普通程序按照上述的守護程序的特性改造成為守護程序。

二. 守護程序的程式設計要點  (來自UEAP)

  前面講過,不同Unix環境下守護程序的程式設計規則並不一致。所幸的是守護程序的程式設計原則其實都一樣,
      區別在於具體的實現細節不同。這個原則就是要滿足守護程序的特性。
      同時,Linux是基於Syetem V的SVR4並遵循Posix標準,實現起來與BSD4相比更方便。程式設計要點如下;

1. 在後臺執行。
  為避免掛起控制終端將Daemon放入後臺執行。方法是在程序中呼叫fork使父程序終止,
    讓Daemon在子程序中後臺執行。

if(pid=fork())
    exit(0); //是父程序,結束父程序,子程序繼續

2. 脫離控制終端,登入會話和程序組
  程序屬於一個程序組,程序組號(GID)就是程序組長的程序號(PID)。登入會話可以包含多個程序組。
    這些程序組共享一個控制終端。這個控制終端通常是建立程序的登入終端。
    控制終端,登入會話和程序組通常是從父程序繼承下來的。
    我們的目的就是要擺脫它們,使之不受它們的影響。
    方法是在第1點的基礎上,呼叫setsid()使程序成為會話組長:

setsid();

  說明:當程序是會話組長時setsid()呼叫失敗。但第一點已經保證程序不是會話組長。
    setsid()呼叫成功後,程序成為新的會話組長和新的程序組長,並與原來的登入會話和程序組脫離。
    由於會話過程對控制終端的獨佔性,程序同時與控制終端脫離。

3. 禁止程序重新開啟控制終端
  現在,程序已經成為無終端的會話組長。但它可以重新申請開啟一個控制終端。
    可以通過使程序不再成為會話組長來禁止程序重新開啟控制終端:

if(pid=fork())
     exit(0); //結束第一子程序,第二子程序繼續(第二子程序不再是會話組長)

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

for(i=0;i 關閉開啟的檔案描述符close(i);>

5. 改變當前工作目錄
  程序活動時,其工作目錄所在的檔案系統不能卸下。一般需要將工作目錄改變到根目錄。
    對於需要轉儲核心,寫執行日誌的程序將工作目錄改變到特定目錄如 /tmpchdir("/")

6. 重設檔案建立掩模
  程序從建立它的父程序那裡繼承了檔案建立掩模。它可能修改守護程序所建立的檔案的存取位。
    為防止這一點,將檔案建立掩模清除:umask(0);

7. 處理SIGCHLD訊號
  處理SIGCHLD訊號並不是必須的。
    但對於某些程序,特別是伺服器程序往往在請求到來時生成子程序處理請求。
    如果父程序不等待子程序結束,子程序將成為殭屍程序(zombie)從而佔用系統資源。
    如果父程序等待子程序結束,將增加父程序的負擔,影響伺服器程序的併發效能。
    在Linux下可以簡單地將 SIGCHLD訊號的操作設為SIG_IGN。

signal(SIGCHLD,SIG_IGN);

  這樣,核心在子程序結束時不會產生殭屍程序。
    這一點與BSD4不同,BSD4下必須顯式等待子程序結束才能釋放殭屍程序。

三. 守護程序例項
  守護程序例項包括兩部分:主程式test.c和初始化程式init.c。
    主程式每隔一分鐘向/tmp目錄中的日誌test.log報告執行狀態。
    初始化程式中的init_daemon函式負責生成守護程序。讀者可以利用init_daemon函式生成自己的守護程序。

1. init.c清單

#include < unistd.h >
#include < signal.h >
#include < sys/param.h >
#include < sys/types.h >
#include < sys/stat.h >

void init_daemon(void)
{
    int pid;
    int i;
    if(pid=fork())
        exit(0);        //是父程序,結束父程序
    else if(pid< 0)
        exit(1);        //fork失敗,退出
    //是第一子程序,後臺繼續執行
    setsid();           //第一子程序成為新的會話組長和程序組長
    //並與控制終端分離
    if(pid=fork())
        exit(0);        //是第一子程序,結束第一子程序
    else if(pid< 0)
        exit(1);        //fork失敗,退出
    //是第二子程序,繼續
    //第二子程序不再是會話組長
    for(i=0;i< NOFILE;++i)  //關閉開啟的檔案描述符
        close(i);

    chdir("/tmp");      //改變工作目錄到/tmp
    umask(0);           //重設檔案建立掩模
    return;
}

2. test.c清單

#include < stdio.h >
#include < time.h >

void init_daemon(void);//守護程序初始化函式

main()
{
    FILE *fp;
    time_t t;
    init_daemon();//初始化為Daemon

    while(1)//每隔一分鐘向test.log報告執行狀態
    {
        sleep(60);//睡眠一分鐘
        if((fp=fopen("test.log","a")) >=0){
            t=time(0);
            fprintf(fp,"Im here at %sn",asctime(localtime(&t)) );
            fclose(fp);
        }
    }
}

以上程式在RedHat Linux6.0下編譯通過。步驟如下:
編譯:gcc -g -o test init.c test.c
執行:./test
檢視程序:ps -ef


說明:在系統呼叫庫中有一個庫函式可以直接使一個程序變成守護程序,
       #include <unistd.h>
       int daemon(int nochdir, int noclose);