歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

《APUE》:單實例守護進程的實現

《Unix環境高級編程》這本書附帶了許多短小精美的小程序,我在閱讀此書的時候,將書上的代碼按照自己的理解重寫了一遍(大部分是抄書上的),加深一下自己的理解(純看書太困了,呵呵)。此例子在Ubuntu 10.04上測試通過。

相關鏈接

  • 《UNIX環境高級編程》(第二版)apue.h的錯誤 http://www.linuxidc.com/Linux/2011-04/34662.htm
  • Unix環境高級編程 源代碼地址 http://www.linuxidc.com/Linux/2011-04/34826.htm

程序簡介:這個DEMO是按照UNIX守護進程的編程規則實現的一個單實例的守護程序。

  1. //《APUE》程序13-1:初始化一個守護進程   
  2. //《APUE》程序13-2:保證只運行某個守護進程的一個副本   
  3. //《APUE》程序14-5:在文件整體加鎖   
  4.   
  5. #include <stdio.h>   
  6. #include <stdlib.h>   
  7. #include <string.h>   
  8. #include <unistd.h>   
  9. #include <fcntl.h>   
  10. #include <time.h>   
  11. #include <signal.h>   
  12. #include <errno.h>   
  13. #include <sys/resource.h>   
  14. #include <sys/syslog.h>   
  15. #include <sys/file.h>   
  16. #include <sys/stat.h>   
  17.   
  18. //創建鎖文件的路徑   
  19. #define LOCKFILE "/var/run/daemon.pid"   
  20. //鎖文件的打開模式   
  21. #define LOCKMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)   
  22.   
  23. //輸出錯誤信息並退出     
  24. void error_quit(const char *str)    
  25. {    
  26.     fprintf(stderr, "%s\n", str);    
  27.     exit(1);    
  28. }   
  29.   
  30. //對文件fd加上記錄鎖   
  31. int lockfile(int fd)  
  32. {  
  33.     struct flock fl;  
  34.     fl.l_type = F_WRLCK;  
  35.     fl.l_start = 0;  
  36.     fl.l_whence = SEEK_SET;  
  37.     fl.l_len = 0;  
  38.     return fcntl(fd, F_SETLK, &fl);  
  39. }  
  40.   
  41. //若程序已經運行,則返回1,否則返回0   
  42. int already_running(void)  
  43. {  
  44.     int fd;  
  45.     char buf[16];  
  46.   
  47.     //打開放置記錄鎖的文件   
  48.     fd = open(LOCKFILE, O_RDWR|O_CREAT, LOCKMODE);  
  49.     if( fd < 0 )  
  50.     {  
  51.         syslog(LOG_ERR, "can't open %s: %s", LOCKFILE, strerror(errno));  
  52.         exit(1);  
  53.     }  
  54.     //試圖對文件fd加鎖,   
  55.     //如果加鎖失敗的話   
  56.     if( lockfile(fd) < 0 )  
  57.     {  
  58.         //如果是因為權限不夠或資源暫時不可用,則返回1   
  59.         if( EACCES == errno ||  
  60.             EAGAIN == errno )  
  61.         {  
  62.             close(fd);  
  63.             return 1;  
  64.         }  
  65.         //否則,程序出錯,寫入一條錯誤記錄後直接退出   
  66.         syslog(LOG_ERR, "can't lock %s: %s", LOCKFILE, strerror(errno));  
  67.         exit(1);  
  68.     }  
  69.   
  70.     //先將文件fd清空,然後再向其中寫入當前的進程號   
  71.     ftruncate(fd, 0);  
  72.     sprintf(buf, "%ld", (long)getpid());  
  73.     write(fd, buf, strlen(buf)+1);  
  74.     return 0;  
  75. }  
  76.   
  77. //將一個進程變為守護進程   
  78. void daemonize(void)  
  79. {  
  80.     int i, fd0, fd1, fd2;  
  81.     pid_t pid;  
  82.     struct rlimit rl;  
  83.     struct sigaction sa;  
  84.   
  85.     //見注解1   
  86.     umask(0);  
  87.   
  88.     //獲取最大的文件描述號   
  89.     int temp;  
  90.     temp = getrlimit(RLIMIT_NOFILE, &rl);  
  91.     if( temp < 0 )  
  92.         error_quit("can't get file limit");  
  93.   
  94.     //見注解2,   
  95.     pid = fork();  
  96.     if( pid < 0 )  
  97.         error_quit("can't fork");  
  98.     else if(pid != 0)  
  99.         exit(0);  
  100.   
  101.     //見注解3   
  102.     setsid();  
  103.     sa.sa_handler = SIG_IGN;  
  104.     sigemptyset(&sa.sa_mask);  
  105.     sa.sa_flags = 0;  
  106.     temp = sigaction(SIGHUP, &sa, NULL);  
  107.     if( temp < 0 )  
  108.         error_quit("can't ignore SIGHUP");  
  109.   
  110.     ////確保子進程不會有機會分配到一個控制終端   
  111.     pid = fork();  
  112.     if( pid < 0 )  
  113.         error_quit("can't fork");  
  114.     else if(pid != 0)  
  115.         exit(0);  
  116.   
  117.     //見注解4   
  118.     temp = chdir("/");  
  119.     if( temp < 0 )  
  120.         error_quit("can't change directoy to /");  
  121.   
  122.     //見注解5   
  123.     if( rl.rlim_max == RLIM_INFINITY )  
  124.         rl.rlim_max = 1024;  
  125.     for(i=0; i<rl.rlim_max; i++)  
  126.         close(i);  
  127.   
  128.     //見注解6   
  129.     fd0 = open("/dev/null", O_RDWR);  
  130.     fd1 = dup(0);  
  131.     fd2 = dup(0);  
  132.   
  133.     if( fd0 != 0 ||  
  134.         fd1 != 1 ||  
  135.         fd2 != 2 )  
  136.     {  
  137.         syslog(LOG_ERR, "unexpected file descriptors %d %d %d",  
  138.             fd0, fd1, fd2);  
  139.         exit(1);  
  140.     }  
  141. }  
  142.   
  143. //該主函數是我原創的,呵呵   
  144. int main(void)  
  145. {  
  146.     //打開系統的日志文件   
  147.     openlog("my test log: ", LOG_CONS, LOG_DAEMON);  
  148.     daemonize();  
  149.   
  150.     //如果程序已經運行,則向記錄文件中寫入一句話,然後退出   
  151.     if( already_running() )  
  152.     {  
  153.         syslog(LOG_ERR, "daemon alread running");  
  154.         closelog();  
  155.         return 1;  
  156.     }  
  157.   
  158.     //向日志文件寫入程序的開始(當前)時間,   
  159.     //過100秒後,再向記錄文件寫入結束時間,然後結束程序   
  160.     time_t tt = time(0);  
  161.     syslog(LOG_INFO, "the log program start at: %s",   
  162.         asctime(localtime(&tt)) );  
  163.     sleep(100);  
  164.     //pause()   
  165.     tt = time(0);  
  166.     syslog(LOG_INFO, "the log program end at: %s",   
  167.         asctime(localtime(&tt)) );  
  168.   
  169.     //關閉日志文件   
  170.     //雖然不關也沒事,但為了和openlog配對,還是將它寫上去吧   
  171.     closelog();  
  172.     return 0;  
  173. }  

運行示例(紅色字體的為輸入):

www.linuxidc.com @ubuntu:~/code$ gcc temp.c -o temp
www.linuxidc.com @ubuntu:~/code$ sudo ./temp
www.linuxidc.com @ubuntu:~/code$ sudo ./temp
www.linuxidc.com @ubuntu:~/code$ ps axj | grep temp
    1  2648  2647  2647 ?           -1 S        0   0:00 ./temp
 2127  2673  2672  2127 pts/0     2672 S+    1000   0:00 grep --color=auto temp

#兩分鐘後,再打開日志文件,查看一下程序的日志
www.linuxidc.com @ubuntu:~/code$ tail -f /var/log/syslog
Sep 24 07:53:58 ubuntu my test log: : the log program start at: Mon Sep 24 07:53:58 2012
Sep 24 07:54:07 ubuntu my test log: : daemon alread running
Sep 24 07:55:38 ubuntu my test log: : the log program end at: Mon Sep 24 07:55:38 2012

注解:守護進程的編程規則
1:首先要調用umask將文件模式創建屏蔽字設置為0.由繼承得來的文件模式創建屏蔽字可能會拒絕設置某些權限。
2:調用fork,然後使父進程退出(exit)。這樣做實現了兩點:第一,如果該守護進程是作為一條簡單shell命令啟動的,那麼父進程終止使得shell認為這條命令已經執行完畢;第二,子進程繼承了父進程的進程組ID,但具有一個新的進程ID,這就保證了子進程不是一個進程組的組長進程。這是setsid調用必要前���條件。
3:調用setsid以創建一個新會話。執行三個操作,(a)成為新會話的首進程,(b)成為一個新進程的組長進程,(c)沒有控制終端。
4:將當前工作目錄更改為根目錄。進程活動時,其工作目錄所在的文件系統不能卸下。一般需要將工作目錄改變到根目錄。對於需要轉儲核心,寫運行日志的進程將工作目錄改變到特定目錄
5:關閉不再需要的文件描述符。進程從創建它的父進程那裡繼承了打開的文件描述符。如不關閉,將會浪費系統資源,造成進程所在的文件系統無法卸下以及引起無法預料的錯誤。
6:重定向0,1,2到/dev/null,使任何一個試圖讀標准輸入,寫標准輸出和標准出錯的程序庫都不會產生任何效果。

Copyright © Linux教程網 All Rights Reserved