守護進程:代碼的分析

守護進程實現代碼如下所示:

[html] view plaincopy
  1. void daemon_mode(void) {  
  2.   int fr=0;  
  3.   
  4.   fr = fork();  
  5.   if( fr < 0 ) {  
  6.     fprintf(stderr, "fork() failed\n");  
  7.     exit(1);  
  8.   }  
  9.   if ( fr > 0 ) {  
  10.     exit(0);  
  11.   }  
  12.   
  13.   if( setsid() < 0 ) {  
  14.     fprintf(stderr, "setsid() failed\n");  
  15.     exit(1);  
  16.   }  
  17.   
  18.   fr = fork();  
  19.   if( fr < 0 ) {  
  20.     fprintf(stderr, "fork() failed\n");  
  21.     exit(1);  
  22.   }  
  23.   if ( fr > 0 ) {  
  24.     fprintf(stderr, "forked to background (%d)\n", fr);  
  25.     exit(0);  
  26.   }  
  27.   
  28.   umask(0);  
  29.   
  30.   fr = chdir("/");  
  31.   if ( fr != 0 ) {  
  32.     fprintf(stderr, "chdir(/) failed\n");  
  33.     exit(0);  
  34.   }  
  35.   
  36.   close(0);  
  37.   close(1);  
  38.   close(2);  
  39.   
  40.   open("/dev/null", O_RDWR);  
  41.     
  42.   fr = dup(0);  
  43.   fr = dup(0);  
  44. }  

其實現可概括如下:

(1) 利用fork()創建進程

[html] view plaincopy
  1. fr = fork();  
  2.   if( fr < 0 ) {  
  3.     fprintf(stderr, "fork() failed\n");  
  4.     exit(1);  
  5.   }  

(2) 父進程退出,使子進程成爲孤兒進程,1號進程(init進程)收養該孤兒進程

[html] view plaincopy
  1. if ( fr > 0 ) {  
  2.     exit(0);  
  3.   }  

(3) 開啓新會話

[html] view plaincopy
  1. if( setsid() < 0 ) {  
  2.     fprintf(stderr, "setsid() failed\n");  
  3.     exit(1);  
  4.   }  

在調用了fork函數時,子進程全盤拷貝了父進程的會話期、進程組、控制終端等。雖然父進程退出了,但會話期、進程組、控制終端等並沒有改變。因此,這還不是真正意義上的獨立開來,而setsid函數能夠使進程完全獨立出來,從而擺脫其他進程的控制。

(4) 再次調用fork

[html] view plaincopy
  1. fr = fork();  
  2.   if( fr < 0 ) {  
  3.     fprintf(stderr, "fork() failed\n");  
  4.     exit(1);  
  5.   }  
  6.   if ( fr > 0 ) {  
  7.     fprintf(stderr, "forked to background (%d)\n", fr);  
  8.     exit(0);  
  9.   }  

POSIX標準中,setsid()系統調用,將進程與當前的會話過程和進程組分開。但是,這一作用,需要執行進程本身不是會話過程的領頭進程。因此,可以通過在第一次fork()系統調用(父進程退出)後,在子進程中執行setsid()系統調用來脫離進程組。這樣,新的子進程成了新的會話過程的領頭進程,也沒有控制終端;但是,當它這種領頭進程去打開未成爲某個會話過程的控制終端的終端設備時,這類終端設備會自動成爲這個沒有控制終端的會話過程的控制終端。從而,背離了守護進程沒有控制終端的要求。因此,需要第二次fork()系統調用(父進程退出)後,在子進程中去完成餘下工作。再次fork創建一個子進程,防止第一次創建的子進程獲取控制終端。可以通過使進程不再成爲會話組長來禁止進程重新打開控制終端:

[html] view plaincopy
  1. if(pid=fork()) exit(0); //結束第一子進程,第二子進程繼續(第二子進程不再是會話組長)  

(5) 設置掩碼

[html] view plaincopy
  1. umask(0);  

就是設置文件權限。把文件權限掩碼設置爲0,可以大大增強該守護進程的靈活性。

(6) 改變目錄爲根目錄

[html] view plaincopy
  1. fr = chdir("/");  
  2.   if ( fr != 0 ) {  
  3.     fprintf(stderr, "chdir(/) failed\n");  
  4.     exit(0);  
  5.   }  

使用fork創建的子進程繼承了父進程的當前工作目錄。由於在進程運行中,當前目錄所在的文件系統(如“/mnt/usb”)是不能卸載的,這對以後的使用會造成諸多的麻煩(比如系統由於某種原因要進入單用戶模式)。

(7) 關閉文件描述符

[html] view plaincopy
  1. close(0);  
  2. close(1);  
  3. close(2);  

在上面的第三步之後,守護進程已經與所屬的控制終端失去了聯繫。因此從終端輸入的字符不可能達到守護進程,守護進程中用常規方法(如printf)輸出的字符也不可能在終端上顯示出來。所以,文件描述符爲0、1和2 的3個文件(常說的輸入、輸出和報錯)已經失去了存在的價值,也應被關閉。

(8) 創建空設備

[html] view plaincopy
  1. open("/dev/null", O_RDWR);  

空設備用於接收錯誤信息。 find指令中常見這種做法:

[html] view plaincopy
  1. find / -name sh 2>/dev/null  

(9) 描述符置0

[html] view plaincopy
  1. fr = dup(0);  
  2. fr = dup(0);  
發佈了64 篇原創文章 · 獲贊 3 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章