【服务程序的运行策略】——守护进程

一、守护进程

守护进程的目的是:检查共享内存中的服务程序信息,判断他们是否还“活着”,如果活着则不进行操作,如果服务程序异常了,则将其进程终止掉:如果进程和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表明不存在,跳出循环。

上一篇:①EtherCAT转ModbusTCP, EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关


下一篇:camunda + oracle 启动报错 解决方法