守護(hù)進(jìn)程(Daemon)也稱為精靈進(jìn)程,是運行在后臺的一種特殊進(jìn)程,它獨立于控制終端并且周期性地執(zhí)行某種任務(wù)或等待處理某些事情的發(fā)生,主要表現(xiàn)為以下兩個特點:
? 長期運行。守護(hù)進(jìn)程是一種生存期很長的一種進(jìn)程,它們一般在系統(tǒng)啟動時開始運行,除非強(qiáng)行終止,否則直到系統(tǒng)關(guān)機(jī)都會保持運行。與守護(hù)進(jìn)程相比,普通進(jìn)程都是在用戶登錄或運行程序時創(chuàng)建,在運行結(jié)束或用戶注銷時終止,但守護(hù)進(jìn)程不受用戶登錄注銷的影響,它們將會一直運行著、直到系統(tǒng)關(guān)機(jī)。
? 與控制終端脫離。在 Linux 中,系統(tǒng)與用戶交互的界面稱為終端,每一個從終端開始運行的進(jìn)程都會依附于這個終端,這是上一小節(jié)給大家介紹的控制終端,也就是會話的控制終端。當(dāng)控制終端被關(guān)閉的時候,該會話就會退出,由控制終端運行的所有進(jìn)程都會被終止,這使得普通進(jìn)程都是和運行該進(jìn)程的終端相綁定的;但守護(hù)進(jìn)程能突破這種限制,它脫離終端并且在后臺運行,脫離終端的目的是為了避免進(jìn)程在運行的過程中的信息在終端顯示并且進(jìn)程也不會被任何終端所產(chǎn)生的信息所打斷。
守護(hù)進(jìn)程是一種很有用的進(jìn)程。Linux 中大多數(shù)服務(wù)器就是用守護(hù)進(jìn)程實現(xiàn)的,譬如,Internet 服務(wù)器 inetd、Web 服務(wù)器 httpd 等。同時,守護(hù)進(jìn)程完成許多系統(tǒng)任務(wù),譬如作業(yè)規(guī)劃進(jìn)程 crond 等。
守護(hù)進(jìn)程 Daemon,通常簡稱為 d,一般進(jìn)程名后面帶有 d 就表示它是一個守護(hù)進(jìn)程。守護(hù)進(jìn)程與終端無任何關(guān)聯(lián),用戶的登錄與注銷與守護(hù)進(jìn)程無關(guān)、不受其影響,守護(hù)進(jìn)程自成進(jìn)程組、自成會話,即pid=gid=sid。通過命令"ps -ajx"查看系統(tǒng)所有的進(jìn)程,如下所示:
TTY 一欄是問號?表示該進(jìn)程沒有控制終端,也就是守護(hù)進(jìn)程,其中 COMMAND 一欄使用中括號[]括起來的表示內(nèi)核線程,這些線程是在內(nèi)核里創(chuàng)建,沒有用戶空間代碼,因此沒有程序文件名和命令行,通常采用 k 開頭的名字,表示 Kernel。
編寫守護(hù)進(jìn)程程序
- 創(chuàng)建子進(jìn)程、終止父進(jìn)程。父進(jìn)程調(diào)用 fork()創(chuàng)建子進(jìn)程,然后父進(jìn)程使用 exit()退出,這樣做實現(xiàn)了下面幾點。第一,如果該守護(hù)進(jìn)程是作為一條簡單地 shell 命令啟動,那么父進(jìn)程終止會讓 shell 認(rèn)為這條命令已經(jīng)執(zhí)行完畢。第二,雖然子進(jìn)程繼承了父進(jìn)程的進(jìn)程組ID,但它有自己獨立的進(jìn)程ID,這保證了子進(jìn)程不是一個進(jìn)程組的組長進(jìn)程,這是下面將要調(diào)用 setsid 函數(shù)的先決條件!
- 子進(jìn)程調(diào)用 setsid 創(chuàng)建會話。setsid()函數(shù)創(chuàng)建新的會話,由于之前子進(jìn)程并不是進(jìn)程組的組長進(jìn)程,所以調(diào)用 setsid()會使得子進(jìn)程創(chuàng)建一個新的會話,子進(jìn)程成為新會話的首領(lǐng)進(jìn)程,同樣也創(chuàng)建了新的進(jìn)程組、子進(jìn)程成為組長進(jìn)程,此時創(chuàng)建的會話將沒有控制終端。所以這里調(diào)用 setsid 有三個作用:讓子進(jìn)程擺脫原會話的控制、讓子進(jìn)程擺脫原進(jìn)程組的控制和讓子進(jìn)程擺脫原控制終端的控制。在調(diào)用 fork 函數(shù)時,子進(jìn)程繼承了父進(jìn)程的會話、進(jìn)程組、控制終端等,雖然父進(jìn)程退出了,但原先的會話期、進(jìn)程組、控制終端等并沒有改變,因此,那還不是真正意義上使兩者獨立開來。setsid 函數(shù)能夠使子進(jìn)程完全獨立出來,從而脫離所有其他進(jìn)程的控制。
- 將工作目錄更改為根目錄。子進(jìn)程是繼承了父進(jìn)程的當(dāng)前工作目錄,由于在進(jìn)程運行中,當(dāng)前目錄所在的文件系統(tǒng)是不能卸載的,這對以后使用會造成很多的麻煩。因此通常的做法是讓“/”作為守護(hù)進(jìn)程的當(dāng)前目錄,當(dāng)然也可以指定其 它目錄來作為守護(hù)進(jìn)程的工作目錄。
- 重設(shè)文件權(quán)限掩碼 umask。文件權(quán)限掩碼 umask 用于對新建文件的權(quán)限位進(jìn)行屏蔽,在 5.5.5 小節(jié)中有介紹。由于使用 fork 函數(shù)新建的子進(jìn)程繼承了父進(jìn)程的文件權(quán)限掩碼,這就給子進(jìn)程使用文件帶來了諸多的麻煩。因此,把文件權(quán)限掩 碼設(shè)置為 0,確保子進(jìn)程有最大操作權(quán)限、這樣可以大大增強(qiáng)該守護(hù)進(jìn)程的靈活性。設(shè)置文件權(quán)限掩碼的函數(shù)是 umask,通常的使用方法為 umask(0)。
- 關(guān)閉不再需要的文件描述符。子進(jìn)程繼承了父進(jìn)程的所有文件描述符,這些被打開的文件可能永遠(yuǎn)不會被守護(hù)進(jìn)程(此時守護(hù)進(jìn)程指的就是子進(jìn)程,父進(jìn)程退出、子進(jìn)程成為守護(hù)進(jìn)程)讀或?qū)懀鼈円粯酉南到y(tǒng)資源,可能導(dǎo)致所在的文件系統(tǒng)無法卸載,所以必須關(guān)閉這些文件,這使得守護(hù)進(jìn)程不再持有從其父進(jìn)程繼承過來的任何文件描述符。
- 將文件描述符號為 0、1、2 定位到/dev/null。將守護(hù)進(jìn)程的標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出以及標(biāo)準(zhǔn)錯誤重定向到/dev/null,這使得守護(hù)進(jìn)程的輸出無處顯示、也無處從交互式用戶那里接收輸入。
- 忽略 SIGCHLD 信號。處理 SIGCHLD 信號不是必須的,但對于某些進(jìn)程,特別是并發(fā)服務(wù)器進(jìn)程往往是特別重要的,服務(wù)器進(jìn)程在接收到客戶端請求時會創(chuàng)建子進(jìn)程去處理該請求,如果子進(jìn)程結(jié)束之后,父進(jìn)程沒有去 wait 回收子進(jìn)程,則子進(jìn)程將成為僵尸進(jìn)程;如果父進(jìn)程 wait 等待子進(jìn)程退出,將又會增加父進(jìn)程的負(fù)擔(dān)、也就是增加服務(wù)器的負(fù)擔(dān),影響服務(wù)器進(jìn)程的并發(fā)性能,在 Linux 下,可以將 SIGCHLD 信號的處理方式設(shè)置為SIG_IGN,也就是忽略該信號,可讓內(nèi)核將僵尸進(jìn)程轉(zhuǎn)交給 init 進(jìn)程去處理,這樣既不會產(chǎn)生僵尸進(jìn)程、又省去了服務(wù)器進(jìn)程回收子進(jìn)程所占用的時間。
#include < stdio.h >
#include < stdlib.h >
#include < unistd.h >
#include < sys/types.h >
#include < sys/stat.h >
#include < fcntl.h >
#include < signal.h >
int main(void) {
pid_t pid;
int i;
/* 創(chuàng)建子進(jìn)程 */
pid = fork();
if (0 > pid) {
perror("fork error");
exit(-1);
}
else if (0 < pid)//父進(jìn)程
exit(0); //直接退出
/*
*子進(jìn)程
*/
/* 1.創(chuàng)建新的會話、脫離控制終端 */
if (0 > setsid()) {
perror("setsid error");
exit(-1);
}
/* 2.設(shè)置當(dāng)前工作目錄為根目錄 */
if (0 > chdir("/")) {
perror("chdir error");
exit(-1);
}
/* 3.重設(shè)文件權(quán)限掩碼 umask */
umask(0);
/* 4.關(guān)閉所有文件描述符 */
for (i = 0; i < sysconf(_SC_OPEN_MAX); i++)
close(i);
/* 5.將文件描述符號為 0、1、2 定位到/dev/null */
open("/dev/null", O_RDWR);
dup(0);
dup(0);
/* 6.忽略 SIGCHLD 信號 */
signal(SIGCHLD, SIG_IGN);
/* 正式進(jìn)入到守護(hù)進(jìn)程 */
for ( ; ; ) {
sleep(1);
puts("守護(hù)進(jìn)程運行中......");
}
exit(0);
}
整個代碼的編寫都是根據(jù)上面的介紹來完成的。運行之后,沒有任何打印信息輸出,原因在于守護(hù)進(jìn)程已經(jīng)脫離了控制終端,它的打印信息并不會輸出顯示到終端,在代碼中已經(jīng)將標(biāo)準(zhǔn)輸入、輸出以及錯誤重定位到了/dev/null,/dev/null 是一個黑洞文件,自 然是看不到輸出信息。
守護(hù)進(jìn)程可以通過終端命令行啟動,但通常它們是由系統(tǒng)初始化腳本進(jìn)行啟動,譬如/etc/rc*或 /etc/init.d/*等。
-
Linux
+關(guān)注
關(guān)注
87文章
11306瀏覽量
209570 -
終端
+關(guān)注
關(guān)注
1文章
1136瀏覽量
29895 -
程序
+關(guān)注
關(guān)注
117文章
3787瀏覽量
81066 -
系統(tǒng)
+關(guān)注
關(guān)注
1文章
1017瀏覽量
21351
發(fā)布評論請先 登錄
相關(guān)推薦
評論