"守護進程"(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進程)。守護進程是脫離於控制終端而且在後臺週期性地執行某種任務或等待處理某些事件的進程,脫離終端是爲了不進程在執行過程當中的信息在任何終端上顯示而且進程也不會被任何終端所產生的中斷信息所終止。html

 

建立守護進程的通常步驟linux

 

(1) 建立子進程,退出父進程數組

爲了脫離控制終端須要退出父進程,以後的工做都由子進程完成。在Linux中父進程先於子進程退出會形成子進程成爲孤兒進程,而每當系統發現一個孤兒進程時,就會自動由1號進程(init)收養它,這樣,原先的子進程就會變成init進程的子進程。服務器

ps –ef | grep ProcName          經過PID/PPID查看進程的父子關係session

 

(2) 在子進程中建立新的會話函數

使用系統函數setsid來完成。測試

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

setsid – creates a session and sets theprocess group ID編碼

#include <unistd.h>spa

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、一、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

相關文章
相關標籤/搜索