最近接触到Linux C中的daemon函数,顾名思义,它和守护进程Daemon有关;简单来说Linux Daemon(守护进程)是运行在后台的一种特殊进程;
一般来说,它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件,因为守护进程是在后台运行,不会占着终端,所以终端可以执行其他命令。
Linux系统的大多数服务器就是通过守护进程实现的。常见的守护进程包括系统日志进程syslogd、 web服务器httpd、邮件服务器sendmail和数据库服务器mysqld等
利用它后台执行且不占用终端的特点,也可以用来守护其他程序,它处于不断监控的状态,一旦监控到目标程序“死掉”,便马上将其“拉起”;
废话不多说,来看案例:
目标
要保证outputTimer.sh
程序在后台一直处于“活着”的状态,一旦发现它突然崩溃,则自动将其拉起;
使用方法
本程序名为: diyDaemon
执行方法:./diyDaemon + <待守护程序所在的全路径>
效果
利用diyDaemon
这个守护进程来保证outputTimer.sh
处于“活着”的状态;
1、可以看到起初并没有outputTimer.sh进程,执行diyDaemon后,便将自动outputTimer.sh拉起来
2、手动杀掉outputTimer.sh进程,根据Pid号可知,又拉起来一个outputTimer.sh程序继续运行,从而保证一直有outputTimer.sh活着
代码
代码中已详细注释
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#define BUFSIZE 128
int main(int argc,char *argv[])
{
char cmd[BUFSIZE] = {'\0'};
char startUpCmd[BUFSIZE] = {'\0'}; //存放 拉起死掉程序 的命令
char returnBuffer[BUFSIZE] = {'\0'}; //存放popen的输出
char *token = NULL;
char *programName = NULL; //存放守护的程序名
char path[BUFSIZE] = {'\0'}; //存放 守护的程序 的路径
FILE *file = NULL;
if(argc < 2)
{
printf("Usage:./diyDaemon <Program execution path>\n");
exit(0);
}
//将本程序设定为守护进程
//int daemon(int nochdir, int noclose);
//函数参数:
//nochdir:为0时表示将当前目录更改至“/”
//noclose:为0时表示将标准输入、标准输出、标准错误重定向至“/dev/null”
if(-1 == daemon(0, 0))
{
printf("daemon error\n");
exit(1);
}
//copy守护的程序的路径名到path数组中,因为strtok会更改到path的值,所以用copy
strcpy(path,argv[1]);
sprintf(startUpCmd,"%s &",argv[1]); //将守护的程序设为后台运行
//分割输入的路径名,以获得程序的名字,默认以“/”来分割
token=strtok(path,"/");
while(token != NULL)
{
programName = token;
token=strtok(NULL,"/"); //NULL代表从上一次切割剩下的path字符串中继续切割
}
while(1)
{
//检测守护的程序是否存在,grep的时候,排除掉“grep进程、自身”,
//防止误认为守护的进程还活着
sprintf(cmd,"ps -ef | grep %s |grep -Ev '(grep|diyDaemon)'",programName);
//puts(cmd);
file = popen(cmd,"r"); //popen相比于system,可以读取到命令的输出结果
fgets(returnBuffer,BUFSIZE,file);
//通过比对来确认守护进程是否活着,如果死去了就重新拉起
if(strstr(returnBuffer,programName) == NULL)
{
printf("%s\n",startUpCmd);
system(startUpCmd);
}
memset(returnBuffer,'\0',BUFSIZE);
//!这里特别重要,花费了许多时间才排查到
//不加的话,returnBuffer的数据永远都是第一次获得数据;导致后面的比对有误
//读man手册可知,因为popen进程执行时输出输出缓冲区和本程序共享
//所以并没有将数据从输出缓存区中读出,所以要用fflush来清空缓存区;
//或者用fread来将数据读出也行;
fflush(file);
sleep(3);
}
pclose(file);
return 0;
}
待优化方向
1、如果outputTimer.sh
的启动需要参数的话,则无法使用,需要改进为允许待守护的程序可带参数启动
2、可以优化为执行程序时的路径参数可以用相对路径,而不是只能绝对路径
3、可以增加一个日志功能,可记录守护的程序挂了多少次,方便后期优化被守护的程序
当然作为曾经写过一段时间shell的小哥,也给这个程序写了给shell版本的,供大家参考,功能基本上一致,shell版本的似乎看起更简洁,如果做运维的同学,可能会更喜欢这种吧
注意执行这个脚本的时候加上&,让它在后台执行好一点,即“脚本名 &” ,这样不会占用终端
#!/bin/bash
path=$1
echo $path
file=${path##*/}
echo $file
while :
do
procnum=`ps -ef|grep $file|grep -Ev '(grep|test.sh)' | wc -l`
#echo $procnum
if [ $procnum -eq 0 ];then
`$path &`
fi
sleep 3
done
当然,实际工作中,也有很多更专业的可实现这个功能的工具,比如supervisor。总之,实际工作时,灵活运用即可