linux服務端程序都需要提供7 * 24不間斷的服務,,如何保證工作進程一直不退出或者不被kill掉,,常見的方法就是啟動一個守護進程來檢測工作進程的狀態(tài),如果發(fā)現(xiàn)工作進程退出,,就再fork一個出來,。一般的實現(xiàn)見下面一段代碼:
- // 守護進程(父進程)
- int status;
- for ( ; ; ) {
- if ( 0 == ( pid = fork()) ) {
- // 工作進程(子進程)
- run();
- }
- waitpid(-1, &status, 0);
- if (WIFEXITED(status))
- if (WEXITSTATUS(status) == 0)
- exit(0);
- if (WIFSIGNALED(status)) {
- switch (WTERMSIG(status)) {
- case SIGKILL:
- exit(0);
- break;
- case SIGINT:
- case SIGTERM:
- exit(1);
- default:
- break;
- }
- }
- }
守護進程fork出工作進程之后,就阻塞在waitpid系統(tǒng)調(diào)用,,等待工作進程的退出,,waitpid返回之后,根據(jù)status選擇繼續(xù)fork工作進程還是退出守護進程,。status為int型變量,,但只用到了低16位(見struct wait),0-6位表示使子進程退出的信號(可以通過 $kill -l 查看信號的值),,8-15位表示子進程退出時的返回碼(exit或者return的值),。
- struct wait{
- # if __BYTE_ORDER == __LITTLE_ENDIAN
- unsigned int __w_termsig:7; /* Terminating signal. */
- unsigned int __w_coredump:1; /* Set if dumped core. */
- unsigned int __w_retcode:8; /* Return code if exited normally. */
- unsigned int:16;
- # endif /* Little endian. */
- };
判斷status的狀態(tài)可以通過下面的宏完成:
- WIFEXITED(status) //子進程調(diào)用exit()或者從main return退出時為true,;
- WEXITSTATUS(status) //在WIFEXITED為true時,表示exit()或return的返回碼,;
- WIFSIGNALED(status) //子進程被信號終止時為true,;
- WTERMSIG(status) //在WIFSIGNALED為true時,表示終止子進程信號的值,;
宏的定義如下:
- /* If WIFEXITED(STATUS), the low-order 8 bits of the status. */
- #define __WEXITSTATUS(status) (((status) & 0xff00) >> 8)
- /* If WIFSIGNALED(STATUS), the terminating signal. */
- #define __WTERMSIG(status) ((status) & 0x7f)
- /* Nonzero if STATUS indicates normal termination. */
- #define __WIFEXITED(status) (__WTERMSIG(status) == 0)
- /* Nonzero if STATUS indicates termination by a signal. */
- #define __WIFSIGNALED(status) \
- (((signed char) (((status) & 0x7f) + 1) >> 1) > 0)
所以根據(jù)waitpid返回的status,,守護進程可以清楚地知道工作進程的死因,但上面的程序有兩個問題:
1. 對SIGKILL, SIGINT, SIGTERM信號,,守護進程直接退出了,,沒有fork工作進程出來;
2. 守護進程被kill掉了,,工作進程就只能裸奔了,。
對于第一個問題:SIGKILL有可能是人為($kill -9 pid)發(fā)出的,也有可能是工作進程占用內(nèi)存過多,,被OOM掉了(關(guān)于OOM參見 這里),,都不是我們想要的結(jié)果,所以工作進程被SIGKILL掉,,守護進程一定要將其重啟,。SIGINT是 CTRL+C 發(fā)出的(非daemon模式下),SIGTERM是 $killall servicename(或者 $kill pid)發(fā)出的,,這兩個信號都是在結(jié)束進程的時候用到,,這個時候工作進程應該捕獲被處理這兩個信號,正常地退出(return 0;),。而守護進程只有在工作進程正常退出的情況下才完成自己的使命,,否則(工作進程被其他信號結(jié)束掉,如SIGABRT, SIGKILL, SIGSEGV)重啟工作進程,。所以守護進程和工作進程的實現(xiàn)應該是下面的代碼邏輯:
- // 守護進程(父進程)
- int status;
- for ( ; ; ) {
- if ( 0 == ( pid = fork()) ) {
- // 工作進程(子進程)
- run();
- //信號處理函數(shù)signal_handler
- if (sig == SIGTERM || sig == SIGINT) {
- destroy();
- return 0;
- }
- }
- waitpid(-1, &status, 0);
- if (WIFEXITED(status) && (WEXITSTATUS(status) == 0)) exit(0);
- }
對于第二個問題:守護進程的監(jiān)控可以用daemontools工具集中的supervise來監(jiān)控,,也可以自己實現(xiàn),但是不能只是通過另外一個應用程序去做,,因為做守護的進程自身也需要被守護,,如此循環(huán)解決不了問題。這個時候就要借助于linux系統(tǒng)的init進程了,,因為init進程是不能被信號kill掉的(強大到無視SIGKILL),。
- 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中添加如下一行:
- SV1:23:respawn:/usr/local/bin/svscanboot
每行用“:”分隔開為4個部分:
id - 該行的標識,,自定義的名稱SV1。
runlevels - 該行為應該發(fā)生的運行級的列表,,這里在level 2和level 3下運行,。
action - 應發(fā)生的行為,respawn表示init應該監(jiān)視這個進程,即使其結(jié)束后也應該被重新啟動,。
process - 應由init啟動的程序的路徑,。
修改完成后,可以通過$kill -HUP 1 來立刻生效,。
解決了上面兩個問題,,守護進程和工作進程提供7 * 24小時的運行才是有保證的。Have fun!
|