軟件看門狗的一種實現——父進程監控子進程

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

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