使用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\n", 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\n", strerror(errno));
        return;
    }

    // 如果子進程是由於某種信號退出的,捕獲該信號
    if (WIFSIGNALED(status))
    {
        int signalNum = WTERMSIG(status);
        printf("Child process was killed by signal num: %d\n", signalNum);
    }

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

    // 等待3秒鍾重新啟動子進程
    sleep(3);

    pid = fork();
    if (pid == 0)
    {
        printf("Fork new child process\n");
        // 運行子進程代碼
    	(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\n");
	int ret = 0;
	ret = childinit();
    if(true != ret)
    {
            printf("init: childinit Fail!\n");
			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!\n");
       return false;
    }
	
	return true;
}

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

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

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

    // 運行子進程代碼
    (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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM