一、守護進程及其特性
守護進程(Daemon)是運行在后台的一種特殊進程。它獨立於控制終端並且周期性地執行某種任務或等待處理某些發生的事件。守護進程是一種很有用的進程。Linux的大多數服務器就是用守護進程實現的,比如,Internet服務器inetd,Web服務器httpd等。同時,守護進程完成許多系統任務。比如,作業規划進程crond,打印進程lpd等。
其特征如下:
1、后台運行
守護進程最重要的特性是后台運行。在這一點上DOS下的常駐內存程序TSR與之相似。
2、獨立於其運行前的環境
守護進程必須與其運行前的環境隔離開來。這些環境包括未關閉的文件描述符,控制終端,會話和進程組,工作目錄以及文件創建掩模等。這些環境通常是守護進程從執行它的父進程(特別是shell)中繼承下來的。
3、啟動方式
守護進程的啟動方式有其特殊之處。它可以在Linux系統啟動時從啟動腳本/etc/rc.d中啟動,可以由作業規划進程crond啟動,還可以由用戶終端(通常是shell)執行。
除了以上這些特征外,守護進程與普通進程基本上沒有什么區別。實際上,編寫守護進程也就是按照上述的守護進程特征把一個普通進程改造成為守護進程。
二、 守護進程編程要點
守護進程的編程本身並不復雜,復雜的是各種版本的Unix的實現機制不盡相同,造成不同Unix環境下守護進程的編程規則並不一致。如果照搬某些書上的規則(特別是BSD4.3和低版本的System V)到Linux會出現錯誤的。所幸的是守護進程的編程原則都是一樣的,區別在於具體的實現細節。這個原則就是要滿足守護進程的特征。同時,Linux是基於Syetem V的SVR4並遵循Posix標准,實現起來比BSD4更方便。守護進程編程要點總結如下:
1、屏蔽控制終端操作信號
屏蔽一些有關控制終端操作的信號。防止在守護進程沒有正常運轉起來時,控制終端受到干擾退出或掛起。方法如下:
- MySignal(SIGTTOU, SIG_IGN);
- MySignal(SIGTTIN, SIG_IGN);
- MySignal(SIGTSTP, SIG_IGN);
- MySignal(SIGHUP, SIG_IGN);
注意,這里調用的是我自定義的為指定信號安裝處理函數MySignal,而不是signal。因為是signal的語義與實現有關,所以這里改用用sigaction實現的MySignal。MySignal具體定義見后面編程實例。
說明:
1) SIGHUP
本信號在用戶終端連接(正常或非正常)結束時發出, 通常是在終端的控制進程結束時, 通知同一session內的各個作業, 這時它們與控制終端不再關聯。
登錄Linux時,系統會分配給登錄用戶一個終端(Session)。在這個終端運行的所有程序,包括前台進程組和后台進程組,一般都屬於這個Session。當用戶退出Linux登錄時,前台進程組和后台有對終端輸出的進程將會收到SIGHUP信號。這個信號的默認操作為終止進程,因此前台進程組和后台有終端輸出的進程就會中止。不過可以捕獲這個信號,比如wget能捕獲SIGHUP信號,並忽略它,這樣就算退出了Linux登錄,wget也能繼續下載。
此外,對於與終端脫離關系的守護進程,這個信號用於通知它重新讀取配置文件。
2) SIGTSTP
停止進程的運行, 但該信號可以被處理和忽略。用戶鍵入SUSP字符時(通常是Ctrl-Z)發出這個信號
3) SIGTTIN
當后台作業要從用戶終端讀數據時, 該作業中的所有進程會收到SIGTTIN信號。缺省時這些進程會停止執行
4) SIGTTOU
類似於SIGTTIN, 當后台作業要向用戶終端寫數據(或修改終端模式)時收到
2、處理SIGCHLD信號
處理SIGCHLD(子進程結束)信號並不是必須的。但對於某些進程,特別是服務器進程往往在請求到來時生成子進程來處理請求。如果父進程不等待子進程結束,子進程將成為僵死進程(zombie)從而占用系統資源。如果父進程等待子進程結束,將增加父進程的負擔,影響服務器進程的並發性能。
在Linux下,可以簡單地將SIGCHLD信號的操作設SIG_IGN(父進程對子進程結束狀態不感興趣,忽略子進程結束信號SIG_IGN)。
- MySignal(SIGCHLD,SIG_IGN);
這樣,內核在子進程結束時不會產生僵屍進程。這一點與BSD4不同,BSD4下必須顯式等待子進程結束才能釋放僵屍進程。
另一種避免僵死進程的方法是:fork兩次,父進程fork一個子進程,然后繼續工作,子進程fork一個孫進程后退出,那么孫進程被init接管,孫進程結束后,init會回收。不過子進程的回收還要父進程來做(參考:linux僵死進程的產生與避免)。
3、后台運行
為避免掛起控制終端,將Daemon放入后台執行。方法:在程序中調用fork使父進程終止,讓Daemon在子進程中后台執行。
- pid = fork();
- if (pid > 0) //父進程終止;子進程繼續運行
- exit(0);
4、脫離控制終端,登錄會話和進程組
有必要先介紹一下Linux中的進程與控制終端、登錄會話和進程組之間的關系:進程屬於一個進程組,進程組號(GID)就是進程組長的進程號(PID)。登錄會話可以包含多個進程組。這些進程組共享一個控制終端。這個控制終端通常是創建進程的登錄終端。
控制終端、登錄會話和進程組通常是從父進程繼承下來的。我們的目的就是要擺脫它們,使之不受它們的影響。方法是:第1點的基礎上,調用setsid()使進程成為會話組長:
- setsid();
說明:當進程是會話組長時setsid()調用失敗,但第1點已經保證進程不是會話組長。setsid()調用成功后,進程成為新的會話組長和新的進程組長,並與原來的登錄會話和進程組脫離。由於會話過程對控制終端的獨占性,進程同時與控制終端脫離。
5、禁止進程重新打開控制終端
現在,進程已經成為無終端的會話組長。但它可以重新申請打開一個控制終端。可以通過使進程不再成為會話組長來禁止進程重新打開控制終端:
- pid = fork();
- if (pid > 0) //子進程終止;孫進程繼續運行,孫進程不在是會話組長
- exit(0);
6、重設文件創建掩模
進程從創建它的父進程那里繼承了文件創建掩模,這可能會修改了守護進程所創建的文件的存取位。為防止這一點,按如下方法將文件創建掩模清除:
- umask(0);
7、關閉打開的文件描述符
進程從創建它的父進程那里繼承了打開的文件描述符。如不關閉,將會浪費系統資源,造成進程所在的文件系統無法卸下以及引起無法預料的錯誤。按如下方法關閉它們:
- fdTableSize = getdtablesize();
- for (fd=0; fd<fdTableSize; fd++)
- close(fd);
8、改變當前工作目錄
進程活動時,其工作目錄所在的文件系統是不能卸下的。一般需要將工作目錄改變到根目錄/。對於需要寫運行日志的進程將工作目錄改變到特定目錄如/tmp。按如下方法切換工作目錄
- chdir("/");
9、出錯記錄和調試信息
因為守護進程已脫離了控制終端,並且關閉了所有文件描述符,所以不能簡單地把出錯記錄或調試信息寫到標准輸出或標准錯誤輸出中。所幸的是,
在Linux/Unix下有個syslogd守護進程,向用戶提供了syslog()系統調用。任何程序都可以通過syslog記錄信息(信息一般保存在/var/log/messages中)。 但在有些嵌入式liunx系統中,可能沒有syslogd守護進程,因此,無法使用syslog()來記錄信息。那就只能重定向標准輸入輸出了。重定向標准輸入輸出,需要在第4點關閉打開的文件描述符時應繞過標准輸入輸出,修改如下:
- RedirectStdIO("/dev/null", LOG_FILE, LOG_FILE); //重定向標准輸入輸出
- fdTableSize = getdtablesize();
- for (fd=3; fd<fdTableSize; fd++)
- close(fd);
三、單實例守護進程(Single-Instance Daemons)
單實例守護進程也就是在任一時刻只運行守護進程的一個實例。這是為了避免運行多個實例時而引起的錯誤。例如,如果同時有多個cron守護進程實例在運行,那么每個實例都可能試圖開始某個預定的操作,於是導致該操作被重復執行,這很可能會引起錯誤。
我們可以通過記錄鎖機制來保證任何時候系統中只有一個該守護進程的實例在運行。具體方法是:
讓守護進程創建一個文件並在整個文件上加一把獨占性寫鎖(我們把文件稱為守護進程的鎖文件);根據記錄鎖機制,只允許創建一把這樣的寫鎖,所以在此之后如試圖再創建一把這樣的寫鎖將會失敗,以此向后續的守護進程實例指明當前已有一個實例在運行。
在擁有這把寫鎖的守護進程實例終止時,這把寫鎖將被自動刪除,無需我們自己來清理它。可以用下面的AlreadyRunning函數來判斷系統中是否已有該守護進程的實例在運行,返回值true表示有該守護進程實例在運行,否則無。
- bool AlreadyRunning()
- {
- int fdLockFile;
- char szPid[32];
- struct flock fl;
- /* 打開鎖文件 */
- fdLockFile = open(LOCK_FILE, O_RDWR | O_CREAT, LOCK_FILE_MODE);
- if (fdLockFile < 0)
- {
- ErrorLog("AlreadyRunning open");
- exit(EXIT_FAILURE);
- }
- /*對整個鎖文件加寫鎖 */
- fl.l_type = F_WRLCK; //記錄鎖類型:獨占性寫鎖
- fl.l_whence = SEEK_SET; //加鎖區域起點:距文件開始處l_start個字節
- fl.l_start = 0;
- fl.l_len = 0; //加鎖區域終點:0表示加鎖區域自起點開始直至文件最大可能偏移量為止,不管寫入多少字節在文件末尾,都屬於加鎖范圍
- if (fcntl(fdLockFile, F_SETLK, &fl) < 0)
- {
- if (EACCES == errno || EAGAIN == errno) //系統中已有該守護進程的實例在運行
- {
- close(fdLockFile);
- return true;
- }
- ErrorLog("AlreadyRunning fcntl");
- exit(EXIT_FAILURE);
- }
- /* 清空鎖文件,然后將當前守護進程pid寫入鎖文件 */
- ftruncate(fdLockFile, 0);
- sprintf(szPid, "%ld", (long)getpid());
- write(fdLockFile, szPid, strlen(szPid) + 1);
- return false;
- }
四、守護進程慣例
1、
守護進程的鎖文件通常保存在/var/run/目錄中(守護進程可能需要root用戶權限才能在改目錄中創建文件),
並以name.pid命名,name為該守護進程或服務的名稱。
2、
守護進程的配置文件通常保存在/etc/目錄中(守護進程可能需要root用戶權限才能在改目錄中創建文件),
並以name.conf命名,name為該守護進程或服務的名稱。通常,守護進程在啟動時讀取其配置文件,但在此之后就不再查看它了。如果我們修改了配置文件,就必須重啟守護進程,才能使配置文件生效。為了避免此麻煩,
有些守護進程捕獲SIGHUP信號,當接收到該信號時,重新讀取配置文件(因為守護進程已與控制終端脫離關系,控制終端SIGHUP信號對其已無用,所以重復利用它來作為重新讀取配置文件信號)。
3、守護進程可用命令行啟動,但它們通常由某一系統初始化腳本(/etc/rc*或/etc/init.d/*)啟動。如果守護進程終止時,應該自動重新啟動它,我們可以在/etc/inittab中為該守護進程包括_respawn;這樣,守護進程終止時init會自動重新啟動該守護進程。
五、守護進程編程實例
守護進程實例先通過InitDaemon函數將普通進程改造成守護進程。然后,判斷是否已有該守護進程的實例在運行,若有則退出運行;若無則先阻塞SIGHUP和SIGUSR1信號,然后為SIGHUP和SIGUSR1信號分別安裝了信號處理函數,SIGHUP用於通知守護進程重新讀取配置文件;SIGUSR1用於通知守護進程執行服務,也就是用echo打印從配置文件讀到的信息。最后,進入一個死循環(守護進程主體):通過sigsuspend掛起守護進程等待信號發生,然后重新讀取配置文件或執行服務,再掛起守護進程等待信號發生……。該實例通過sigprocmask、sigsuspend和sigaction的配合使用,避免了信號SIGHUP、SIGUSR1的丟失。該實例可以選擇是否通過syslog輸出信息,若用syslog輸出信息,編譯時加上-DUSE_SYSLOG。該實例也可以選擇是否忽略子進程結束信號SIGCHLD,若忽略,編譯時加上-DIGN_SIGCHLD。我們可以通過命令“kill -SIGHUP 守護進程pid”,通知守護進程重新讀取配置文件;通過“kill -SIGUSR1 守護進程pid”,通知守護進程執行服務。
程序清單如下:
1./* 2. * test.c 3. * 4. * Created on: 2011-04-23 5. * Author: lingdxuyan 6. */ 7. 8. 9. #include <stdio.h> /* 標准輸入輸出定義 */ 10. #include <stdlib.h> /* 標准函數庫定義 */ 11. #include <unistd.h> /* Unix 標准函數定義 */ 12. #include <sys/types.h> 13. #include <sys/stat.h> 14. #include <sys/wait.h> 15. #include <fcntl.h> /* 文件控制定義 */ 16. #include <errno.h> /* 錯誤號定義 */ 17. #include <signal.h> /* 信號定義 */ 18. #include <time.h> /* 定時器定義 */ 19. #include <stdarg.h> /* 可變參數定義 */ 20. #include <syslog.h> /* syslog定義 */ 21. #include <string.h> 22. #include <fcntl.h> 23. 24. #define true 1 25. #define false 0 26. 27. typedef unsigned char BYTE; 28. typedef BYTE bool; 29. typedef BYTE byte; 30. 31. #define MAX_BUF_SIZE 1024 32. 33. #define CONFIG_FILE "/etc/daemon.conf" 34. #define LOG_FILE "/tmp/daemon.log" 35. #define LOCK_FILE "/var/run/daemon.pid" 36. #define LOCK_FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) 37. 38. 39.static volatile sig_atomic_t g_nUpdateParameter = 1; 40.static volatile sig_atomic_t g_nServer = 0; 41.//static volatile int g_nUpdateParameter = 1; 42.//static volatile int g_nServer = 0; 43. 44. 45. /* 46. * 功能: 寫日志 47. */ 48. void vWriteLog(int nPriority, const char *fmt, va_list va) 49. { 50. #ifdef USE_SYSLOG 51. vsyslog(LOG_DAEMON | nPriority, fmt, va); 52. #else 53. FILE *stream; 54. if (nPriority & LOG_ERR) 55. stream = stderr; 56. else 57. stream = stdout; 58. vfprintf(stream, fmt, va); 59. fflush(stream); 60. #endif 61. } 62. void WriteLog(int nPriority, const char *fmt, ...) 63. { 64. va_list va; 65. 66. va_start(va, fmt); 67. vWriteLog(nPriority, fmt, va); 68. va_end(va); 69. } 70. 71. /* 72. * 功能: 寫錯誤日志,用法類似perror 73. */ 74. void ErrorLog(const char *str) 75. { 76. WriteLog(LOG_ERR, "%s: %s\n", str, strerror(errno)); 77. } 78. /* 79. * 功能: 寫錯誤日志,用法類似於printf 80. */ 81. void ErrorLogFmt(const char *fmt, ...) 82. { 83. va_list va; 84. 85. va_start(va, fmt); 86. vWriteLog(LOG_ERR, fmt, va); 87. va_end(va); 88. } 89. 90. /* 91. * 功能: 寫日志,用法類似printf 92. */ 93. void InfoLog(const char *fmt, ...) 94. { 95. va_list va; 96. 97. va_start(va, fmt); 98. vWriteLog(LOG_INFO, fmt, va); 99. va_end(va); 100. } 101. 102. /* 103. * 功能: 重定向標准輸入輸出 104. */ 105. void RedirectStdIO(char *szInFile, char *szOutFile, char *szErrFile) 106. { 107. int fd; 108. 109. if (NULL != szInFile) 110. { 111. fd = open(szInFile, O_RDONLY | O_CREAT, 0666); 112. if (fd > 0) 113. { 114. // 標准輸入重定向 115. if (dup2(fd, STDIN_FILENO) < 0) 116. { 117. ErrorLog("RedirectStdIO dup2 in"); 118. exit(EXIT_FAILURE); 119. } 120. 121. close(fd); 122. } 123. else 124. ErrorLogFmt("RedirectStdIO open %s: %s\n", szInFile, strerror(errno)); 125. } 126. 127. if (NULL != szOutFile) 128. { 129. fd = open(szOutFile, O_WRONLY | O_CREAT | O_APPEND /*| O_TRUNC*/, 0666); 130. if (fd > 0) 131. { 132. // 標准輸出重定向 133. if (dup2(fd, STDOUT_FILENO) < 0) 134. { 135. ErrorLog("RedirectStdIO dup2 out"); 136. exit(EXIT_FAILURE); 137. } 138. 139. close(fd); 140. } 141. else 142. ErrorLogFmt("RedirectStdIO open %s: %s\n", szOutFile, strerror(errno)); 143. } 144. 145. if (NULL != szErrFile) 146. { 147. fd = open(szErrFile, O_WRONLY | O_CREAT | O_APPEND /*| O_TRUNC*/, 0666); 148. if (fd > 0) 149. { 150. // 標准錯誤重定向 151. if (dup2(fd, STDERR_FILENO) < 0) 152. { 153. ErrorLog("RedirectIO dup2 error"); 154. exit(EXIT_FAILURE); 155. } 156. 157. close(fd); 158. } 159. else 160. ErrorLogFmt("RedirectStdIO open %s: %s\n", szErrFile, strerror(errno)); 161. } 162. } 163. 164. /* 165. * 功能: 讀取配置文件SIGHUP信號處理函數 166. */ 167. void UpdateHandler(int nSigNum) 168. { 169. g_nUpdateParameter = 1; 170. } 171. 172. /* 173. * 功能: 折行服務SIG_USR1信號處理函數 174. */ 175. void ServerHandler(int nSigNum) 176. { 177. g_nServer = 1; 178. } 179. 180. /* 181. * 功能:確保任一時刻系統中只有一個該守護進程實例在運行 182. */ 183. bool AlreadyRunning() 184. { 185. int fdLockFile; 186. char szPid[32]; 187. struct flock fl; 188. 189. /* 打開鎖文件 */ 190. fdLockFile = open(LOCK_FILE, O_RDWR | O_CREAT, LOCK_FILE_MODE); 191. if (fdLockFile < 0) 192. { 193. ErrorLog("AlreadyRunning open"); 194. exit(EXIT_FAILURE); 195. } 196. 197. /*對整個鎖文件加寫鎖 */ 198. fl.l_type = F_WRLCK; //記錄鎖類型:獨占性寫鎖 199. fl.l_whence = SEEK_SET; //加鎖區域起點:距文件開始處l_start個字節 200. fl.l_start = 0; 201. fl.l_len = 0; //加鎖區域終點:0表示加鎖區域自起點開始直至文件最大可能偏移量為止,不管寫入多少字節在文件末尾,都屬於加鎖范圍 202. if (fcntl(fdLockFile, F_SETLK, &fl) < 0) 203. { 204. if (EACCES == errno || EAGAIN == errno) //系統中已有該守護進程的實例在運行 205. { 206. close(fdLockFile); 207. return true; 208. } 209. 210. ErrorLog("AlreadyRunning fcntl"); 211. exit(EXIT_FAILURE); 212. } 213. 214. /* 清空鎖文件,然后將當前守護進程pid寫入鎖文件 */ 215. ftruncate(fdLockFile, 0); 216. sprintf(szPid, "%ld", (long)getpid()); 217. write(fdLockFile, szPid, strlen(szPid) + 1); 218. 219. return false; 220. } 221. 222. /* 223. * 功能:設置信號nSigNum的處理函數為handler,在調用該信號處理函數前.若handler不為SIG_DEF或SIG_IGN,則系統會將該信號添加到信號屏蔽字中;信號處理函數返回后,信號屏蔽字恢復到原先值.這樣,可保證在處理指定信號時,如果該信號再次發生,那么它會被阻塞到上一信號處理結束為止.不過,要是此時信號發生了多次,在對該信號解除阻塞后,也只會調用一次信號處理函數 224. */ 225. typedef void (*sighandler)(int); 226. sighandler MySignal(int nSigNum, sighandler handler) 227. //void ( *Signal(int nSigNum, void (*handler)(int)) )(int) 228. { 229. struct sigaction saNew, saOld; 230. 231. saNew.sa_handler = handler; 232. sigemptyset(&saNew.sa_mask); 233. if (SIG_DFL != handler && SIG_IGN != handler) 234. sigaddset(&saNew.sa_mask, nSigNum); 235. 236. saNew.sa_flags = 0; 237. if (SIGALRM == nSigNum) 238. { 239. //不重啟該信號中斷的系統調用 240. #ifdef SA_INTERRUPT 241. saNew.sa_flags |= SA_INTERRUPT; 242. #endif 243. } 244. else 245. { 246. //重啟該信號中斷的系統調用 247. #ifdef SA_RESTART 248. saNew.sa_flags |= SA_RESTART; 249. #endif 250. } 251. 252. if (sigaction(nSigNum, &saNew, &saOld) < 0) 253. return SIG_ERR; 254. 255. return saOld.sa_handler; 256. } 257. 258. /* 259. * 功能: 將普通進程改造成守護進程 260. */ 261. void InitDaemon() 262. { 263. pid_t pid; 264. int fd, fdTableSize; 265. 266. /* 1、屏蔽控制終端操作信號 267. */ 268. MySignal(SIGTTOU, SIG_IGN); 269. MySignal(SIGTTIN, SIG_IGN); 270. MySignal(SIGTSTP, SIG_IGN); 271. MySignal(SIGHUP, SIG_IGN); 272. 273. /* 2、忽略子進程結束信號 274. */ 275. #ifdef IGN_SIGCHLD 276. signal(SIGCHLD, SIG_IGN); //忽略子進程結束信號,避免僵死進程產生 277. #endif 278. 279. /* 3、使守護進程后台運行 280. * 父進程直接退出,子進程繼續運行(讓守護進程在子進程中后台運行) 281. */ 282. pid = fork(); 283. if (pid > 0) //父進程終止運行;子進程過繼給init進程,其退出狀態也由init進程處理,避免了產生僵死進程 284. exit(EXIT_SUCCESS); 285. else if (pid < 0) 286. { 287. ErrorLog("InitDaemon fork(parent)"); 288. exit(EXIT_FAILURE); 289. } 290. 291. /* 4、脫離控制終端,登錄會話和進程組 292. * 調用setsid()使子進程成為會話組長 293. */ 294. setsid(); 295. 296. /* 5、禁止進程重新打開控制終端 297. * 通過使守護進程不再成為會話組長來禁止進程重新打開控制終端 298. */ 299. pid = fork(); 300. if (pid > 0) //子進程終止運行;孫進程過繼給init進程,其退出狀態也由init進程處理,避免了產生僵死進程 301. exit(EXIT_SUCCESS); 302. else if (pid < 0) 303. { 304. ErrorLog("InitDaemon fork(child)"); 305. exit(EXIT_FAILURE); 306. } 307. 308. /* 6、重設文件創建掩模 309. */ 310. umask(0); 311. 312. /* 7、關閉打開的文件描述符 313. */ 314. RedirectStdIO("/dev/null", LOG_FILE, LOG_FILE); //重定向標准輸入輸出 315. fdTableSize = getdtablesize(); 316. for (fd=3; fd<fdTableSize; fd++) 317. close(fd); 318. 319. /* 8、改變當前工作目錄 320. */ 321. chdir("/tmp"); 322. } 323. 324. /* 325. * 功能: 讀取守護進程的配置文件,並將獲取到的信息保存在szParameter中 326. */ 327. void ReadConfigFile(char *szParameter) 328. { 329. FILE *stream; 330. int nRet; 331. 332. InfoLog("------ ReadConfigFile ------\n"); 333. stream = fopen(CONFIG_FILE, "r"); 334. if (NULL != stream) 335. { 336. nRet = fread(szParameter, sizeof(char), MAX_BUF_SIZE, stream); 337. if (nRet >= 0) 338. szParameter[nRet - 1] = '\0'; 339. fclose(stream); 340. InfoLog("ReadConfigFile sucesss!\n"); 341. } 342. else 343. ErrorLogFmt("ReadConfigFile fopen %s: %s\n", CONFIG_FILE, strerror(errno)); 344. } 345. 346. /* 347. * 功能: 執行守護進程的服務,也就是將szParameter用echo打印出來 348. */ 349. void Server(char *szParameter) 350. { 351. int nStatus; 352. pid_t pid; 353. 354. InfoLog("------ Server ------\n"); 355. 356. pid = vfork(); //生成子進程 357. #ifdef IGN_SIGCHLD 358. InfoLog("ignore child SIGCHLD signal!\n"); 359. if (0 == pid) //子進程 360. { 361. if (execlp("echo", "echo", szParameter, NULL) < 0) 362. { 363. ErrorLog("Server execlp"); 364. exit(EXIT_FAILURE); 365. } 366. } 367. else if (pid < 0) 368. { 369. ErrorLog("Server vfork(parent)"); 370. } 371. #else 372. if (pid > 0) //父進程 373. { 374. waitpid(pid, &nStatus, 0); //等待子進程結束,否則子進程會成為僵死進程,一直存在,即便子進程已結束執行 375. } 376. else if (0 == pid) //子進程 377. { 378. pid = vfork(); //生成孫進程 379. if (pid > 0) 380. { 381. exit(EXIT_SUCCESS); //子進程退出,孫進程過繼給init進程,其退出狀態也由init進程處理,與原有父進程無關 382. } 383. else if (0 == pid) //孫進程 384. { 385. if (execlp("echo", "echo", szParameter, NULL) < 0) 386. { 387. ErrorLog("Server execlp"); 388. exit(EXIT_FAILURE); 389. } 390. } 391. else 392. { 393. ErrorLog("Server vfork(child)"); 394. } 395. } 396. else 397. { 398. ErrorLog("Server vfork(parent)"); 399. } 400. #endif 401. } 402. 403. int main() 404. { 405. time_t t; 406. sigset_t sigNewMask, sigOldMask; 407. char szParameter[MAX_BUF_SIZE]; 408. 409. //將普通進程改造成守護進程 410. InitDaemon(); 411. if (AlreadyRunning()) //若系統中已有該守護進程的實例在運行,則退出 412. { 413. ErrorLogFmt("Daemon already running!\n"); 414. exit(EXIT_FAILURE); 415. } 416. 417. //阻塞SIGHUP信號和SIGUSR1信號 418. sigemptyset(&sigNewMask); 419. sigaddset(&sigNewMask, SIGHUP); 420. sigaddset(&sigNewMask, SIGUSR1); 421. if (sigprocmask(SIG_BLOCK, &sigNewMask, &sigOldMask) < 0) 422. { 423. ErrorLog("main sigprocmask"); 424. exit(EXIT_FAILURE); 425. } 426. 427. //為SIGHUP信號和SIGUSR1信號添加信號處理函數 428. MySignal(SIGHUP, UpdateHandler); 429. MySignal(SIGUSR1, ServerHandler); 430. 431. t = time(NULL); 432. InfoLog("Daemon %d start at %s", getpid(), ctime(&t)); 433. 434. //讀取守護進程配置文件 435. ReadConfigFile(szParameter); 436. while(1) 437. { 438. sigsuspend(&sigOldMask);//將進程的信號屏蔽字暫時替代為sigOldMask並掛起進程,直到捕捉到一個信號並從其信號處理函數返回,sigsuspend才返回並將信號屏蔽字恢復為調用它之前的值;若捕捉到的是終止進程信號,sigsuspend不返回,進程直接終止 439. if (1 == g_nUpdateParameter) //讀取配置文件 440. { 441. ReadConfigFile(szParameter); 442. g_nUpdateParameter = 0; 443. } 444. 445. if (1 == g_nServer) //執行服務 446. { 447. Server(szParameter); 448. g_nServer = 0; 449. } 450. } 451. 452. return 0; 453. }