什么是Daemon進程
這又是一個有趣的概念,daemon在英語中是"精靈"的意思,就像我們經(jīng)常在迪斯尼動畫里見到的那些,有些會飛,有些不會,經(jīng)常圍著動畫片的主人公轉(zhuǎn)來轉(zhuǎn)去,啰里啰唆地提一些忠告,時不時倒霉地撞在柱子上,有時候還會想出一些小小的花招,把主人公從敵人手中救出來,正因如此,daemon有時也被譯作"守護神"。所以,daemon進程在國內(nèi)也有兩種譯法,有些人譯作"精靈進程",有些人譯作"守護進程",這兩種稱呼的出現(xiàn)頻率都很高。
與真正的daemon相似,daemon進程也習(xí)慣于把自己隱藏在人們的視線之外,默默為系統(tǒng)做出貢獻,有時人們也把它們稱作"后臺服務(wù)進程"。daemon進程的壽命很長,一般來說,從它們一被執(zhí)行開始,直到整個系統(tǒng)關(guān)閉,它們才會退出。幾乎所有的服務(wù)器程序,包括我們熟知的Apache和wu-FTP,都用daemon進程的形式實現(xiàn)。很多Linux下常見的命令如inetd和ftpd,末尾的字母d就是指daemon。
為什么一定要使用daemon進程呢?Linux中每一個系統(tǒng)與用戶進行交流的界面稱為終端(terminal),每一個從此終端開始運行的進程都會依附于這個終端,這個終端就稱為這些進程的控制終端(Controlling terminal),當(dāng)控制終端被關(guān)閉時,相應(yīng)的進程都會被自動關(guān)閉。關(guān)于這點,讀者可以用X-Window中的XTerm試驗一下,(每一個XTerm就是一個打開的終端,)我們可以通過鍵入命令啟動應(yīng)用程序,比如:$netscape 然后我們關(guān)閉XTerm窗口,剛剛啟動的netscape窗口也會隨之一同突然蒸發(fā)。但是daemon進程卻能夠突破這種限制,即使對應(yīng)的終端關(guān)閉,它也能在系統(tǒng)中長久地存在下去,如果我們想讓某個進程長命百歲,不因為用戶或終端或其他的變化而受到影響,就必須把這個進程變成一個daemon進程。
Daemon進程的編程規(guī)則
如果想把自己的進程變成daemon進程,我們必須嚴格按照以下步驟進行:
1、調(diào)用fork產(chǎn)生一個子進程,同時父進程退出。我們所有后續(xù)工作都在子進程中完成。這樣做我們可以:
1.1 如果我們是從命令行執(zhí)行的該程序,這可以造成程序執(zhí)行完畢的假象,shell會回去等待下一條命令;
1.2 剛剛通過fork產(chǎn)生的新進程一定不會是一個進程組的組長,這為第2步的執(zhí)行提供了前提保障。
這樣做還會出現(xiàn)一種很有趣的現(xiàn)象:由于父進程已經(jīng)先于子進程退出,會造成子進程沒有父進程,變成一個孤兒進程(orphan)。每當(dāng)系統(tǒng)發(fā)現(xiàn)一個孤兒進程,就會自動由1號進程收養(yǎng)它,這樣,原先的子進程就會變成1號進程的子進程。
2、調(diào)用setsid系統(tǒng)調(diào)用。這是整個過程中最重要的一步。setsid的介紹見附錄2,它的作用是創(chuàng)建一個新的會話(session),并自任該會話的組長(session leader)。如果調(diào)用進程是一個進程組的組長,調(diào)用就會失敗,但這已經(jīng)在第1步得到了保證。調(diào)用setsid有3個作用:
2.1 讓進程擺脫原會話的控制;
2.2 讓進程擺脫原進程組的控制;
2.3 讓進程擺脫原控制終端的控制;
總之,就是讓調(diào)用進程完全獨立出來,脫離所有其他進程的控制。
3、把當(dāng)前工作目錄切換到根目錄。
如果我們是在一個臨時加載的文件系統(tǒng)上執(zhí)行這個進程的,比如:/mnt/floppy/,該進程的當(dāng)前工作目錄就會是/mnt/floppy/。在整個進程運行期間該文件系統(tǒng)都無法被卸下(umount),而無論我們是否在使用這個文件系統(tǒng),這會給我們帶來很多不便。解決的方法是使用chdir系統(tǒng)調(diào)用把當(dāng)前工作目錄變?yōu)楦夸?,?yīng)該不會有人想把根目錄卸下吧。
關(guān)于chdir的用法,參見附錄1。
當(dāng)然,在這一步里,如果有特殊的需要,我們也可以把當(dāng)前工作目錄換成其他的路徑,比如/tmp。
4、將文件權(quán)限掩碼設(shè)為0。
這需要調(diào)用系統(tǒng)調(diào)用umask,參見附錄3。每個進程都會從父進程那里繼承一個文件權(quán)限掩碼,當(dāng)創(chuàng)建新文件時,這個掩碼被用于設(shè)定文件的默認訪問權(quán)限,屏蔽掉某些權(quán)限,如一般用戶的寫權(quán)限。當(dāng)另一個進程用exec調(diào)用我們編寫的daemon程序時,由于我們不知道那個進程的文件權(quán)限掩碼是什么,這樣在我們創(chuàng)建新文件時,就會帶來一些麻煩。所以,我們應(yīng)該重新設(shè)置文件權(quán)限掩碼,我們可以設(shè)成任何我們想要的值,但一般情況下,大家都把它設(shè)為0,這樣,它就不會屏蔽用戶的任何操作。
如果你的應(yīng)用程序根本就不涉及創(chuàng)建新文件或是文件訪問權(quán)限的設(shè)定,你也完全可以把文件權(quán)限掩碼一腳踢開,跳過這一步。
5、關(guān)閉所有不需要的文件。
同文件權(quán)限掩碼一樣,我們的新進程會從父進程那里繼承一些已經(jīng)打開了的文件。這些被打開的文件可能永遠不被我們的daemon進程讀或?qū)懀鼈円粯酉南到y(tǒng)資源,而且可能導(dǎo)致所在的文件系統(tǒng)無法卸下。需要指出的是,文件描述符為0、1和2的三個文件(文件描述符的概念將在下一章介紹),也就是我們常說的輸入、輸出和報錯這三個文件也需要被關(guān)閉。很可能不少讀者會對此感到奇怪,難道我們不需要輸入輸出嗎?但事實是,在上面的第2步后,我們的daemon進程已經(jīng)與所屬的控制終端失去了聯(lián)系,我們從終端輸入的字符不可能達到daemon進程,daemon進程用常規(guī)的方法(如printf)輸出的字符也不可能在我們的終端上顯示出來。所以這三個文件已經(jīng)失去了存在的價值,也應(yīng)該被關(guān)閉。
使用PHP編寫Gearman的Worker守護進程
在我之前的文章中,介紹過Gearman的使用。在我的項目中,我使用了PHP來編寫一直運行的Worker。如果按照Gearman官方推薦的例子,只是簡單的一個循環(huán)來等待任務(wù),會有一些問題,包括:1、當(dāng)代碼進行過修改之后,如何讓代碼的修改生效;2、重啟Worker的時候,如何保證當(dāng)前的任務(wù)處理完成才重啟。
針對這個問題,我考慮了以下的解決方法:
1、每次修改完代碼后,Worker需要手工重啟(先殺死然后啟動)。這個只能解決重新加載配置文件的問題。
2、在Worker中設(shè)置,單次任務(wù)循環(huán)完成后,就對Worker進行重啟。這個方案的問題在于消耗比較大。
3、在Worker中添加一個退出函數(shù),如果需要Worker退出的時候,在Client端發(fā)送一個優(yōu)先級比較高的退出調(diào)用。這個需要客戶端配合,在使用后臺類任務(wù)時,不太適合。
4、在Worker中檢查文件是否發(fā)生變化,如果發(fā)生了變化,退出并重啟自身。
5、為Worker編寫信號控制,接受重啟指令,類似于 http restart graceful 指令。
最后,結(jié)合4和5兩種方法,可以實現(xiàn)這樣一個Daemon,如果配置文件發(fā)生了變化,他就會自動重啟;如果接受到了用戶的 kill -1 pid 信號,也會重新啟動。
代碼如下:
Copy to Clipboard
declare( ticks = 1 );
// This case will check the config file regularly, if the config file changed, it will restart it self
// If you want to restart the daemon gracefully, give it a HUP signal
// by shiqiang at 2011-12-04
$init_md5 = md5_file( 'config.php');
// register signal handler
pcntl_signal( SIGALRM, "signal_handler", true );
pcntl_signal( SIGHUP, 'signal_handler', TRUE );
$job_flag = FALSE; //Job status flag, to justify if the job has been finished
$signal_flag = FALSE; //Signal status flag, to justify whether we received the kill -1 signal
while( 1 ){
$job_flag = FALSE; //Job status flag
print "Worker start running ... n";
sleep(5);
print "Worker's task done ... n";
$flag = TRUE; //Job status flag
AutoStart( $signal_flag );
}
function signal_handler( $signal ) {
global $job_flag;
global $signal_flag;
switch( $signal ){
case SIGQUIT:
print date('y-m-d H:i:s', time() ) . " Caught Signal : SIGQUIT - No : $signal n";
exit(0);
break;
case SIGSTOP:
print date('y-m-d H:i:s', time() ) . " Caught Signal : SIGSTOP - No : $signal n";
break;
case SIGHUP:
print date('y-m-d H:i:s', time() ) . " Caught Signal : SIGHUP - No : $signal n";
if( $flag === TRUE ){
AutoStart( TRUE );
}else{
$signal_flag = TRUE;
}
break;
case SIGALRM:
print date('y-m-d H:i:s', time() ) . " Caught Signal : SIGALRM - No : $signal n";
//pcntl_exec( '/bin/ls' );
pcntl_alarm( 5 );
break;
default:
break;
}
}
function AutoStart( $signal = FALSE, $filename = 'config.php' ){
global $init_md5;
if( $signal || md5_file( $filename ) != $init_md5 ){
print "The config file has been changed, we are going to restart. n";
$pid = pcntl_fork();
if( $pid == -1 ){
print "Fork error n";
}else if( $pid > 0 ){
print "Parent exit n";
exit(0);
}else{
$init_md5 = md5_file( $filename );
print "Child continue to run n";
}
}
}
更多信息請查看IT技術(shù)專欄