实现简单的Deamon守护进程,包含2个功能,拉起系统所需进程、监控到有子进程意外退出时重新拉起。
Daemon父进程分别拉起1个ChildA、2个ChildB、3个ChildC子进程,在察觉到ChildA、ChildC子进程存在关闭情况时重新拉起,并保存新的进程ID。
使用kill -9命令强制退出子进程ChildA、ChildC,重新查看进程信息发新Pid改变,说明Daemon重新拉起成功。
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