软件看门狗的一种实现——父进程监控子进程

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章