為什麼要引入守護進程: 因為它生存期長,它獨立於控制終端、會話周期(下文有解釋)執行任務: 由於在linux中,每一個系統與用戶進行交流的界面稱為終端,每一個從此終端開始運行的進程都會依賴這個終端,這個終端就稱為這些進程的控制終端。當控制終端被關閉時,相應的進程都會自動關閉。但是守護進程卻能...
-
為什麼要引入守護進程:
因為它生存期長,它獨立於控制終端、會話周期(下文有解釋)執行任務:
由於在linux中,每一個系統與用戶進行交流的界面稱為終端,每一個從此終端開始運行的進程都會依賴這個終端,這個終端就稱為這些進程的控制終端。當控制終端被關閉時,相應的進程都會自動關閉。但是守護進程卻能突破這種限制,它被執行開始運轉,直到整個系統關閉時才退出。
-
守護進程的特性:
1> 守護進程最重要的特性是後臺運行。
2> 其次,守護進程必須與其運行前的環境隔離開來。這些環境包括未關閉的文件描述符、控制終端、會話和進程組、工作目錄已經文件創建掩碼等。這些環境通常是守護進程從父進程那裡繼承下來的。
3> 守護進程的啟動方式有其特殊之處:它可以在linux系統啟動時從啟動腳本/etc/rc.d中啟動,可以由作業規划進程crond啟動,還可以由用戶終端(通常是shell)執行。
-
後臺進程 == 守護進程?
在linux下使用&可以使程式進入後臺運行模式,使用守護進程方法也可以使程式和終端分離出來,那麼後臺進程是否就是守護進程?其實兩者不相等!
最直觀最重要的區別,守護進程沒有控制終端,而後臺進程有。
1.基本上任何一個程式都可以後臺運行,但守護進程是具有特殊要求的程式,比如要脫離自己的父進程,成為自己的會話組長等,這些要在代碼中顯式地寫出來。
2.守護進程成為了進程組長(或者會話組長),和控制終端失去了聯繫(其文件描述符也是繼承於父進程的,但是在變成守護進程的同時stdin,stdout,stderr和控制台失去聯繫了)。
3.後臺的文件描述符也是繼承於父進程,例如shell,所以它也可以再當前終端下顯式輸出數據,但是daemon進程自己變成了進程組長,其文件描述符號和控制終端沒有關聯,是脫離控制台的進程。
小結:守護進程肯定是後臺進程,但反之不成立。守護進程顧名思義,主要用於一些長期運行,守護著自己的職責(監聽埠,監聽服務等)。我們的系統下就有很多守護進程。
-
守護進程編程規則(步驟):
step 1、在後臺運行
一些概念:
- 進程組:是一個或多個進程的集合。進程組有進程組ID來唯一標識。除了進程號(PID)之外,進程組ID(GID)也是一個進程的必備屬性。每個進程都有一個組長進程,其組長進程的進程號等於進程組ID。且該進程組ID不會因為組長進程的退出而受影響。
- 會話周期:會話期是一個或多個進程組的集合。通常,一個會話開始於用戶登錄,終止於用戶退 出,在此期間該用戶運行的所有進程都屬於這個會話期。
- 控制終端:由於在linux中,每一個系統與用戶進行交流的界面稱為終端,每一個從此終端開始運行 的進程都會依賴這個控制終端。
為避免掛起控制終端,將daemon放入後臺執行,方法是進程中調用fork,然後使父進程exit,讓daemon在後臺執行。這樣做實現了以下兩點:
1.如果該守護進程是作為一條簡單的shell命令啟動的,那麼父進程終止會讓shell認為這條命令已經執行完畢。
2.雖然紫禁城繼承了父進程的進程組ID(pgid),但獲得了一個新的進程ID(pid),這就保證了子進程不是一個進程組的組長進程,這是下麵要進行的setsid調用的先決條件!
控制終端,登錄會話和進程組通常是從父進程繼承下來的。我們的目的就是要擺脫它們,使之不受它們的影響。
進程調用 setsid函數建立一個新會話
1 #include<unistd.h> 2 pid_t setsid(void);
如果調用此函數的進程不是一個進程組的組長,則此函數創建一個新會話,會發生以下3件事
1.該進程會變成新回話的會話首進程(session leader,會話首進程是創建該會話的進程)。此時,該進程是新會話中的唯一進程。(擺脫原會話的控制)
2.該進程成為一個新進程組的組長進程,新進程組ID是該調用進程的進程ID。(擺脫原進程組的控制)
3.該進程沒有控制終端。如果在調用setsid之前該進程有一個控制終端,那麼這種聯繫也被切斷。(擺脫控制終端)
step 2:在子進程中調用setsid創建一個新會話。這會執行上面的3件事!
通過這一步,新的子進程就擺脫了原會話的控制,擺脫了原進程組的控制,擺脫了終端的控制。
step 3:禁止進程重新打開控制終端//可省,很多開源服務沒有fork第二次
現在,進程已經成為無終端的會話組長了。
但它可以重新申請打開一個控制終端。可以通過使進程不再成為會話組長來禁止進程重新打開控制終端。
因為打開一個控制終端的前提條件是該進程必須是會話組長!所以再fork一次,結束第一子進程,第二子進程繼續(第二子進程不再是會話組長),第二子進程ID != sid(sid是進程第一子進程的會ID:sid)。所以無法打開新的控制終端。
step 4 :將當前目錄更改為根目錄
從父進程處繼承過來的當前工作目錄可能在一個掛載的文件系統中。因為守護進程通常在系統再引導之前是一直存在的,所以如果守護進程的當前工作目錄在一個掛載文件系統中,那麼該文件系統就不能被卸載。
或者,某些守護進程還可能會把當前工作目錄更改到某個指定的位置,併在此位置進行它們的全部工作。例如,行式印表機假離線守護進程就可能將其工作目錄更改到它們的spool目錄上。
step 5:關閉所有(不再需要的)文件描述符
新進程會從父進程(父進程可能是shell進程,或某個其他進程)那裡繼承一些已經打開了的文件。這些被打開的文件可能永遠不會被守護進程讀寫,而它們一直消耗系統資源。另外守護進程已經與所屬的終端失去聯繫,那麼從終端輸入的字元不可能到達守護進程。可以使用open_max函數或getrlimit函數來判定最高文件描述符值,並關閉0直到該值的所有描述符。
strp 6:重設文件掩碼
進程從創建它的父進程那裡繼承了文件創建掩碼。它可能修改守護進程所創建的文件的存取位。即調用umask將文件模式創建屏蔽字設置為一個已知(通常為0)。
step 7:處理SIGCHLD信號
處理SIGCHLD信號並不是必須的。但對於某些進程,特別是伺服器進程往往在請求到來時生成子進程處理請求。如果父進程不等待子進程結束,子進程將成為 僵屍進程(zombie)從而占用系統資源。如果父進程等待子進程結束,將增加父進程的負擔,影響伺服器進程的併發性能。在Linux下可以簡單地將 SIGCHLD信號的操作設為SIG_IGN。
signal(SIGCHLD,SIG_IGN);
這樣,內核在子進程結束時不會產生僵屍進程。這一點與BSD4不同,BSD4下必須顯式等待子進程結束才能釋放僵屍進程。
- 無代碼無真相!
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <sys/stat.h> 5 void Daemon() 6 { 7 const int MAXFD=64; 8 int i=0; 9 if(fork()!=0) //父進程退出 10 exit(0); 11 setsid(); //成為新進程組組長和新會話領導,脫離控制終端 12 chdir("/"); //設置工作目錄為根目錄 13 umask(0); //重設文件訪問許可權掩碼 14 for(;i<MAXFD;i++) //儘可能關閉所有從父進程繼承來的文件 15 close(i); 16 } 17 int main() 18 { 19 Daemon(); //成為守護進程 20 while(1){ 21 sleep(1); 22 } 23 return 0; 24 }
(未完,待完善)