當(dāng)前位置:首頁 > 嵌入式培訓(xùn) > 嵌入式學(xué)習(xí) > 講師博文 > 詳解守護(hù)進(jìn)程的創(chuàng)建與fork兩次分析
相信大部分程序員都知道如何去創(chuàng)建一個守護(hù)(deamon)進(jìn)程,但是另一方面,有許多人不知道為什么要這么做,具體為什么這么實(shí)現(xiàn)。這里我們就來詳細(xì)分析一下創(chuàng)建deamon進(jìn)程每一步的意義。
本文引用地址://www.mairao.cn/emb/Column/7509.html
首先說一下deamon進(jìn)程的概念,deamon是一種運(yùn)行在后臺的一種特殊的進(jìn)程,它獨(dú)立于控制終端并且周期性的執(zhí)行某種任務(wù)或等待處理某些發(fā)生的事件。由于在Linux中,每個系統(tǒng)與用戶進(jìn)行交流的界面成為終端,每一個從此終端開始運(yùn)行的進(jìn)程都會依附于這個終端,這個終端被稱為這些進(jìn)程的控制終端,當(dāng)控制終端被關(guān)閉的時(shí)候,相應(yīng)的進(jìn)程都會自動關(guān)閉。但是守護(hù)進(jìn)程卻能突破這種限制,它脫離于終端并且在后臺運(yùn)行,并且它脫離終端的目的是為了避免進(jìn)程在運(yùn)行的過程中的信息在任何終端中顯示并且進(jìn)程也不會被任何終端所產(chǎn)生的終端信息所打斷。它從被執(zhí)行的時(shí)候開始運(yùn)轉(zhuǎn),直到整個系統(tǒng)關(guān)閉才退出(當(dāng)然可以人為的殺死相應(yīng)的守護(hù)進(jìn)程)。如果想讓某個進(jìn)程不因?yàn)橛脩艋蛑袛嗷蚱渌兓绊懀敲淳捅仨毎堰@個進(jìn)程變成一個守護(hù)進(jìn)程。
守護(hù)進(jìn)程的創(chuàng)建步驟:
1、創(chuàng)建子進(jìn)程,父進(jìn)程退出。由于守護(hù)進(jìn)程是脫離終端的,因此完成第一步后就會在shell終端里造成一個程序已經(jīng)運(yùn)行完畢的假象。之后的所有工作在子進(jìn)程中完成,而用戶在shell終端里則可以執(zhí)行其他命令,從而在形式上做到了與控制終端脫離。實(shí)現(xiàn)的語句如下:if(pid=fork()){exit(0);}是父進(jìn)程就結(jié)束,然后子進(jìn)程繼續(xù)執(zhí)行。
2、在子進(jìn)程中創(chuàng)建新的會話(脫離控制終端)。在這里使用的是系統(tǒng)函數(shù)setsid()來創(chuàng)建一個新的會話,并且擔(dān)任該會話組的組長,擺脫原會話的控制==》擺脫原進(jìn)程的控制==》擺脫原控制終端的控制。
3、改變當(dāng)前目錄為根目錄。使用fork()創(chuàng)建的子進(jìn)程是繼承了父進(jìn)程的當(dāng)前工作目錄,由于在進(jìn)程運(yùn)行中,當(dāng)前目錄所在的文件系統(tǒng)是不能卸載的,這對以后使用會造成很多的麻煩。因此通常的做法是讓“/”作為守護(hù)進(jìn)程的當(dāng)前目錄,當(dāng)然也可以指定其他的別的目錄來作為守護(hù)進(jìn)程的工作目錄。
4、重設(shè)文件權(quán)限掩碼。文件權(quán)限掩碼是屏蔽掉文件權(quán)限中的對應(yīng)位。由于使用fork()函數(shù)新創(chuàng)建的子進(jìn)程繼承了父進(jìn)程的文件權(quán)限掩碼,這就給該子進(jìn)程使用文件帶了很多的麻煩(比如父進(jìn)程中的文件沒有執(zhí)行文件的權(quán)限,然而在子進(jìn)程中希望執(zhí)行相應(yīng)的文件這個時(shí)候就會出問題)。因此在子進(jìn)程中要把文件的權(quán)限掩碼設(shè)置成為0,即在此時(shí)有大的權(quán)限,這樣可以大大增強(qiáng)該守護(hù)進(jìn)程的靈活性。設(shè)置的方法是:umask(0)。
5、關(guān)閉文件描述符。同文件權(quán)限碼一樣,用fork()函數(shù)新建的子進(jìn)程會從父進(jìn)程那里繼承一些已經(jīng)打開了的文件。這些文件被打開的文件可能永遠(yuǎn)不會被守護(hù)進(jìn)程讀寫,如果不進(jìn)行關(guān)閉的話將會浪費(fèi)系統(tǒng)的資源,造成進(jìn)程所在的文件系統(tǒng)無法卸下以及引起預(yù)料的錯誤。按照如下方法關(guān)閉它們:
fdtablesize = getdtablesize();
for (fd = 0; fd < fdtablesize; fd++)
close(fd);
我們來看一下代碼:
int main(int argc, const char *argv[])
{
pid_t pid;
pid = fork();
if(pid < 0)
{
perror("fail to fork");
exit(0);
}else if(pid > 0)
{
exit(0);
}else{
setsid();
umask(0);
pid = fork();
if(pid != 0)
{
exit(0);
}
chdir("/");
int maxfd = getdtablesize();
while(maxfd--)
{
close(maxfd);
}
while(1)
{
syslog(LOG_INFO,"im deamon\n");
sleep(1);
}
}
return 0;
}
可以看到上面的代碼里我fork了兩次,雖然說這并不是必須的,但是這的確是對守護(hù)進(jìn)程做出了一些更優(yōu)化的操作。
首先第一次fork:這里第一次fork的作用就是讓shell認(rèn)為這條命令已經(jīng)終止,不用掛在終端輸入上;再一個是為了后面的setsid服務(wù),因?yàn)檎{(diào)用setsid函數(shù)的進(jìn)程不能是進(jìn)程組組長(會報(bào)錯Operation not permitted),如果不fork子進(jìn)程,那么此時(shí)的父進(jìn)程是進(jìn)程組組長,無法調(diào)用setsid。所以到這里子進(jìn)程便成為了一個新會話組的組長。
第二次fork:第二次fork是為了避免后期進(jìn)程誤操作而再次打開終端。因?yàn)榇蜷_一個控制終端的前提條件是該進(jìn)程必須為會話組組長,而我們通過第二次fork,確保了第二次fork出來的子進(jìn)程不會是會話組組長。