一、守护进程
守护进程的目的是:检查共享内存中的服务程序信息,判断他们是否还“活着”,如果活着则不进行操作,如果服务程序异常了,则将其进程终止掉:如果进程和pid不存在了,对该进程的共享内存的区域进行清理;如果进程已经超时了,发送15信号尝试正常终止,如果终止不了,则发送9信号强行杀死,并从共享内存中删除已超时进程的心跳记录。实现流程为:
- 程序的帮助。
- 忽略全部的信号和关闭I/O,设置信号处理函数。
- 打开日志文件
- 创建/获取共享内存,键值为SHMKEYP,大小为MAXNUMP个st_procinfo结构体的大小。
- 将共享内存连接到当前进程的地址空间。
- 遍历共享内存中全部的记录,如果进程已超时,终止他。
- 把共享内存从当前进程中分离。
二、判断进程是否存
向进程发送信号0,返回-1 则说明进程已经不存在了。
int iret=kill(m_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;
}
三、守护进程的实现
/*
* checkproc.cpp 守护程序:检查共享内存中进程的心跳,如果超时,则终止进程。
* 作者:张咸武。
*/
#include<_public.h>
using namespace idc;
int main(int argc,char* argv[])
{
// 程序的帮助。
if (argc != 2)
{
printf("\n");
printf("Using:./checkproc logfilename\n");
printf("Example:/project/tools/bin/procctl 10 /project/tools/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); //测试阶段先不启用关闭IO,方便调试。
//打开日志文件
clogfile logfile;
if(logfile.open(argv[1])==false)
{
printf("logfile.open(%s) failed.\n",argv[1]); return -1;
}
int m_shmid=0;
//创建/获取共享内存,键值为SHMKEYP,大小为MAXNUMP个st_procinfo结构体的大小。
if((m_shmid=shmget((key_t)0x5095, 1000*sizeof(st_procinfo),0666|IPC_CREAT))==-1)//如果失败
{
printf("创建/获取共享内存(%x)失败。\n",0x5095);return -1;
}
//将共享内存连接到当前进程的地址空间。
struct st_procinfo *m_shm=(struct st_procinfo *)shmat(m_shmid,0,0);
//遍历共享内存中全部的记录,如果进程已超时,终止他。
for(int ii=0;ii<MAXNUMP;ii++)
{
if(m_shm[ii].pid==0) continue;
//如果记录的pid!=0 表示是服务程序的心跳记录。
// 显示进程信息,程序稳定运行后,以下两行代码可以注释掉。
// logfile.write("ii=%d,pid=%d,pname=%s,timeout=%d,atime=%d\n",\
// ii,m_shm[ii].pid,m_shm[ii].pname,m_shm[ii].timeout,m_shm[ii].atime);
//要理解 //如果进程已经不存在了,共享内存中是残留的心跳信息。那么以后的代码没必要了。
//向进程发送信号0,判断它是否还存在,如果不存在,从共享内存中删除该纪录,continue;
int iret=kill(m_shm[ii].pid,0);
if(iret==-1)
{
logfile.write("进程pid=%d(%s)已经不存在。\n",m_shm[ii].pid,m_shm[ii].pname);
memset(&m_shm[ii],0,sizeof(struct st_procinfo)); // 从共享内存中删除该记录。
continue;
}
//判断进程的心跳是否超时,如果超时了,就终止它。
time_t now=time(0); //获取当前时间
//如果进程未超时,continue;
if(now-m_shm[ii].atime<m_shm[ii].timeout) continue;
struct st_procinfo tmp=m_shm[ii];
if(tmp.pid==0) continue;
//如果进程已超时。
logfile.write("进程pid=%d(%s)已经超时。\n",tmp.pid,tmp.pname);
//发送信号15,尝试正常终止已超时的进程。
kill(tmp.pid,15);
// sleep(5); //给进程一些时间让其正常退出,给多少时间?需要优化。
//每隔1秒判断一次进程是否存在,累计5秒,一般来说,5秒的时间足够让进程退出。
for(int jj=0;jj<5;jj++)
{
sleep(1);
iret=kill(tmp.pid,0); //向进程发送信号0,判断它是否还存在。
if(iret==-1) break; //进程已经退出。
}
if(iret==-1)
logfile.write("进程pid=%d(%s)已经正常终止。\n",tmp.pid,tmp.pname);
else
{ //强行终止进程。
kill(tmp.pid,9);
logfile.write("进程pid=%d(%s)已经强制终止。\n",tmp.pid,tmp.pname);
// 从共享内存中删除已超时进程的心跳记录。
memset(m_shm+ii,0,sizeof(struct st_procinfo));
}
}
//把共享内存从当前进程中分离。
shmdt(m_shm);
return 0;
}
测试程序demo.cpp
#include"_public.h"
using namespace idc;
cpactive pactive;
void EXIT(int sig)
{
printf("sig=%d\n",sig);
exit(0);
}
int main(int argc,char* argv[])
{
//处理信号
signal(SIGINT,EXIT);signal(SIGTERM,EXIT);
// closeioandsignal(true);
pactive.addpinfo(atoi(argv[1]),"demo"); //把当前进程的信息加入共享内存进程组中
while(true)
{
sleep(atoi(argv[2]));
pactive.uptatime(); //更新进程的心跳。
}
return 0;
}
四、一些细节
????demo.cpp细节,cpactive pactive要设置为全局变量,否则exit(0);的时候不会调用pactive的析构。
exit()表示终止进程,不会调用局部对象的析构函数,只调用全局对象的析构函数。
????如果进程已经不存在了,共享内存中是残留的心跳信息。向进程发送信号0,判断它是否还存在,如果不存在,从共享内存中删除该纪录,continue;
????如果一个超时的进程无法响应15信号(已经死掉或者其他原因)不能正常退出该怎么办?
先发送15信号尝试正常终止超时的进程;再发送0信号(给一些时间让进程退出),判断它是否存在;不存在说明正常终止了,存在说明未正常终止进行强制终止-9并清理共享内存中的进程心跳记录。
那么发送15信号后给出多少时间让其退出呢?5s?会不会太长?这里需要优化:for循环5次,每次休眠1秒,发送0信号,当收到返回值为-1表明不存在,跳出循环。