天天看點

linux上編寫守護程序的例程

linux上編寫守護程序的例程

  摘自《開放系統世界》2004年第5期郭吉平、任蓮的文章“親自動手編寫守護程序”。

#include

#include

#include

void main(int argc, char ** argv){

time_t now;

int childpid, fd, fdtablesize;

int error, in, out;

signal(SIGTTOU,SIG_IGN);

signal(SIGTTIN,SIG_IGN);

signal(SIGTSTP,SIG_IGN);

signal(SIGHUP,SIG_IGN);

if( fork()!=0 ) exit(1);

if( setsid()<0 ) exit(1);

if( fork()!=0 ) exit(1);

if( chdir("/tmp")==-1 )exit(1);

for( fd=0, fdtablesize=getdtablesize(); fd< fdtablesize;fd++) close(fd);

umask(0);

signal(SIGCHLD,SIG_IGN);

syslog(LOG_USER|LOG_INFO,"守護程序測試!/n");

while(1){

time(&now);

syslog(LOG_USER|LOG_INFO,"目前時間:/t%s/t/t/n",ctime(&now));

sleep(6);

}

守護程序在Linux/Unix系統中有着廣泛的應用。有時,開發人員也想把自己的程式變成守護程序。在建立一個守護程序的時候,要接觸到子程序、程序組、會晤期、信号機制、檔案、目錄和控制終端等多個概念。是以守護程序還是比較複雜的,在這裡詳細地讨論Linux/Unix的守護程序的編寫,總結出八條經驗,并給出應用範例。

    程式設計要點

    1.屏蔽一些有關控制終端操作的信号。防止在守護程序沒有正常運轉起來時,控制終端受到幹擾退出或挂起。示例如下:

signal(SIGTTOU,SIG_IGN);

signal(SIGTTIN,SIG_IGN);

signal(SIGTSTP,SIG_IGN);

signal(SIGHUP ,SIG_IGN);

    所有的信号都有自己的名字。這些名字都以“SIG”開頭,隻是後面有所不同。開發人員可以通過這些名字了解到系統中發生了什麼事。當信号出現時,開發人員可以要求系統進行以下三種操作:

    ◆ 忽略信号。大多數信号都是采取這種方式進行處理的,這裡就采用了這種用法。但值得注意的是對SIGKILL和SIGSTOP信号不能做忽略處理。

    ◆ 捕捉信号。最常見的情況就是,如果捕捉到SIGCHID信号,則表示子程序已經終止。然後可在此信号的捕捉函數中調用waitpid()函數取得該子程序的程序ID和它的終止狀态。另外,如果程序建立了臨時檔案,那麼就要為程序終止信号SIGTERM編寫一個信号捕捉函數來清除這些臨時檔案。

    ◆ 執行系統的預設動作。對絕大多數信号而言,系統的預設動作都是終止該程序。

    對這些有關終端的信号,一般采用忽略處理,進而保障了終端免受幹擾。

    這類信号分别是,SIGTTOU(表示背景程序寫控制終端)、SIGTTIN(表示背景程序讀控制終端)、SIGTSTP(表示終端挂起)和SIGHUP(程序組長退出時向所有會議成員發出的)。

    2.将程式進入背景執行。由于守護程序最終脫離控制終端,到背景去運作。方法是在程序中調用fork使父程序終止,讓Daemon在子程序中背景執行。這就是常說的“脫殼”。子程序繼續函數fork()的定義如下:

#include <sys/types.h>

#include <unistd.h>

pid_t fork(void);

    該函數是Linux/Unix程式設計中非常重要的函數。它被調用一次,但傳回兩次。這兩次傳回的差別是子程序的傳回值為“0”,而父程序的傳回值為子程序的ID。如果出錯則傳回“-1”。

    3.脫離控制終端、登入會話和程序組。開發人員如果要擺脫它們,不受它們的影響,一般使用 setsid() 設定新會話的領頭程序,并與原來的登入會話和程序組脫離。這隻是其中的一種方法,也有如下處理的辦法:

if  ((fd = open("/dev/tty",O_RDWR)) >= 0) { ioctl(fd,TIOCNOTTY,NULL); close(fd); }

    其中/dev/tty是一個流裝置,也是終端映射,調用close()函數将終端關閉。

    4.禁止程序重新打開控制終端。程序已經成為無終端的會話組長,但它可以重新申請打開一個控制終端。開發人員可以通過不再讓程序成為會話組長的方式來禁止程序重新打開控制終端,需要再次調用fork函數。

    上面的程式代碼表示結束第一子程序,第二子程序繼續(第二子程序不再是會話組長)。

    5. 關閉打開的檔案描述符,并重定向标準輸入、标準輸出和标準錯誤輸出的檔案描述符。程序從建立它的父程序那裡繼承了打開的檔案描述符。如果不關閉,将會浪費系統資源,引起無法預料的錯誤。關閉三者的代碼如下:

for (fd = 0, fdtablesize = getdtablesize();  fd < fdtablesize; fd++)   close(fd);

    但标準輸入、标準輸出和标準錯誤輸出的重定向是可選的。也許有的程式想保留标準輸入(0)、标準輸出(1)和标準錯誤輸出(2),那麼循環應繞過這三者。代碼如下:

for (fd =3, fdtablesize = getdtablesize();fd < fdtablesize; fd++)   close(fd);

    有的程式有些特殊的需求,還需要将這三者重新定向。示例如下:

error=open("/tmp/error",O_WRONLY|O_CREAT,0600);  dup2(error,2); close(error); in=open("/tmp/in",O_RDONLY|O_CREAT,0600); if(dup2(in,0)==-1)  perror("in"); close(in);

out=open("/tmp/out",O_WRONLY|O_CREAT,0600); if(dup2(out,1)==-1) perror("out"); close(out);

    6.改變工作目錄到根目錄或特定目錄程序活動時,其工作目錄所在的檔案系統不能卸下。

    一般需要将工作目錄改變到根目錄或特定目錄,注意使用者對此目錄需要有讀寫權。防止超級使用者解除安裝裝置時系統報告裝置忙。

    7.處理SIGCHLD信号。SIGCHLD信号是子程序結束時,向核心發送的信号。

如果父程序不等待子程序結束,子程序将成為僵屍程序(zombie)進而占用系統資源。是以需要對SIGCHLD信号做出處理,回收僵屍程序的資源,避免造成不必要的資源浪費。可以用如下語句:

    signal(SIGCHLD,(void *)reap_status);

    捕捉信号SIGCHLD,用下面的函數進行處理:

void reap_status() 

{

int pid;   

union wait status;   

while ((pid = wait3(&status,WNOHANG,NULL)) > 0)  

……

}

    8.在Linux/Unix下有個syslogd的守護程序,向使用者提供了syslog()系統調用。任何程式都可以通過syslog記錄事件。

     由于syslog非常好用和易配置,是以很多程式都使用syslog來發送它們的記錄資訊。一般守護程序也使用syslog向系統輸出資訊。syslog 有三個函數,一般隻需要用syslog(...)函數,openlog()/closelog()可有可無。syslog()在shslog.h定義如下:

#include <syslog.h>

void syslog(int priority,char *format,...);

    其中參數priority指明了程序要寫入資訊的等級和用途。第二個參數是一個格式串,指定了記錄輸出的格式。在這個串的最後需要指定一個%m,對應errno錯誤碼。

    應用範例

    下面給出Linux下程式設計的守護程序的應用範例,在UNIX中,不同版本實作的細節可能不一緻,但其實作的原則是與Linux一緻的。

#include <stdio.h>

#include <signal.h>

#include <sys/file.h>

main(int argc,char **argv)

time_t now; 

int childpid,fd,fdtablesize; 

int error,in,out; 

signal(SIGTTOU,SIG_IGN);

signal(SIGTTIN,SIG_IGN);  signal(SIGTSTP,SIG_IGN);   signal(SIGHUP ,SIG_IGN); 

if(fork()!=0) exit(1); 

if(setsid()<0)exit(1);       if(fork()!=0) exit(1);  if(chdir("/tmp")==-1)exit(1);

for (fd = 0, fdtablesize = getdtablesize(); fd < fdtablesize; fd++)   

close(fd);  

umask(0);    signal(SIGCHLD,SIG_IGN);

  syslog(LOG_USER|LOG_INFO,"守護程序測試!/n");    

while(1)    

{     

time(&now);  

syslog(LOG_USER|LOG_INFO,"目前時間:/t%s/t/t/n",ctime(&now));   

sleep(6);    

}  

}

    此程式在Turbo Linux 4.0下編譯通過。這個程式比較簡單,但基本展現了守護程序的程式設計要點。讀者針對實際應用中不同的需要,還可以做相應的調整。Linux聯盟收集整理

繼續閱讀