歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> 《APUE》:單實例守護進程的實現

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

日期:2017/3/1 10:09:12   编辑:Linux編程

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

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

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