Linux平臺C++監控子程序程式
阿新 • • 發佈:2019-01-31
1、問題描述
有時候服務端程式會偶現異常停止,這個時候除了要查詢程式的bug外,還有拉起程式以繼續提供服務。目前為了方便,我們直接用shell指令碼去監控服務程序,這種方法比較簡單,不再贅述。這裡描述另一種方法:服務程序分離為主監控程序和子工作程序,主程序只負責監控子程序(停止、拉起子程序等),子程序負責對外提供服務。
監控子程序的程式,個人認為重點如下:
- 子程序收到終止訊號後,完成自身善後工作,然後直接呼叫exit()終止。
- 父程序收到子程序終止的訊號後,先呼叫waitpid()回收子程序的資源,避免殭屍程序,再拉起新的子程序。
- 父程序收到終止訊號後,首先呼叫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掉父程序,看父子程序是否都停止。