"守護進程"(daemon)就是一直在后台運行的進程


 

        //fork執行中已經出現父和子進程,狀態一樣但不是相同的進程,兩條進程執行序都指向了fork函數內創建進程代碼后面一句的指令集,
        //此時是父進程占據cpu時間,父進程繼續執行根據fork后面的代碼實現返回創建的pid,
        //子進程之后繼續執行根據fork代碼實現返回的是0
        //創建子進程失敗返回-1
        $pid = pcntl_fork();
        if (-1 === $pid) {
            throw new Exception('fork fail');
        } elseif ($pid > 0) {
            exit(0);
        }

在Linux/UNIX系統引導的時候會開啟很多服務,這些服務稱為守護進程(也叫Daemon進程)。守護進程是脫離於控制終端並且在后台周期性地執行某種任務或等待處理某些事件的進程,脫離終端是為了避免進程在執行過程中的信息在任何終端上顯示並且進程也不會被任何終端所產生的中斷信息所終止。

 

創建守護進程的一般步驟

 

(1) 創建子進程,退出父進程

為了脫離控制終端需要退出父進程,之后的工作都由子進程完成。在Linux中父進程先於子進程退出會造成子進程成為孤兒進程,而每當系統發現一個孤兒進程時,就會自動由1號進程(init)收養它,這樣,原先的子進程就會變成init進程的子進程。

ps –ef | grep ProcName          通過PID/PPID查看進程的父子關系

 

(2) 在子進程中創建新的會話

使用系統函數setsid來完成。

man 2 setsid    查看關於setsid函數的說明

setsid – creates a session and sets theprocess group ID

#include <unistd.h>

pid_t setsid(void);

setsid() creates a new session if thecalling process is not a process group leader. The calling process is theleader of the new session, the process group leader of the new process group,and has no controlling tty. The process group ID and session ID of the callingprocess are set to the PID of the calling process. The calling process will bethe only process in this new process group and in this new session.

進程組:是一個或多個進程的集合。進程組有進程組ID來唯一標識。除了進程號PID之外,進程組ID也是一個進程的必備屬性。每個進程組都有一個組長進程,其組長進程的進程號等於進程組ID,且該進程組ID不會因組長進程的退出而受到影響。

setsid函數作用:用於創建一個新的會話,並擔任該會話組的組長。調用setsid有3個作用

(a) 讓進程擺脫原會話的控制;

(b) 讓進程擺脫原進程組的控制;

(c) 讓進程擺脫原控制終端的控制;

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

 

(3) 改變當前目錄為根目錄

使用fork創建的子進程繼承了父進程的當前的工作目錄。由於在進程運行中,當前目錄所在的文件系統是不能卸載的,這對以后的使用會造成諸多的麻煩。因此,通常的做法是讓根目錄”/”作為守護進程的當前工作目錄。這樣就可以避免上述的問題。如有特殊的需求,也可以把當前工作目錄換成其他的路徑。改變工作目錄的方法是使用chdir函數。

 

(4) 重設文件權限掩碼

文件權限掩碼:是指屏蔽掉文件權限中的對應位。例如,有個文件權限掩碼是050,它就屏蔽了文件組擁有者的可讀與可執行權限(對應二進制為,rwx, 101)。由於fork函數創建的子進程繼承了父進程的文件權限掩碼,這就給子進程使用文件帶來了諸多的麻煩。因此,把文件權限掩碼設置為0(即,不屏蔽任何權限),可以增強該守護進程的靈活性。設置文件權限掩碼的函數是umask。通常的使用方法為umask(0)。

 

(5) 關閉文件描述符

用fork創建的子進程也會從父進程那里繼承一些已經打開了的文件。這些被打開的文件可能永遠不會被守護進程讀寫,但它們一樣消耗系統資源,而且可能導致所在的文件系統無法卸載。在使用setsid調用之后,守護進程已經與所屬的控制終端失去了聯系,因此從終端輸入的字符不可能達到守護進程,守護進程中用常規方法(如printf)輸出的字符也不可能在終端上顯示出來。所以,文件描述符為0、1、2(即,標准輸入、標准輸出、標准錯誤輸出)的三個文件已經失去了存在的價值,也應該關閉。

 

(6) 守護進程退出處理

當用戶需要外部停止守護進程時,通常使用kill命令停止該守護進程。所以,守護進程中需要編碼來實現kill發出的signal信號處理,達到進程正常退出。

http://blog.csdn.net/delphiwcdj/article/details/7364343

http://www.ruanyifeng.com/blog/2016/02/linux-daemon.html

 

當我們只fork()一次后,存在父進程和子進程。這時有兩種方法來避免產生僵屍進程:

  • 父進程調用waitpid()等函數來接收子進程退出狀態。
  • 父進程先結束,子進程則自動托管到Init進程(pid = 1)。

      目前先考慮子進程先於父進程結束的情況:     

  • 若父進程未處理子進程退出狀態,在父進程退出前,子進程一直處於僵屍進程狀態。
  • 若父進程調用waitpid()(這里使用阻塞調用確保子進程先於父進程結束)來等待子進程結束,將會使父進程在調用waitpid()后進入睡眠狀態,只有子進程結束父進程的waitpid()才會返回。 如果存在子進程結束,但父進程還未執行到waitpid()的情況,那么這段時期子進程也將處於僵屍進程狀態。

      由此,可以看出父進程與子進程有父子關系,除非保證父進程先於子進程結束或者保證父進程在子進程結束前執行waitpid(),子進程均有機會成為僵屍進程。那么如何使父進程更方便地創建不會成為僵屍進程的子進程呢?這就要用兩次fork()了。

      父進程一次fork()后產生一個子進程隨后立即執行waitpid(子進程pid, NULL, 0)來等待子進程結束,然后子進程fork()后產生孫子進程隨后立即exit(0)。這樣子進程順利終止(父進程僅僅給子進程收屍,並不需要子進程的返回值),然后父進程繼續執行。這時的孫子進程由於失去了它的父進程(即是父進程的子進程),將被轉交給Init進程托管。於是父進程與孫子進程無繼承關系了,它們的父進程均為Init,Init進程在其子進程結束時會自動收屍,這樣也就不會產生僵屍進程了。

#include <stdio.h>  
#include <unistd.h>  
#include <stdlib.h>  
#include <sys/wait.h>  
  
int main(void)  
{  
    pid_t   pid;  
  
    if( ( pid = fork() ) < 0 ){  
        fprintf( stdout, "fork error!\n" );  
    } else if( pid == 0 ) {                     /*first child*/  
        if (( pid = fork()) < 0 )  
            printf( "fork error!\n" );  
        else if( pid > 0 )  
            exit( 0 );  
      
    sleep( 2 );  
    printf( "Second child , parent pid = %d\n", getppid() );  
    exit( 0 );            
    }  
  
    if( waitpid( pid, NULL, 0 ) != pid )  
        printf( "waitpid error!\n" );  
  
    printf( "father of original!\n" );  
  
    exit( 0 );  
  
  
}  

 進程調用fork與文件描述符的共享(fork,dump)

 Linux的進程描述task_struct{}中有一個數組專門用於記錄一打開的文件,其中文件描述符作為該數組的下標,數組元素為指向所打開的文件所創建的文件表項。如下圖所示,文件表項是用於描述文件當前被某個進程打開后的狀態信息,包括文件狀態標志,記錄當前文件讀取的位移量(可以通過接口lseek設置),以及文件的i節點指針(i節點描述文件的具體信息,如:創建,修改時間,文件大小,文件存儲的塊信息)。

       不同進程打開同一個文件后,進程表和文件表的關系如下圖所示:

進程的fork與文件描述符的拷貝

進程的所打開文件和在fork后的結構圖如下所示,子進程是共享父進程的文件表項;

可以通過一個測試實例來證實以上的描述:

測試源碼

 

[cpp]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. #include "slp.h"  
  2.   
  3. int main()  
  4. {  
  5.     int fd1,fd2,fd3,nr;  
  6.     char buff[20];  
  7.     pid_t pid;  
  8.     fd1 = open("data.in",O_RDWR);  
  9.     pid = fork();  
  10.     if(pid == 0)  
  11.     {     
  12.         nr = read(fd1,buff,10);  
  13.         buff[nr]='\0';  
  14.         printf("pid#%d content#%s#\n",getpid(),buff);  
  15.         exit(0);  
  16.     }     
  17.     nr = read(fd1,buff,10);  
  18.     buff[nr]='\0';  
  19.     printf("pid#%d content#%s#\n",getpid(),buff);  
  20.     return 0;  
  21. }  

測試用例

[html]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. data.in  
  2. abcdefghijklmnopqrstuvwxyz1234567890  
  3. EOF  


測試結果:
pid#20029 content#abcdefghij#
pid#20030 content#klmnopqrst#

 

結果分析:

進程20029對文件的讀取后的當前位置應該為data.in的k字符所在的位置,進程20030是由20029進程之后開始讀取的,他讀取文件內容不是從a開始,而是從k開始,說明20030共享了20029的文件表。

進程dump一個文件描述符

 

 

總結

進程調用fork后,子進程和父進程的文件描述符所對應的文件表項是共享的,這意味着子進程對文件的讀寫直接影響父進程的文件位移量(反之同理)。

進程中調用fd2 = dump(fd1) 產生的新的fd2所指向的文件表項和fd1指向的文件表項是相同的;

進程中分別調用:fd1 = open("data.in",O_RDWR); fd2 = open("data.in",O_RDWR); 那么fd1和fd2指向的文件表項是不同的。

原文鏈接:http://blog.csdn.net/ordeder/article/details/21716639 

服務器端預先創建子進程(work)同時監聽服務端口和驚群現象

http://blog.csdn.net/ordeder/article/details/21716639


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM