創建守護進程爲什麼fork兩次

      守護進程(daemon進程)是後臺守護進程,有時候也叫精靈進程(agent).linux 下server都是daemon進程。

特點是:

  1)其父進程是一號進程,通常以d結尾

  2)在後臺運行,獨立於終端,週期性的以某種任務或等待處理某些發生的事

  3)自成進程組,自成會話,不受登陸註銷等影響

  4)一般是孤兒進程


  daemon函數存在的原因是因爲控制終端由於某些原因(如斷開終端鏈接)會發送一些信號的原因。而接收進程處理這些信號缺省動作會讓進程退出。這些信號會由於終端上敲一些特殊按鍵而產生。
       創建守護進程最關鍵的⼀一步是調⽤用setsid函數創建⼀一個新的Session,併成爲Session Leader。
該函數調⽤用成功時返回新創建的Session的id(其實也就是當前進程的id),出錯返回-1。注意,調 ⽤用這個函數之前,當前進程不允許是進程組的Leader,否則該函數返回-1。要保證當前進程不 是進 程組的Leader也很容易,只要先fork再調⽤用setsid就⾏行了。fork創建的⼦子進程和⽗父進程在同 ⼀一個進 程組中,進程組的Leader必然是該組的第⼀一個進程,所以⼦子進程不可能是該組的第⼀一個 進程,在⼦子 進程中調⽤用setsid就不會有問題了。

成功調⽤用該函數的結果是:

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

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

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

創建守護進程

     1. 調⽤用umask將⽂文件模式創建屏蔽字設置爲0.

     2. 調⽤用fork,⽗父進程退出(exit)。原因:1)如果該守護進程是作爲⼀一條簡單的shell命令 啓動的,那麼⽗父進程終⽌止使得shell認爲該命令已經執⾏行完畢。2)保證⼦子進程不是⼀一個 進程組的組長進程。

    3. 調⽤用setsid創建⼀一個新會話。setsid會導致:1)調⽤用進程成爲新會話的⾸首進程。 2)調⽤用 進程成爲⼀一個進程組的組長進程 。3)調⽤用進程沒有控制終端。(再次fork⼀一次,保證 daemon進程,之後不會打開tty設備)

   4. 將當前⼯工作⽬目錄更改爲根⽬目錄。

   5. 關閉不在需要的⽂文件描述符。

   6. 其他:忽略SIGCHLD信號


  貼一個daemon函數常見的實現:

int daemon(void)
{
    pid_t pid = fork();

    if( pid != 0 ) exit(0);//parent

    //first children
    if(setsid() == -1)
    {
       printf("setsid failed\n");
       assert(0);
       exit(-1);
    }

    umask(0);


    pid = fork();

    if( pid != 0) exit(0);
  
    //second children 
    chdir ("/");

    for (int i = 0; i < 3; i++)
    {
        close (i);
    }


    int stdfd = open ("/dev/null", O_RDWR);
    dup2(stdfd, STDOUT_FILENO);
    dup2(stdfd, STDERR_FILENO);

    return 0;
}

1 、第一次fork的作用是讓shell 認爲本條命令 已經終止,不用掛在終端輸入上。還有一個作用是爲後面setsid服務。setsid的調用者不能是進程組組長(group leader). 此時父進程是進程組組長。
    
    2 、setsid() 是本函數最重要的一個調用。它完成了daemon函數想要做的大部分事情。調用完整個函數。子進程是會話組長(sid==pid),也是進程組組長(pgid == pid),並且脫離了原來控制終端。到了這一步,基本上不管控制終端如何怎麼樣。新的進程都不會收到那些信號。

    3  、經過前面2個步驟,基本想要做的都做了。第2次fork不是必須的。也看到很多開源服務沒有fork第二次。fork第二次主要目的是。防止進程再次打開一個控制終端。因爲打開一個控制終端的前臺條件是該進程必須是會話組長。再fork一次,子進程ID != sid(sid是進程父進程的sid)。所以也無法打開新的控制終端。

    daemon目的就是防止終端產生的一些信號讓進程退出。上面函數並沒有直接調用signal函數去處理它。而是間接通過fork和setsid函數使用更少代碼優雅處理。而被有些人誤以爲是僵死進程的原因需要這樣處理。

    當然,也有很多程序不是像上面函數那樣去實現。而是直接通過忽略信號方式處理。這樣其實也不錯,因爲這些信號很少會有用到的價值。直接忽略基本上不存在誤殺的情況。反正達到最終目的就可以。條條大路通羅馬。

   下面羅列一下控制終端會產生哪些信號。程序中只要處理好這些信號,同樣能達到上面函數實現的目的。

   //後臺進程讀取/寫入終端輸入產生下面兩個信號,或者控制終端不存在情況讀取和寫入會產生
   signal(SIGTTOU, SIG_IGN);
   signal(SIGTTIN, SIG_IGN);

   //按CTRL-C ,CTRL-\ CTRL-Z會向前臺進程組發送下面這些信號
   signal(SIGINT,  SIG_IGN );
   signal(SIGQUIT, SIG_IGN );
   signal(SIGTSTP, SIG_IGN );
   
   //終端斷開,會給會話組長或孤兒進程組所有成員發送下面信號
   signal(SIGHUP,  SIG_IGN );

   還有有些信號也可以由終端shell產生,需要關注
   signal(SIGCONT, SIG_IGN );
   signal(SIGSTOP, SIG_IGN );


  上面這些信號,應該有些程序缺省處理(SIG_DFL)本身動作就是忽略(SIG_IGN),不是退出進程。不過按照上面寫也不會造成什麼問題。
發佈了66 篇原創文章 · 獲贊 17 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章