這又是一個有趣的概念,daemon在英語中是"精靈"的意思,就像我們經常在迪斯尼動畫里見到的那些,有些會飛,有些不會,經常圍着動畫片的主人公轉來轉去,啰里啰唆地提一些忠告,時不時倒霉地撞在柱子上,有時候還會想出一些小小的花招,把主人公從敵人手中救出來,正因如此,daemon有時也被譯作"守護神"。所以,daemon進程在國內也有兩種譯法,有些人譯作"精靈進程",有些人譯作"守護進程",這兩種稱呼的出現頻率都很高。
與真正的daemon相似,daemon進程也習慣於把自己隱藏在人們的視線之外,默默為系統做出貢獻,有時人們也把它們稱作"后台服務進程"。daemon進程的壽命很長,一般來說,從它們一被執行開始,直到整個系統關閉,它們才會退出。幾乎所有的服務器程序,包括我們熟知的Apache和wu-FTP,都用daemon進程的形式實現。很多Linux下常見的命令如inetd和ftpd,末尾的字母d就是指daemon。
為什么一定要使用daemon進程呢?Linux中每一個系統與用戶進行交流的界面稱為終端(terminal),每一個從此終端開始運行的進程都會依附於這個終端,這個終端就稱為這些進程的控制終端(Controlling terminal),當控制終端被關閉時,相應的進程都會被自動關閉。關於這點,讀者可以用X-Window中的XTerm試驗一下,(每一個XTerm就是一個打開的終端,)我們可以通過鍵入命令啟動應用程序,比如:
$netscape
然后我們關閉XTerm窗口,剛剛啟動的netscape窗口也會隨之一同突然蒸發。但是daemon進程卻能夠突破這種限制,即使對應的終端關閉,它也能在系統中長久地存在下去,如果我們想讓某個進程長命百歲,不因為用戶或終端或其他的變化而受到影響,就必須把這個進程變成一個daemon進程。
如果想把自己的進程變成daemon進程,我們必須嚴格按照以下步驟進行:
- 調用fork產生一個子進程,同時父進程退出。我們所有后續工作都在子進程中完成。這樣做我們可以:
- 如果我們是從命令行執行的該程序,這可以造成程序執行完畢的假象,shell會回去等待下一條命令;
- 剛剛通過fork產生的新進程一定不會是一個進程組的組長,這為第2步的執行提供了前提保障。
這樣做還會出現一種很有趣的現象:由於父進程已經先於子進程退出,會造成子進程沒有父進程,變成一個孤兒進程(orphan)。每當系統發現一個孤兒進程,就會自動由1號進程收養它,這樣,原先的子進程就會變成1號進程的子進程。 - 調用setsid系統調用。這是整個過程中最重要的一步。setsid的介紹見附錄2,它的作用是創建一個新的會話(session),並自任該會話的組長(session leader)。如果調用進程是一個進程組的組長,調用就會失敗,但這已經在第1步得到了保證。調用setsid有3個作用:把當前工作目錄切換到根目錄。如果我們是在一個臨時加載的文件系統上執行這個進程的,比如:/mnt/floppy/,該進程的當前工作目錄就會是/mnt/floppy/。在整個進程運行期間該文件系統都無法被卸下(umount),而無論我們是否在使用這個文件系統,這會給我們帶來很多不便。解決的方法是使用chdir系統調用把當前工作目錄變為根目錄,應該不會有人想把根目錄卸下吧。關於chdir的用法,參見附錄1。
- 讓進程擺脫原會話的控制;
- 讓進程擺脫原進程組的控制;
- 讓進程擺脫原控制終端的控制;
總之,就是讓調用進程完全獨立出來,脫離所有其他進程的控制。 - 當然,在這一步里,如果有特殊的需要,我們也可以把當前工作目錄換成其他的路徑,比如/tmp。
- 將文件權限掩碼設為0。這需要調用系統調用umask,參見附錄3。每個進程都會從父進程那里繼承一個文件權限掩碼,當創建新文件時,這個掩碼被用於設定文件的默認訪問權限,屏蔽掉某些權限,如一般用戶的寫權限。當另一個進程用exec調用我們編寫的daemon程序時,由於我們不知道那個進程的文件權限掩碼是什么,這樣在我們創建新文件時,就會帶來一些麻煩。所以,我們應該重新設置文件權限掩碼,我們可以設成任何我們想要的值,但一般情況下,大家都把它設為0,這樣,它就不會屏蔽用戶的任何操作。
如果你的應用程序根本就不涉及創建新文件或是文件訪問權限的設定,你也完全可以把文件權限掩碼一腳踢開,跳過這一步。 - 關閉所有不需要的文件。同文件權限掩碼一樣,我們的新進程會從父進程那里繼承一些已經打開了的文件。這些被打開的文件可能永遠不被我們的daemon進程讀或寫,但它們一樣消耗系統資源,而且可能導致所在的文件系統無法卸下。需要指出的是,文件描述符為0、1和2的三個文件(文件描述符的概念將在下一章介紹),也就是我們常說的輸入、輸出和報錯這三個文件也需要被關閉。很可能不少讀者會對此感到奇怪,難道我們不需要輸入輸出嗎?但事實是,在上面的第2步后,我們的daemon進程已經與所屬的控制終端失去了聯系,我們從終端輸入的字符不可能達到daemon進程,daemon進程用常規的方法(如printf)輸出的字符也不可能在我們的終端上顯示出來。所以這三個文件已經失去了存在的價值,也應該被關閉。
下面,就然我們親眼看一個daemon進程的誕生:
/* daemon.c */
#include<unistd.h>
#include<sys/types.h>
#include <sys/stat.h>
#define MAXFILE 65535
main()
{
pid_t pid; int i;
pid=fork();
if(pid<0)
{
printf("error in fork\n");
exit(1);
}
else if(pid>0)
/* 父進程退出 */
exit(0);
/* 調用setsid */
setsid();
/* 切換當前目錄 */
chdir("/");
/* 設置文件權限掩碼 */
umask(0);
/* 關閉所有可能打開的不需要的文件 */
for(i=0;i<MAXFILE;i++)
close(i);
/*到現在為止,進程已經成為一個完全的daemon進程,你可以在這里添加任何你要daemon做的事情*/
for(;;)
sleep(10);
}