【Linux】守護進程 : 充滿正能量的Orphan

繼殭屍與孤兒進程之後,我們終於迎來了一個充滿正能量的進程,但是很可惜,它仍舊是一個孤兒進程。但守護進程用途很廣泛,大多數的Linux服務器都是用守護進程來實現的,比如Internet服務器inetd,Web服務器httpd等。

守護進程的基本特性

守護進程也稱·精靈進程(Daemon),是運行在後臺的一種特殊進程。它獨立於控制終端並且週期性地執行某種任務或等待處理某些發生的事件。

Linux系統啓動時會啓動很多系統服務進程,這些系統服務進程沒有控制終端,不能直接和用戶交互。這些系統服務進程不受用戶登錄註銷的影響,它們一直運行着,這些系統服務進程就是守護進程。

下面我們通過一個終端命令來查看一下系統(CentOS)的守護進程:
ps axj | grep -E "d$"

對上述命令的解釋爲:

①ps:表示對進程監測和控制。

②參數a:表示不僅列出當前用戶的進程,也列出所有其他用戶的進程。

③參數x:表示不僅列出控制終端的進程,也列出所有無控制終端的進程。

④參數j:表示列出與作業控制相關的信息。

⑤grep -E “d$”:表示遞歸匹配以字符d結尾的信息。
在這裏插入圖片描述
從上圖的監測篩選結果可以看出,上述進程均屬於守護進程,它的特點如下:

1)PPID爲1(上圖紅色框):守護進程的父進程爲1,即init進程,守護進程爲孤兒進程。

2)PID|PGID|SIG相同(上圖黃色框):守護進程自成進程組,自成會話,不受用戶登錄或註銷影響。
	
3)TPGID爲-l(上圖粉色框):守護進程沒有控制終端的進程,並且它與終端無任何關係。

4)一般以-d結尾(上圖橘色框):表示Daemon,可有可無。

5)生命週期7*24:守護進程永不關閉,因爲大多數服務器都是藉助守護進程來實現的,服務器只能重啓,不能關機。

除此之外,在command一列用[]括起來的名字表示 內核線程,這些線程在內核裏創建,沒有用戶空間代碼,因此沒有程序文件名和命令行,通常採用以k開頭的名字,表示Kernal。udevd負責維護/dev目錄下的設備文件acpid負責電源管理syslogd負責維護/var/log下的日誌文件。

守護進程的創建規則

在編寫守護進程程序時需要遵循一些基本的規則,以便防止產生並不需要的交互作用,它的·創建規則如下:

⑴ 首先調用umask將文件模式創建屏蔽字(掩碼)設置爲0。

原因在於:由繼承得來的文件模式創建屏蔽字可能會拒絕設置某些權限。比如守護進程要創建一個可讀可寫的文件,而繼承的文件模式創建屏蔽字可能屏蔽了寫權限,導致功能缺失。

⑵ 調用fork(),然後使父進程退出(exit)。

原因在於:第一,如果該守護進程是作爲一條簡單的shell命令啓動的,那麼父進程退出使得shell認爲這條命令已經執行完畢。第二,子進程繼承了父進程的進程組id,但具有一個新的進程id,保證了子進程不是一個進程組的組長(下面會提到原因) 。

**⑶ 調用setsid創建一個新會話,併成爲Session Leader。**它的原型如下:
#include<unistd.h>
pid_t setsid(void);

返回值:調用成功返回新建會話的id(當前進程的id),出錯返回-1。

注意:調用setsid函數之前,當前進程不允許是進程組的Leader(組長),否則返回-1。

成功調用該函數的結果如下:

① 創建一個新的Session。當前進程成爲Session Leader,當前進程的id就是Session的id。

② 創建一個新的進程組。當前進程成爲進程組的Leader,當前進程的id就是進程組的id。

③ 如果當前進程原本有一個控制終端,則它失去了這個控制終端,成爲一個沒有控制終端的進程。(所謂失去控制終端指原來的控制終端仍然是打開的,仍然可以讀寫,但只是一個普通的打開文件而不是控制終端)。

⑷ 將當前工作目錄更改爲根目錄。使用chdir修改當前的工作目錄。

原因在於:從當前父進程繼承來的當前工作目錄可能在一個裝配文件系統中,因爲守護進程通常在系統再引導之前是一直存在的,如果當前守護進程目錄在一個裝配文件系統中,那麼該文件系統就不能被卸載。

⑸ 關閉不再需要的文件描述符。

原因在於:可以使得守護進程不再持有從其父進程繼承來的某些文件描述符。

⑹ 其他:忽略SIGCHLD信號。子進程退出時不再向父進程發送SIGCHLD信號。

下面我們遵循以上規則來創建一個守護進程,測試代碼如下:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void mydaemon()
{
	umask(0);//缺省值清0
	pid_t id = fork();
	if(id > 0){//father
		exit(1);
	}
	setsid();//設置新會話
	chdir("/");//更改工作目錄爲根目錄
	close(0);//關閉0、1、2文件描述符
	close(1);
	close(2);
	signal(SIGCHLD,SIG_IGN);//忽略SIGCHLD信號
}
int main()
{
	mydaemon();
	while(1){
		sleep(1);
	}
	return 0;
} 

在這裏插入圖片描述
結果與我們上面的預期一致,守護進程的父進程的pid爲1,自成進程組,自成會話,且與控制終端無關。

我們也可以通過守護進程的進程號,cd /proc目錄下查看相關信息:
在這裏插入圖片描述
在/proc目錄下有很多關於文件系統的信息,比如CPU信息(cpuinfo),內存信息(meminfo),磁盤信息(diskstats)等。
在這裏插入圖片描述
我們的Linux也提供了一個標準的創建守護進程的函數daemon,它的原型如下:

#include<unistd.h>
int daemon(int nochdir,int noclose);

參數nochdir:爲0時表示當前工作目錄變爲根目錄,否則不變。

參數noclose:爲0時表示將標準輸入、輸出、錯誤輸出重導向爲/dev/null,它是Linux下的黑洞,寫入的信息會被kernal丟棄,永遠寫不滿。

返回值:成功返回0,失敗返回-1並設置errno。

下面我們將兩個參數都設置爲0,觀察一下效果:
在這裏插入圖片描述

守護進程的兩次fork

有些open source採用的依舊是一次fork,當然,兩次fork是鑑於安全性的考慮,在開始正題之前,我們先模擬一下兩次fork,通過觀察結果來闡述爲什麼儘量要fork兩次?

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void mydaemon()
{
   umask(0);//缺省值清零
   if(fork() > 0){//第一次fork
   	exit(1);
   }
   setsid();//設置新會話
   signal(SIGCHLD,SIG_IGN);//忽略SIGCHLD信號
   if(fork() > 0){//第二次fork
   	exit(2);
   }
   chdir("/");//更改工作目錄爲/目錄
   close(0);
   close(1);
   close(2);
}
int main()
{
   mydaemon();
   while(1){
   	sleep(1);
   }
   return 0;
}

在這裏插入圖片描述
從結果可以最主要的區別是:進程的pid與gid、sid不同,這表明守護進程是屬於某個會話,屬於某個進程組。而脫離了一次fork自成會話,自成進程組的理論。那麼這樣做的目的何在呢?

第一次fork:我們使得子進程稱爲一個獨立的會話,稱爲了一個孤兒進程,被init收養,也就脫離了控制終端。

第二次fork:當前子進程已經稱爲了會話組長,則可以去打開控制終端,當我們再次fork時,子進程的id與sid不再相同,即不再是會話組的組長,因此也就無法打開新的控制終端(打開一個控制終端的前提是該進程必須是會話組長)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章