本文利用 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