使用Linux C编写看门狗(watchdog)程序

0x00前言

文章中的文字可能存在语法错误以及标点错误,请谅解;

如果在文章中发现代码错误或其它问题请告知,感谢!

0x01 watchdog(看门狗)简介

最近由于业务需要需要一个watchdog来确保设备上运行的程序在崩溃后可以再次重启,所以进行了一些研究。
watchdog(看门狗)就是为了让自己的程序在运行时出现崩溃或跑飞后能够让该程序再次重启。
Linux下使用watchdog的方法主要有三种:
1.编写一个watchdog可执行程序;
2.编写一个watchdog.sh脚本;
3.在可执行程序中包含watchdog。

本例使用第三种方法,通过父进程监控子进程(任务进程)的运行状态来判断子进程是否崩溃,父进程相当于watchdog。

0x02 代码实例

本例代码为测试当子进程出现错误崩溃后,父进程(看门狗)能够让子进程再次重启。

#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#define true 1
#define false -1
void childProcessFunc()
{
    int i = 0;
    while (true)
    {
        ++i;
        printf("i: %d, pid: %d, ppid: %d
", i, getpid(), getppid());
        if (i == 10)
        {
            // 子进程主动结束
            char* p = NULL;
            *p = 1;
        }
        sleep(1);
    }
}

void forkChildProcess()
{
    int status = 0;
    // 等待子进程中断或终止,释放子进程资源
    // 否则死掉的子进程会变成僵尸进程
    int pid = wait(&status);
    if (pid < 0)
    {
        printf("error: %s
", strerror(errno));
        return;
    }

    // 如果子进程是由于某种信号退出的,捕获该信号
    if (WIFSIGNALED(status))
    {
        int signalNum = WTERMSIG(status);
        printf("Child process was killed by signal num: %d
", signalNum);
    }

    // 检测是否生成了core文件
    if (WCOREDUMP(status))
    {
        printf("Child process core dump file generated
");
    }

    // 等待3秒钟重新启动子进程
    sleep(3);

    pid = fork();
    if (pid == 0)
    {
        printf("Fork new child process
");
        // 运行子进程代码
    	(void)init();
    }
}

int initWatchDog()
{
    int pid = fork();
    if (pid)
    {
        // 父进程一直监视子进程的运行状态
        while (true)
        {
            // 捕获子进程结束信号
            assert(signal(SIGCHLD, forkChildProcess) != SIG_ERR);
            // 父进程挂起,当有信号来时被唤醒
            pause();
        }
    }
    else if (pid < 0)
    {
        return false;
    }

    return true;
}


int init()
{	
	printf("pthread begain
");
	int ret = 0;
	ret = childinit();
    if(true != ret)
    {
            printf("init: childinit Fail!
");
			return false;
    }
	
	return true;

}

int childinit()
{
	int iRet = 0;
    pthread_t Thread_ID;

    iRet = pthread_create(&Thread_ID,NULL,childProcessFunc,NULL);	
    if(iRet != 0)
    {
       printf("childinit: childProcessFunc failed!
");
       return false;
    }
	
	return true;
}

int main()
{
	int ret = 0;
    printf("Main pid: %d
", getpid());

    // 初始化看门狗进程
    ret = initWatchDog();
    if (!ret)
    {
        printf("Init watch dog failed
");
        return -1;
    }

    printf("Init watch dog success...
");

    // 运行子进程代码
    (void)init();

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138

运行结果如下:
在这里插入图片描述

0x03 对代码中的函数简单说明

1.signal(SIGCHLD, forkChildProcess);

signal()是设置某一个信号的对应动作,它的声明如下 :

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
  • 1
  • 2
  • 3

第一个参数signum为指定要处理信号的类型,可以设置成除SIGKILL和SIGSTOP以外的任何信号。到底该参数可以设置成哪些类型,可以参考以下文档:
https://www.cnblogs.com/Jokeyyu/p/9012553.html

本例中参数类型设置为SIGCHLD,意为当子进程终止的时候,系统内核会给父进程发送信号。

第二个参数是和信号关联的动作,就是当信号发生并由系统通知进程后,进程需要做什么处理,一般可以取三种值:
1)SIG_IGN,表示忽略该信号;
2)SIG_DFL,对该信号进行默认处理;
3)由程序设计人员设定的信号处理函数sighandler_t handler()。

注意,在3中定义的类型sighandler_t,表示指向返回值为void型(参数为int型)的函数(的)指针。它可以用来声明一个或多个函数指针,举例如下:

  sighandler_t sig1, sig2; 这个声明等价于下面这种晦涩难懂的写法:

  void (*sig1)(int), (*sig2)(int);
  • 1
  • 2
  • 3

很显然,本例中使用的3方法,信号处理函数声明为:void forkChildProcess(int);

另外,进程中要是没有对信号进行signal()操作,则进程会对信号采用系统默认的处理方式进行操作。例如程序(进程)产生Segmentation fault错误时,系统内核程序会发送一个SIGSEGV信号通知程序有不合法内存引用的事件发生。若我们在程序中没有编写任何针对该信号的处理函数,系统则按照默认的方式处理传过来的信号(终止程序运行)。

2.assert(signal(SIGCHLD, forkChildProcess) != SIG_ERR);
assert是一个断言,即如果假设被违反(signal(SIGCHLD, forkChildProcess) != SIG_ERR不成立), 那表明有个严重的程序错误, 那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。
关于assert()用法总结,可以参考这篇文档:
http://www.cnblogs.com/ggzss/archive/2011/08/18/2145017.html

3.WIFSIGNALED()、 WTERMSIG()、WCOREDUMP()、wait()
WIFSIGNALED(status)为非0 表明进程异常终止。
若宏为真,此时可通过WTERMSIG(status)获取使得进程退出的信号编号 。
WCOREDUMP(status)检测是否生成了core文件
wait()就是得到子进程的返回码,防止子进程编程僵尸进程。
以上。
参考文档:
1.https://blog.csdn.net/yockie/article/details/51729774
2.https://www.cnblogs.com/highway-9/p/5515392.html

原文地址:https://www.cnblogs.com/lidabo/p/14004758.html