C语言实现简单的Deamon守护进程

实现简单的Deamon守护进程,包含2个功能,拉起系统所需进程、监控到有子进程意外退出时重新拉起。

目录

  • 程序效果
  • 功能流程
  • 关键实现
  • 参考资料

程序效果

C语言实现简单的Deamon守护进程_第1张图片 截图1.Daemon父进程视角

Daemon父进程分别拉起1个ChildA、2个ChildB、3个ChildC子进程,在察觉到ChildA、ChildC子进程存在关闭情况时重新拉起,并保存新的进程ID。

C语言实现简单的Deamon守护进程_第2张图片 截图2.查看系统进程视角

使用kill -9命令强制退出子进程ChildA、ChildC,重新查看进程信息发新Pid改变,说明Daemon重新拉起成功。

功能流程

C语言实现简单的Deamon守护进程_第3张图片 截图3.功能流程图

 

关键实现

1.拉起子进程

pid_t lStartProcess(char *sSrvId, int nSrvSeqId)
{
    char sSrvId[4+1]; //自定义的服务ID
    char sSrvSeqId[3]; //同名服务的序号
    int iRet;
    int i, j, k;
    pid_t iPid;
    char sProName[251]; //程序名称
    char *sProArgv[20+1]; //execmp程序入参
    char sArgv[20][250]; //数据表中设置的程序入参

    /* 初始化变量值 */
    memset(sArgv, 0x00, sizeof(sArgv));
    for (i=0; i<20+1; i++)
    {
        sProArgv[i] = 0;
    }

    /* 获取TBL_PRO_PARAM表中的程序名称 */
    memset(&tTblProParam, 0x00, sizeof(TBL_SRV_PARAM_Def));
    memcpy(tTblProParam.srv_id, sSrvId, 4);
    memcpy(tTblProParam.param_type, "0", 1);
    memcpy(tTblProParam.param_seq, "01", 2);
    iRet = DbsTBL_PRO_PARAM(DBS_SELECT, &tTblProParam);
    if (0 != iRet)
    {
        printf("DbsTBL_PRO_PARAM DBS_SELECT error! iRet = %d\n", iRet);
        return -1;
    }

    lRtrim(tTblProParam.param_value);
    memcpy(sProName, tTblProParam.param_value, 251);

    sProArgv[0] = sProName;

    /* 获取程序入参 */
    memset(&tTblProParam, 0x00, sizeof(tTblProParam));
    memcpy(tTblProParam.srv_id, sSrvId, 4);
    memcpy(tTblProParam.param_type, "2", 1);
    iRet = DbsTBL_PRO_PARAM(DBS_OPEN, &tTblProParam);
    if (0 != iRet)
    {
        printf("DbsTBL_PRO_PARAM DBS_OPEN error! iRet = %d\n", iRet);
        return -1;
    }

    i = 1;
    memset(sSrvId, 0x00, sizeof(sSrvId));
    memcpy(sSrvId, sSrvId, 4);

    i++;
    memset(sSrvSeqId, 0x00, sizeof(sSrvSeqId));
    sprintf(sSrvSeqId, "%d", nSrvSeqId);

    i++;
    for (;;)
    {
        memset(&tTblProParam, 0x00, sizeof(TBL_SRV_PARAM_Def));
        iRet = DbsTBL_PRO_PARAM(DBS_FETCH, &tTblProParam);
        if (DBS_NOTFOUND == iRet)
        {
            break;
        }
        else if (0 != iRet)
        {
            printf("DbsTBL_PRO_PARAM DBS_FETCH error! iRet = %d\n", iRet);
            DbsTBL_PRO_PARAM(DBS_CLOSE, &tTblProParam);
            return -1;
        }

        lRtrim(tTblProParam.param_value);
        strcpy(sArgv[i], tTblProParam.param_value);
        sProArgv[i] = sArgv[i];
        i++;
    }

    DbsTBL_PRO_PARAM(DBS_CLOSE, &tTblProParam);

    /* 创建子进程 */
    iPid = fork();
    if (-1 == iPid)
    {
        printf("fork error, %d\n", errno);
        return -1;
    }
    else if (iPid > 0)
    {
        /* 父进程 */
        printf("start %s %s success, pid %d\n", sSrvId, sProName, iPid);
        return iPid;
    }
    else
    {
        /* 子进程执行execvp重新装载新程序ChildA、ChildB、ChildC */
        if (-1 == execvp(sProName, sProArgv))
        {
            printf("start %s fail at execvp, %d: [%s]", sProName, errno, strerror(errno));
            sleep(10);
        }

        exit(1);
    }

    return -1;
}

主要使用fork创建子进程,使用execvp装载新的程序。 程序名称、入参从数据表取,是为了能在最开始统一定义系统涉及的所有程序的名称、入参,便于后期维护。 也可以直接在程序里写死。

2.监控子进程

(1)记录创建的子进程的进程号结构

typedef struct
{
    pid_t pid;
    char sSrvId[4+1];
    int nSrvSeqId;
}ProPcbDef;

记录服务ID为sSrvId的第nSrvSeqId个进程的进程号。 会创建多个同名进程。

(2)设置信号响应

int main(int argc, char **argv)
{
    int i;
    int iRet;

    /* 连接数据库 */
    iRet = db_connect();
    if (0 != iRet)
    {
        printf("db_connect error! iRet = %d\n", iRet);
        return -1;
    }

    memset(&tTblProInf, 0x00, sizeof(TBL_PRO_INF_Def));

    /* 屏蔽会使Daemon退出的SIGHUP信号 */
    signal(SIGHUP, SIG_IGN);

    /* 初始化记录子进程Pid的结构体数组 */
    for (i=0; i<10; i++)
    {
        gProList[i].pid = -1;
        memset(gProList[i].sSrvId, 0x00, sizeof(gProList[i].sSrvId));
    }

    /* 设置子进程退出信号SIGCHLD信号的响应函数 */
    sighold(SIGCHLD);
    sigset(SIGCHLD, HandleChildDeath);

    /* 拉起系统进程 */
    sighold(SIGCHLD);
    vStartAllProcess();
    sigrelse(SIGCHLD);

    /* Daemon死循环不退出,一直监控子进程状态 */
    while (1)
    {
        sleep(120);
    }

    return 0;
}

(3)子进程退出的处理

void HandleChildDeath(int n)
{
    int i;
    int iPid;

    /* 等待子进程返回信号 */
    while (waitpid(-1, &iPid, WNOHANG) > 0)
    {
        ;
    }

    /* 捕捉到信号后暂时屏蔽SIGCHLD信号 */
    sighold(SIGCHLD);

    /* 遍历所有子进程查看已退出的部分 */
    for (i=0; i<10; i++)
    {
        if (gProList[i].pid != -1)
        {
            /* kill发出信号返回-1表示子进程已死亡,需要重新拉起 */
            if (-1 == kill(gProList[i].pid, 0))
            {
                printf("server %s pid %d stopped, restart it.\n", gProList[i].sSrvId, gProList[i].pid);

                /* 重新拉起并记录新的进程号ID */
                gProList[i].pid = lStartProcess(gProList[i].sSrvId, gProList[i].nSrvSeqId);
            }
        }
    }

    /* 解除屏蔽并重新设置响应函数 */
    sigrelse(SIGCHLD);
    sigset(SIGCHLD, vHandleChildDeath);
}

参考资料

1.Linux下利用fork / execvp过程在子进程中执行小程序

https://blog.csdn.net/estellehey/article/details/79981290

2.SIGCHLD信号

https://blog.csdn.net/qq33883085/article/details/89325396

你可能感兴趣的:(练习集)