1. 程式人生 > >建立守護程序為什麼fork兩次

建立守護程序為什麼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),不是退出程序。不過按照上面寫也不會造成什麼問題。