如何編寫一個可靠的linux守護進程


linux服務端程序都需要提供7 * 24不間斷的服務,如何保證工作進程一直不退出或者不被kill掉,常見的方法就是啓動一個守護進程來檢測工作進程的狀態,如果發現工作進程退出,就再fork一個出來。一般的實現見下面一段代碼:

C代碼  收藏代碼
  1. // 守護進程(父進程)  
  2. int status;  
  3. for ( ; ; )  {  
  4.     if ( 0 == ( pid = fork()) ) {  
  5.         // 工作進程(子進程)  
  6.         run();  
  7.     }  
  8.     waitpid(-1, &status, 0);  
  9.     if (WIFEXITED(status))  
  10.             if (WEXITSTATUS(status) == 0)  
  11.                 exit(0);  
  12.         if (WIFSIGNALED(status)) {  
  13.             switch (WTERMSIG(status)) {  
  14.             case SIGKILL:  
  15.                 exit(0);  
  16.                 break;  
  17.             case SIGINT:  
  18.             case SIGTERM:  
  19.                 exit(1);  
  20.             default:  
  21.                 break;  
  22.             }  
  23.         }  
  24. }  
    守護進程fork出工作進程之後,就阻塞在waitpid系統調用,等待工作進程的退出,waitpid返回之後,根據status選擇繼續fork工作進程還是退出守護進程。status爲int型變量,但只用到了低16位(見struct wait),0-6位表示使子進程退出的信號(可以通過 $kill -l 查看信號的值),8-15位表示子進程退出時的返回碼(exit或者return的值)。
C代碼  收藏代碼
  1. struct wait{  
  2. # if    __BYTE_ORDER == __LITTLE_ENDIAN  
  3.     unsigned int __w_termsig:7; /* Terminating signal.  */  
  4.     unsigned int __w_coredump:1; /* Set if dumped core.  */  
  5.     unsigned int __w_retcode:8; /* Return code if exited normally.  */  
  6.     unsigned int:16;  
  7. # endif             /* Little endian.  */  
  8. };  
     判斷status的狀態可以通過下面的宏完成:
C代碼  收藏代碼
  1. WIFEXITED(status)               //子進程調用exit()或者從main return退出時爲true;  
  2. WEXITSTATUS(status)        //在WIFEXITED爲true時,表示exit()或return的返回碼;  
  3. WIFSIGNALED(status)        //子進程被信號終止時爲true;  
  4. WTERMSIG(status)            //在WIFSIGNALED爲true時,表示終止子進程信號的值;  
    宏的定義如下:
C代碼  收藏代碼
  1. /* If WIFEXITED(STATUS), the low-order 8 bits of the status.  */  
  2. #define __WEXITSTATUS(status)   (((status) & 0xff00) >> 8)  
  3. /* If WIFSIGNALED(STATUS), the terminating signal.  */  
  4. #define __WTERMSIG(status)  ((status) & 0x7f)  
  5. /* Nonzero if STATUS indicates normal termination.  */  
  6. #define __WIFEXITED(status) (__WTERMSIG(status) == 0)  
  7. /* Nonzero if STATUS indicates termination by a signal.  */  
  8. #define __WIFSIGNALED(status) \  
  9.   (((signed char) (((status) & 0x7f) + 1) >> 1) > 0)  
    所以根據waitpid返回的status,守護進程可以清楚地知道工作進程的死因,但上面的程序有兩個問題:
    1. 對SIGKILL, SIGINT, SIGTERM信號,守護進程直接退出了,沒有fork工作進程出來;
    2. 守護進程被kill掉了,工作進程就只能裸奔了。
    對於第一個問題:SIGKILL有可能是人爲($kill -9 pid)發出的,也有可能是工作進程佔用內存過多,被OOM掉了(關於OOM參見這裏),都不是我們想要的結果,所以工作進程被SIGKILL掉,守護進程一定要將其重啓。SIGINT是 CTRL+C 發出的(非daemon模式下),SIGTERM是 $killall servicename(或者 $kill pid)發出的,這兩個信號都是在結束進程的時候用到,這個時候工作進程應該捕獲被處理這兩個信號,正常地退出(return 0;)。而守護進程只有在工作進程正常退出的情況下才完成自己的使命,否則(工作進程被其他信號結束掉,如SIGABRT, SIGKILL, SIGSEGV)重啓工作進程。所以守護進程和工作進程的實現應該是下面的代碼邏輯:
C代碼  收藏代碼
  1. // 守護進程(父進程)  
  2. int status;  
  3. for ( ; ; )  {  
  4.     if ( 0 == ( pid = fork()) ) {  
  5.         // 工作進程(子進程)  
  6.         run();  
  7.         //信號處理函數signal_handler  
  8.         if (sig == SIGTERM || sig == SIGINT) {  
  9.            destroy();  
  10.            return 0;  
  11.         }  
  12.     }  
  13.     waitpid(-1, &status, 0);  
  14.     if (WIFEXITED(status) && (WEXITSTATUS(status) == 0)) exit(0);  
  15. }  
     對於第二個問題:守護進程的監控可以用daemontools工具集中的supervise來監控,也可以自己實現,但是不能只是通過另外一個應用程序去做,因爲做守護的進程自身也需要被守護,如此循環解決不了問題。這個時候就要藉助於linux系統的init進程了,因爲init進程是不能被信號kill掉的(強大到無視SIGKILL)。
C代碼  收藏代碼
  1. The  only  signals  that can be sent task number one, the init process, are those for which init has explicitly installed signal handlers.  This is done to assure the system is not brought down accidentally.  
    所以讓init進程來守護我們的應用程序是最可靠的,看看supervise的作法:
    在/etc/inittab中添加如下一行:
C代碼  收藏代碼
  1. SV1:23:respawn:/usr/local/bin/svscanboot  
    每行用“:”分隔開爲4個部分:
        id - 該行的標識,自定義的名稱SV1。
        runlevels - 該行爲應該發生的運行級的列表,這裏在level 2和level 3下運行。
        action - 應發生的行爲,respawn表示init應該監視這個進程,即使其結束後也應該被重新啓動。
        process - 應由init啓動的程序的路徑。
    修改完成後,可以通過$kill -HUP 1 來立刻生效。
    解決了上面兩個問題,守護進程和工作進程提供7 * 24小時的運行纔是有保證的。Have fun!   

原文地址:http://nil-zhang.iteye.com/blog/1806356


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章