两个程序,一个是崩溃后再运行、一个是杀死超时程序。
调度进程
先让1号进程接管,然后由这个被1号进程接管的进程来负责fork子进程,并等待子进程退出。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
// ./procct1 5 /bin/ls -lt /tmp/tmp.txt
if (argc < 3)
{
printf("Using: ./procct1 timetv1 program argv ...\n");
printf("Example:/project/tools1/bin/procct1 5 /bin/tar zcvf /tmp/tmp.tgz /usr/include\n\n");
printf("本程序是服务程序的调度程序,周期性启动服务程序或shell脚本\n");
printf("timetv1 运行周期,单位:秒 被调度的程序运行结束后,在timetv1秒后会被procct1重新启动\n");
printf("program 被调度的程序名,必须使用完整路径\n");
printf("argvs 被调度的程序的参数\n");
printf("注意,本程序不会被kill杀死,但可以用kill -9强行杀死\n\n\n");
return -1;
}
// 关闭所有信号和io
for (int ii=0; ii<64; ii++)
{
signal(ii,SIG_IGN);
close(ii);
}
// 生成子进程,父进程退出,让程序运行在后台,由一号进程接管
if (fork() != 0) exit(0);
// 启用SIGCHLD信号,让父进程可以wait子进程退出的状态(僵尸进程)
signal(SIGCHLD,SIG_DFL);
char *pargv[argc-1];
for (int ii=2;ii<argc;ii++)
pargv[ii-2] = argv[ii];
pargv[argc-2] = NULL;
while (true)
{
if (fork() == 0)
{
execv(argv[2], pargv);
exit(0); //execv调用失败被执行,不加死循环
}
else
{
int status;
wait(&status);
sleep(atoi(argv[1]));
}
}
return 0;
}
共享内存超时进程
共享内存是进程间通信的一种方式,好处是可以在内存里存起来。掉电不保存。
#include "_public.h"
// 程序运行的日志。
CLogFile logfile;
int main(int argc,char *argv[])
{
// 程序的帮助。
if (argc != 2)
{
printf("\n");
printf("Using:./checkproc logfilename\n");
printf("Example:/project/tools1/bin/procctl 10 /project/tools1/bin/checkproc /tmp/log/checkproc.log\n\n");
printf("本程序用于检查后台服务程序是否超时,如果已超时,就终止它。\n");
printf("注意:\n");
printf(" 1)本程序由procctl启动,运行周期建议为10秒。\n");
printf(" 2)为了避免被普通用户误杀,本程序应该用root用户启动。\n");
printf(" 3)如果要停止本程序,只能用killall -9 终止。\n\n\n");
return 0;
}
// 忽略全部的信号和IO,不希望程序被干扰。
CloseIOAndSignal(true);
// 打开日志文件。
if (logfile.Open(argv[1],"a+")==false)
{ printf("logfile.Open(%s) failed.\n",argv[1]); return -1; }
int shmid=0;
// 创建/获取共享内存,键值为SHMKEYP,大小为MAXNUMP个st_procinfo结构体的大小。
if ( (shmid = shmget((key_t)SHMKEYP, MAXNUMP*sizeof(struct st_procinfo), 0666|IPC_CREAT)) == -1)
{
logfile.Write("创建/获取共享内存(%x)失败。\n",SHMKEYP); return false;
}
// 将共享内存连接到当前进程的地址空间。
struct st_procinfo *shm=(struct st_procinfo *)shmat(shmid, 0, 0);
// 遍历共享内存中全部的记录。
for (int ii=0;ii<MAXNUMP;ii++)
{
// 如果记录的pid==0,表示空记录,continue;
if (shm[ii].pid==0) continue;
// 如果记录的pid!=0,表示是服务程序的心跳记录。
// 程序稳定运行后,以下两行代码可以注释掉。
//logfile.Write("ii=%d,pid=%d,pname=%s,timeout=%d,atime=%d\n",\
// ii,shm[ii].pid,shm[ii].pname,shm[ii].timeout,shm[ii].atime);
// 向进程发送信号0,判断它是否还存在,如果不存在,从共享内存中删除该记录,continue;
int iret=kill(shm[ii].pid,0);
if (iret==-1)
{
logfile.Write("进程pid=%d(%s)已经不存在。\n",(shm+ii)->pid,(shm+ii)->pname);
memset(shm+ii,0,sizeof(struct st_procinfo)); // 从共享内存中删除该记录。
continue;
}
time_t now=time(0); // 取当前时间。
// 如果进程未超时,continue;
if (now-shm[ii].atime<shm[ii].timeout) continue;
// 如果已超时。
logfile.Write("进程pid=%d(%s)已经超时。\n",(shm+ii)->pid,(shm+ii)->pname);
// 发送信号15,尝试正常终止进程。
kill(shm[ii].pid,15);
// 每隔1秒判断一次进程是否存在,累计5秒,一般来说,5秒的时间足够让进程退出。
for (int jj=0;jj<5;jj++)
{
sleep(1);
iret=kill(shm[ii].pid,0); // 向进程发送信号0,判断它是否还存在。
if (iret==-1) break; // 进程已退出。
}
// 如果进程仍存在,就发送信号9,强制终止它。
if (iret==-1)
logfile.Write("进程pid=%d(%s)已经正常终止。\n",(shm+ii)->pid,(shm+ii)->pname);
else
{
kill(shm[ii].pid,9); // 如果进程仍存在,就发送信号9,强制终止它。
logfile.Write("进程pid=%d(%s)已经强制终止。\n",(shm+ii)->pid,(shm+ii)->pname);
}
// 从共享内存中删除已超时进程的心跳记录。
memset(shm+ii,0,sizeof(struct st_procinfo)); // 从共享内存中删除该记录。
}
// 把共享内存从当前进程中分离。
shmdt(shm);
return 0;
}
服务器上的共享内存有 MAXNUMP 个记录进程的空间。每执行一次check程序会遍历这个数组,遇到有记录的,先发送0确定进程在运行,再判断是否超时、如果超时:先发送15信号正常终止、再循环发送5次0、如果还没有结束发送9。
#include "_public.h"
// 程序运行的日志。
CLogFile logfile;
int main(int argc,char *argv[])
{
// 程序的帮助。
if (argc != 2)
{
printf("\n");
printf("Using:./checkproc logfilename\n");
printf("Example:/project/tools1/bin/procctl 10 /project/tools1/bin/checkproc /tmp/log/checkproc.log\n\n");
printf("本程序用于检查后台服务程序是否超时,如果已超时,就终止它。\n");
printf("注意:\n");
printf(" 1)本程序由procctl启动,运行周期建议为10秒。\n");
printf(" 2)为了避免被普通用户误杀,本程序应该用root用户启动。\n");
printf(" 3)如果要停止本程序,只能用killall -9 终止。\n\n\n");
return 0;
}
// 忽略全部的信号和IO,不希望程序被干扰。
CloseIOAndSignal(true);
// 打开日志文件。
if (logfile.Open(argv[1],"a+")==false)
{ printf("logfile.Open(%s) failed.\n",argv[1]); return -1; }
int shmid=0;
// 创建/获取共享内存,键值为SHMKEYP,大小为MAXNUMP个st_procinfo结构体的大小。
if ( (shmid = shmget((key_t)SHMKEYP, MAXNUMP*sizeof(struct st_procinfo), 0666|IPC_CREAT)) == -1)
{
logfile.Write("创建/获取共享内存(%x)失败。\n",SHMKEYP); return false;
}
// 将共享内存连接到当前进程的地址空间。
struct st_procinfo *shm=(struct st_procinfo *)shmat(shmid, 0, 0);
// 遍历共享内存中全部的记录。
for (int ii=0;ii<MAXNUMP;ii++)
{
// 如果记录的pid==0,表示空记录,continue;
if (shm[ii].pid==0) continue;
// 如果记录的pid!=0,表示是服务程序的心跳记录。
// 程序稳定运行后,以下两行代码可以注释掉。
//logfile.Write("ii=%d,pid=%d,pname=%s,timeout=%d,atime=%d\n",\
// ii,shm[ii].pid,shm[ii].pname,shm[ii].timeout,shm[ii].atime);
// 向进程发送信号0,判断它是否还存在,如果不存在,从共享内存中删除该记录,continue;
int iret=kill(shm[ii].pid,0);
if (iret==-1)
{
logfile.Write("进程pid=%d(%s)已经不存在。\n",(shm+ii)->pid,(shm+ii)->pname);
memset(shm+ii,0,sizeof(struct st_procinfo)); // 从共享内存中删除该记录。
continue;
}
time_t now=time(0); // 取当前时间。
// 如果进程未超时,continue;
if (now-shm[ii].atime<shm[ii].timeout) continue;
// 如果已超时。
logfile.Write("进程pid=%d(%s)已经超时。\n",(shm+ii)->pid,(shm+ii)->pname);
// 发送信号15,尝试正常终止进程。
kill(shm[ii].pid,15);
// 每隔1秒判断一次进程是否存在,累计5秒,一般来说,5秒的时间足够让进程退出。
for (int jj=0;jj<5;jj++)
{
sleep(1);
iret=kill(shm[ii].pid,0); // 向进程发送信号0,判断它是否还存在。
if (iret==-1) break; // 进程已退出。
}
// 如果进程仍存在,就发送信号9,强制终止它。
if (iret==-1)
logfile.Write("进程pid=%d(%s)已经正常终止。\n",(shm+ii)->pid,(shm+ii)->pname);
else
{
kill(shm[ii].pid,9); // 如果进程仍存在,就发送信号9,强制终止它。
logfile.Write("进程pid=%d(%s)已经强制终止。\n",(shm+ii)->pid,(shm+ii)->pname);
}
// 从共享内存中删除已超时进程的心跳记录。
memset(shm+ii,0,sizeof(struct st_procinfo)); // 从共享内存中删除该记录。
}
// 把共享内存从当前进程中分离。
shmdt(shm);
return 0;
}
如何往共享内存里面写进程信息?
涉及到信号量加锁。锁之间的流程是:先找有没有已经存了该进程号的,没有的话定位到第一个空位置。然后将当前进程的心跳信息存入共享内存的进程组中。
如何加锁解锁?