編寫Linux/Unix守護(hù)進(jìn)程 [zz]
守護(hù)進(jìn)程在Linux/Unix系統(tǒng)中有著廣泛的應(yīng)用。有時(shí),開發(fā)人員也想把自己的程序變成守護(hù)進(jìn)程。在創(chuàng)建一個(gè)守護(hù)進(jìn)程的時(shí)候,要接觸到子進(jìn)程、進(jìn)程組、會(huì)晤期、信號(hào)機(jī)制、文件、目錄和控制終端等多個(gè)概念。因此守護(hù)進(jìn)程還是比較復(fù)雜的,在這里詳細(xì)地討論Linux/Unix的守護(hù)進(jìn)程的編寫,總結(jié)出八條經(jīng)驗(yàn),并給出應(yīng)用范例。
?編程要點(diǎn)
????1.屏蔽一些有關(guān)控制終端操作的信號(hào)。防止在守護(hù)進(jìn)程沒有正常運(yùn)轉(zhuǎn)起來(lái)時(shí),控制終端受到干擾退出或掛起。示例如下:
signal(SIGTTOU,SIG_IGN);
signal(SIGTTIN,SIG_IGN);
signal(SIGTSTP,SIG_IGN);
signal(SIGHUP ,SIG_IGN);/PRE> |
BR>
????所有的信號(hào)都有自己的名字。這些名字都以“SIG”開頭,只是后面有所不同。開發(fā)人員可以通過(guò)這些名字了解到系統(tǒng)中發(fā)生了什么事。當(dāng)信號(hào)出現(xiàn)時(shí),開發(fā)人員可以要求系統(tǒng)進(jìn)行以下三種操作:
????◆ 忽略信號(hào)。大多數(shù)信號(hào)都是采取這種方式進(jìn)行處理的,這里就采用了這種用法。但值得注意的是對(duì)SIGKILL和SIGSTOP信號(hào)不能做忽略處理。
????◆ 捕捉信號(hào)。最常見的情況就是,如果捕捉到SIGCHID信號(hào),則表示子進(jìn)程已經(jīng)終止。然后可在此信號(hào)的捕捉函數(shù)中調(diào)用waitpid()函數(shù)取得該子進(jìn)程的進(jìn)程ID和它的終止?fàn)顟B(tài)。另外,如果進(jìn)程創(chuàng)建了臨時(shí)文件,那么就要為進(jìn)程終止信號(hào)SIGTERM編寫一個(gè)信號(hào)捕捉函數(shù)來(lái)清除這些臨時(shí)文件。
????◆ 執(zhí)行系統(tǒng)的默認(rèn)動(dòng)作。對(duì)絕大多數(shù)信號(hào)而言,系統(tǒng)的默認(rèn)動(dòng)作都是終止該進(jìn)程。
????對(duì)這些有關(guān)終端的信號(hào),一般采用忽略處理,從而保障了終端免受干擾。
????這類信號(hào)分別是,SIGTTOU(表示后臺(tái)進(jìn)程寫控制終端)、SIGTTIN(表示后臺(tái)進(jìn)程讀控制終端)、SIGTSTP(表示終端掛起)和SIGHUP(進(jìn)程組長(zhǎng)退出時(shí)向所有會(huì)議成員發(fā)出的)。
????2.將程序進(jìn)入后臺(tái)執(zhí)行。由于守護(hù)進(jìn)程最終脫離控制終端,到后臺(tái)去運(yùn)行。方法是在進(jìn)程中調(diào)用fork使父進(jìn)程終止,讓Daemon在子進(jìn)程中后臺(tái)執(zhí)行。這就是常說(shuō)的“脫殼”。子進(jìn)程繼續(xù)函數(shù)fork()的定義如下:
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);/PRE> |
BR>
????該函數(shù)是Linux/Unix編程中非常重要的函數(shù)。它被調(diào)用一次,但返回兩次。這兩次返回的區(qū)別是子進(jìn)程的返回值為“0”,而父進(jìn)程的返回值為子進(jìn)程的ID。如果出錯(cuò)則返回“-1”。
????3.脫離控制終端、登錄會(huì)話和進(jìn)程組。開發(fā)人員如果要擺脫它們,不受它們的影響,一般使用 setsid() 設(shè)置新會(huì)話的領(lǐng)頭進(jìn)程,并與原來(lái)的登錄會(huì)話和進(jìn)程組脫離。這只是其中的一種方法,也有如下處理的辦法:
if ((fd = open("/dev/tty",O_RDWR)) >= 0) {
ioctl(fd,TIOCNOTTY,NULL);
close(fd);
}/PRE> |
BR>
????其中/dev/tty是一個(gè)流設(shè)備,也是終端映射,調(diào)用close()函數(shù)將終端關(guān)閉。
????4.禁止進(jìn)程重新打開控制終端。進(jìn)程已經(jīng)成為無(wú)終端的會(huì)話組長(zhǎng),但它可以重新申請(qǐng)打開一個(gè)控制終端。開發(fā)人員可以通過(guò)不再讓進(jìn)程成為會(huì)話組長(zhǎng)的方式來(lái)禁止進(jìn)程重新打開控制終端,需要再次調(diào)用fork函數(shù)。
????上面的程序代碼表示結(jié)束第一子進(jìn)程,第二子進(jìn)程繼續(xù)(第二子進(jìn)程不再是會(huì)話組長(zhǎng))。
????5. 關(guān)閉打開的文件描述符,并重定向標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤輸出的文件描述符。進(jìn)程從創(chuàng)建它的父進(jìn)程那里繼承了打開的文件描述符。如果不關(guān)閉,將會(huì)浪費(fèi)系統(tǒng)資源,引起無(wú)法預(yù)料的錯(cuò)誤。關(guān)閉三者的代碼如下:
for (fd = 0, fdtablesize = getdtablesize();
fd < fdtablesize; fd++)
close(fd);/PRE> |
BR>
????但標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤輸出的重定向是可選的。也許有的程序想保留標(biāo)準(zhǔn)輸入(0)、標(biāo)準(zhǔn)輸出(1)和標(biāo)準(zhǔn)錯(cuò)誤輸出(2),那么循環(huán)應(yīng)繞過(guò)這三者。代碼如下:
for (fd =3, fdtablesize = getdtablesize();
fd < fdtablesize; fd++)
close(fd);/PRE> |
BR>
????有的程序有些特殊的需求,還需要將這三者重新定向。示例如下:
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);/PRE> |
BR>
????6.改變工作目錄到根目錄或特定目錄進(jìn)程活動(dòng)時(shí),其工作目錄所在的文件系統(tǒng)不能卸下。
????一般需要將工作目錄改變到根目錄或特定目錄,注意用戶對(duì)此目錄需要有讀寫權(quán)。防止超級(jí)用戶卸載設(shè)備時(shí)系統(tǒng)報(bào)告設(shè)備忙。
????7.處理SIGCHLD信號(hào)。SIGCHLD信號(hào)是子進(jìn)程結(jié)束時(shí),向內(nèi)核發(fā)送的信號(hào)。
如果父進(jìn)程不等待子進(jìn)程結(jié)束,子進(jìn)程將成為僵尸進(jìn)程(zombie)從而占用系統(tǒng)資源。因此需要對(duì)SIGCHLD信號(hào)做出處理,回收僵尸進(jìn)程的資源,避免造成不必要的資源浪費(fèi)??梢杂萌缦抡Z(yǔ)句:
????signal(SIGCHLD,(void *)reap_status);
????捕捉信號(hào)SIGCHLD,用下面的函數(shù)進(jìn)行處理:
void reap_status()
{ int pid;
union wait status;
while ((pid = wait3(&status,WNOHANG,NULL)) > 0)
…… }/PRE> |
BR>
????8.在Linux/Unix下有個(gè)syslogd的守護(hù)進(jìn)程,向用戶提供了syslog()系統(tǒng)調(diào)用。任何程序都可以通過(guò)syslog記錄事件。
????由于syslog非常好用和易配置,所以很多程序都使用syslog來(lái)發(fā)送它們的記錄信息。一般守護(hù)進(jìn)程也使用syslog向系統(tǒng)輸出信息。syslog有三個(gè)函數(shù),一般只需要用syslog(...)函數(shù),openlog()/closelog()可有可無(wú)。syslog()在shslog.h定義如下:
#include <syslog.h>
void syslog(int priority,char *format,...);/PRE> |
BR>
????其中參數(shù)priority指明了進(jìn)程要寫入信息的等級(jí)和用途。第二個(gè)參數(shù)是一個(gè)格式串,指定了記錄輸出的格式。在這個(gè)串的最后需要指定一個(gè)%m,對(duì)應(yīng)errno錯(cuò)誤碼。
????
應(yīng)用范例????下面給出Linux下編程的守護(hù)進(jìn)程的應(yīng)用范例,在UNIX中,不同版本實(shí)現(xiàn)的細(xì)節(jié)可能不一致,但其實(shí)現(xiàn)的原則是與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;
/* 忽略終端 I/O信號(hào),STOP信號(hào) */
signal(SIGTTOU,SIG_IGN);
signal(SIGTTIN,SIG_IGN);
signal(SIGTSTP,SIG_IGN);
signal(SIGHUP ,SIG_IGN);
/* 父進(jìn)程退出,程序進(jìn)入后臺(tái)運(yùn)行 */
if(fork()!=0) exit(1);
if(setsid()<0)exit(1);/* 創(chuàng)建一個(gè)新的會(huì)議組 */
/* 子進(jìn)程退出,孫進(jìn)程沒有控制終端了 */
if(fork()!=0) exit(1);
if(chdir("/tmp")==-1)exit(1);
/* 關(guān)閉打開的文件描述符,包括標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤輸出 */
for (fd = 0, fdtablesize = getdtablesize(); fd < fdtablesize; fd++)
close(fd);
umask(0);/*重設(shè)文件創(chuàng)建掩模 */
signal(SIGCHLD,SIG_IGN);/* 忽略SIGCHLD信號(hào) */
/*打開log系統(tǒng)*/
syslog(LOG_USER|LOG_INFO,"守護(hù)進(jìn)程測(cè)試!n");
while(1)
{
time(&now);
syslog(LOG_USER|LOG_INFO,"當(dāng)前時(shí)間:t%sttn",ctime(&now));
sleep(6);
}
}/PRE> |
BR>
????此程序在Turbo Linux 4.0下編譯通過(guò)。這個(gè)程序比較簡(jiǎn)單,但基本體現(xiàn)了守護(hù)進(jìn)程的編程要點(diǎn)。讀者針對(duì)實(shí)際應(yīng)用中不同的需要,還可以做相應(yīng)的調(diào)整。