本文利用 fork、wait、execvp 三个函数实现了类似软件看门狗的功能,具体如下:
1. 父进程创建子进程,子进程负责运行小程序
2. 父进程的 log 打印到终端,子进程的 log 被重定向到指定文件
3. 父进程负责监控子进程的运行状态,发现子进程退出则重新拉起子进程(小程序)
准备一个小程序 dog.c,编译成 aDog 和 bDog 两个小程序
// dog.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
int main(int argc, char *argv[])
{
// 默认一分钟退出, 如有参数则按参数来计时
int cnt = 60;
if(argc == 2)
{
cnt = atoi(argv[1]);
}
while(cnt--)
{
sleep(1);
}
printf("[%s]<%d> exit!\n", argv[0], getpid());
return 0;
}
软件看门狗程序 softWatchDog.c,编译成 softWatchDog
// softWatchDog.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
long GetStrFields(char *str, char c)
{
long ret = 0;
char *pos = NULL;
pos = str;
while(1)
{
pos = strchr(pos, c);
if (pos == NULL)
{
break;
}
*pos = '\0'; // 把找到的 c 替换成尾零
pos++;
ret++;
}
ret++; // 最后再加多一次
return ret;
}
void Buff2Argv(char *str, char **argv, int items)
{
char *pos;
int i;
int j;
pos = str;
j = 0;
for(i = 0; i < items; i++)
{
if(*pos != '\0')
{
argv[j] = pos;
j++;
}
// 得益于 GetStrFields() 已经把 str 的空格替换成尾零
pos += strlen(pos) + 1;
}
}
char str_cmd[1024];
int main(int argc, char *argv[])
{
int i;
int pid;
int st;
int ret;
long len;
char *argv_buff;
char **argv_new;
char sLogFile[1024] = {0};
char sTmp[1024] = {0};
// 例如: ./softWatchDog "/bin/aDog 100", 则 "/bin/aDog 100" 就是 argv[1]
if(argc != 2)
{
printf("Usage: %s \"YourCommand [arg]\" \n", argv[0]);
exit(1);
}
printf("argv[1] = %s\n", argv[1]);
// 得到 argv[1] 的长度
len = strlen(argv[1]);
// 分配一段内存空间把 argv[1] 存起来
argv_buff = (char *)malloc(len);
memset(argv_buff, 0, len);
strcpy(argv_buff, argv[1]);
// 解析出 argv[1] 有多少段以空格隔开的字符串, 执行完该函数后, argv_buff 的空格会被尾零替换掉
ret = GetStrFields(argv_buff, ' ');
printf("ret = %d\n", ret);
// 创建一个字符串指针数组
len = (ret + 1) * sizeof(char *);
argv_new = (char **) malloc(len);
memset(argv_new, 0, len);
// 把 argv[1] 拆成一段段放进数组中
Buff2Argv(argv_buff, argv_new, ret);
for(i = 0; i < ret; i++)
{
printf("argv_new[%d] = %s\n", i, argv_new[i]);
}
printf("argv_new[%d] = %s\n", ret, argv_new[ret]);
// 数组最后一个要是 NULL, 后续的 execvp() 第二个参数要求数组最后一个成员必须是 NULL
argv_new[ret] = NULL;
// str_cmd 存放的是被监控的进程名字(包括路径, 如: /bin/aDog)
strcpy(str_cmd, argv_new[0]);
printf("str_cmd = %s\n", str_cmd);
while(1)
{
while(1)
{
pid = fork();
if(pid < 0)
{
printf("fork() error[%s]<%d>\n", strerror(errno), errno);
sleep(16);
}
else
{
break;
}
}
// 父进程走的这段
if(pid > 0)
{
// 父进程等待子进程: 只有子进程退出 wait() 才会返回(返回值是子进程的pid), 否则阻塞
ret = wait(&st);
printf("wait ret = %d\n", ret);
if (ret < 0)
{
printf("wait() error[%s]<%d>\n", strerror(errno), errno);
}
// 解析子进程退出的原因
if(WIFEXITED(st))
{
if(WEXITSTATUS(st) == 199)
{
printf("Fail to exec [%s], program not started.\n", str_cmd);
//exit(0);
}
else if(WEXITSTATUS(st) == 198)
{
printf("There is already a process [%s]<%d> running!\n", str_cmd, pid);
//exit(0);
}
else if(WEXITSTATUS(st) == 197)
{
printf("%s is shutdown because watching proccess[%s]<%d> exited, and it does not want to be restared again.\n", argv[0], str_cmd, pid);
exit(0);
}
else if(WEXITSTATUS(st) == 0)
{
printf("Watching proccess[%s]<%d> was normal exited.\n", str_cmd, pid);
//exit(0);
}
printf("Watching proccess[%s]<%d> was exited, restart it.\n", str_cmd, pid);
}
else if(WIFSIGNALED(st))
{
printf("Watching proccess [%s]<%d> was abnormal exited, receive signal[%d].\n", str_cmd, pid, WTERMSIG(st));
if(WTERMSIG(st) == SIGKILL)
{
printf("Watching proccess[%s]<%d> has been killed manual.\n", str_cmd, pid);
//exit(0);
}
}
else if(WIFSTOPPED(st))
{
printf("Watching proccess [%s]<%d> was stopped, receive signal[%d]\n", str_cmd, pid, WSTOPSIG(st));
//exit(0);
}
sleep(5);
continue; // 父进程走到这里就会继续跑到上面去创建子进程
}
// 子进程走的这段
if(pid == 0)
{
int fd = 0;
// 默认情况下子进程的打印会和父进程的一起都在打在终端, 这也许不是我们想要的
// 接下来把子进程的打印重定向到指定文件
// 打开某个文件, 不存在则创建(创建的权限指定为755吧)
fd = open("/tmp/dog.log", O_RDWR|O_APPEND|O_CREAT, 00755);
if(fd < 0)
{
exit(1);
}
if((dup2(fd, fileno(stdin)) < 0)
|| (dup2(fd, fileno(stdout)) < 0)
|| (dup2(fd, fileno(stderr)) < 0))
{
close(fd);
exit(1);
}
close(fd);
printf("Starting Proccess [%s]<%d> ...\n", str_cmd, getpid());
setpgrp();
// execvp() 这个函数如果正常运行是不会有返回的, 有返回说明启动的程序出现异常
// 这样的定义就意味着, 正常情况下所有 execvp() 后面的代码都将不被执行
// execvp 执行参数传进来的进程, 并覆盖掉子进程自身
ret = execvp(argv_new[0], argv_new);
printf("execute[%s] error:[%s]<%d>.\n", str_cmd, strerror(errno), errno);
exit(199);
}
}
if(argv_buff != NULL)
{
free(argv_buff);
argv_buff = NULL;
}
if(argv_new != NULL)
{
free(argv_new);
argv_new = NULL;
}
return 0;
}
测试一下,把 aDog 和 bDog 带起来
./softWatchDog "aDog 10" &
./softWatchDog "bDog 8" &
运行期间,可以观察终端 log,也可以 ps 看进程的情况,还可以看 log 文件
cat /tmp/dog.log