1. 程式人生 > >Linux平臺C++監控子程序程式

Linux平臺C++監控子程序程式

1、問題描述

有時候服務端程式會偶現異常停止,這個時候除了要查詢程式的bug外,還有拉起程式以繼續提供服務。目前為了方便,我們直接用shell指令碼去監控服務程序,這種方法比較簡單,不再贅述。這裡描述另一種方法:服務程序分離為主監控程序和子工作程序,主程序只負責監控子程序(停止、拉起子程序等),子程序負責對外提供服務。

監控子程序的程式,個人認為重點如下:

  1. 子程序收到終止訊號後,完成自身善後工作,然後直接呼叫exit()終止。
  2. 父程序收到子程序終止的訊號後,先呼叫waitpid()回收子程序的資源,避免殭屍程序,再拉起新的子程序。
  3. 父程序收到終止訊號後,首先呼叫kill()通知子程序終止,然後呼叫waitpid()等待子程序終止並回收子程序的資源,最後父程序呼叫exit()終止。

2、程式原始碼

下面直接貼上程式碼(直接g++編譯即可):

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>

static sig_atomic_t  reap;
static
sig_atomic_t terminate; static pid_t master_pid; static pid_t worker_pid; static const char* szfilename = "/tmp/my_test_daemon.txt"; void signal_handler(int signo); void worker(long i); void init_signals(); void start_worker_processes(); bool start_daemon(); typedef struct { int signo; //需要處理的訊號
void (*handler)(int signo); //收到signo訊號後就會回撥handler方法 } signal_t; //指定各種訊號的處理函式 signal_t signals[] = { {SIGHUP, signal_handler}, {SIGQUIT, signal_handler}, {SIGTERM, signal_handler}, {SIGALRM, signal_handler}, {SIGINT, signal_handler}, {SIGIO, signal_handler}, {SIGCHLD, signal_handler}, {SIGSYS, SIG_IGN}, {SIGPIPE, SIG_IGN}, {0, NULL} }; int main(int argc, char** argv) { sigset_t set; //初始化各訊號,給個訊號安裝處理函式 init_signals(); //主程序以daemon方式執行 start_daemon(); //設定訊號掩碼,暫時遮蔽訊號,後續通過sigsuspend主動獲取訊號 sigemptyset(&set); sigaddset(&set, SIGHUP); sigaddset(&set, SIGQUIT); sigaddset(&set, SIGTERM); sigaddset(&set, SIGALRM); sigaddset(&set, SIGINT); sigaddset(&set, SIGIO); sigaddset(&set, SIGCHLD); if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) { printf("sigprocmask failed\n"); return 0; } sigemptyset(&set); //啟動worker程序 start_worker_processes(); //主程序迴圈監控 while (true) { //sigsuspend(const sigset_t *mask))用於在接收到某個訊號之前, 臨時用mask替換程序的訊號掩碼, 並暫停程序執行,直到收到訊號為止。 sigsuspend(&set); if (terminate) { kill(worker_pid, SIGTERM); //通知子程序關閉 waitpid(worker_pid, NULL, 0); //等待回收資源,避免殭屍程序 //主程序退出時刪除測試檔案 if (0 == access(szfilename, F_OK)) { remove(szfilename); } exit(0); } if (reap) { waitpid(worker_pid, NULL, 0); //若子程序退出,需等待回收資源,避免殭屍程序 start_worker_processes(); //拉起新的子程序 } sleep(1); } return 0; } void signal_handler(int signo) { signal_t *sig; for (sig = signals; sig->signo != 0; sig++) { if (sig->signo == signo) { break; } } switch (signo) { case SIGTERM: case SIGINT: { terminate = 1; break; } case SIGCHLD: { reap = 1; break; } default: { break; } } } bool start_daemon() { int fd; switch (fork()) { case -1: printf("fork() failed\n"); return false; case 0: break; default: exit(0); } if (setsid() == -1) { printf("setsid() failed\n"); return false; } switch (fork()) { case -1: printf("fork() failed\n"); return false; case 0: break; default: exit(0); } umask(0); chdir("/"); long maxfd; if ((maxfd = sysconf(_SC_OPEN_MAX)) != -1) { for (fd = 0; fd < maxfd; fd++) { close(fd); } } fd = open("/dev/null", O_RDWR); if (fd == -1) { printf("open(\"/dev/null\") failed\n"); return false; } if (dup2(fd, STDIN_FILENO) == -1) { printf("dup2(STDIN) failed\n"); return false; } if (dup2(fd, STDOUT_FILENO) == -1) { printf("dup2(STDOUT) failed\n"); return false; } if (dup2(fd, STDERR_FILENO) == -1) { printf("dup2(STDERR) failed\n"); return false; } if (fd > STDERR_FILENO) { if (close(fd) == -1) { printf("close() failed\n"); return false; } } return true; } void worker(long i) { sigset_t set; //子程序繼承父程序對訊號的遮蔽,所以這裡需要解除對訊號的遮蔽 sigemptyset(&set); if (sigprocmask(SIG_SETMASK, &set, NULL) == -1) { printf("work sigprocmask failed\n"); return; } FILE* pFile = fopen(szfilename, "a+"); while (true) { if (terminate) { exit(0); } fprintf(pFile, "pid = %ld hello world\n", i); fflush(pFile); sleep(2); } if (NULL != pFile) { fclose(pFile); pFile = NULL; } } void init_signals() { signal_t* sig; struct sigaction sa; for (sig = signals; sig->signo != 0; sig++) { memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = sig->handler; sigemptyset(&sa.sa_mask); if (sigaction(sig->signo, &sa, NULL) == -1) { printf("sigaction error\n"); return; } } } void start_worker_processes() { pid_t pid; pid = fork(); switch (pid) { case -1: printf("exec fork failure\n"); return; case 0: worker((long)getpid()); break; default: worker_pid = pid; break; } }

3、程式執行

程式執行後可通過ps -ef檢視程式父子程序,嘗試kill掉子程序,看是否會拉起新的子程序,同時檢視/tmp/my_test_daemon.txt檔案裡的程序pid是否是新的子程序的pid。嘗試kill掉父程序,看父子程序是否都停止。