RTOS 使用看门狗策略

什么是看门狗

看门狗是一种防止故障和崩溃的机制,其本质是一种电子计时器,开始计时器后,必须要定期重置计时以防止超时,当程序跑飞或者发生故障时,因为不能定期重置计时导致计时器超时,这时看门狗会重新启动芯片。

看门狗可以用软件或者硬件来实现。两者都是通过定时器实现,定时时间内没有喂狗,就会复位芯片。软件看门狗优点在于可以通过程序改变时间,并且可以随时禁用看门狗。缺点是如果在初始化看门狗前程序就已经跑飞或者禁用看门狗后程序跑飞,这样看门狗就没有起到系统恢复的作用。硬件看门狗则是一种更强大的解决方案,一旦开启,看门狗计数器就需要由程序定期发送信号重置计时,任何系统崩溃(无论是硬件还是软件)都会导致看门狗计数器超时,这时硬件看门狗会通过向 CPU 发送复位信号来将崩溃或故障的 CPU 重启。

在裸机系统(没有操作系统)上使用看门狗相对简单,初始化启动看门狗后,实现一个功能即可重置看门狗计数器。在任何系统崩溃的故障中,计数器都不会被重置,所以看门狗也会重置系统。

上面说了裸机系统,那么在多任务的系统中使用看门狗的有效方法是什么呢?

多任务系统中的看门狗实现必须能够保证操作系统和每个应用任务的正确执行。要做到这一点,我们必须单独监控应用程序的每个任务(或至少是最重要的任务)的执行情况,并确保所有任务都按计划执行。

在 RTOS 中看门狗的优先级选择也有区别,如果看门狗任务具有较高的优先级,即使有其他任务崩溃,也可以保证它始终运行,并且也有利于我们记录崩溃的任务名称,保存便于下次排错。但是这也导致了看门狗任务会占用更多的 CPU 时间,而这些时间原本可以供给需要高优先级的任务使用。如果看门狗任务优先级较低,则可以保证它不会占用其他优先级的 CPU 时间,提高利用率,但是我们得确保其他任务阻塞时间不会超过喂狗的时间,不然将有可能导致超时而导致看门狗复位芯片,并导致 CPU 重启。

出于这些考虑,我更倾向于把看门狗的优先级抬高。

有几种方法可以实现这种监控,每种方法都有其优点和缺点。在本文中,我将介绍我通常使用的两种策略。第一种策略很简单,但没有那么稳健,也有一些不足之处。第二种策略实施起来比较费劲,但成本效益比很好(复杂性与稳健性)。

看门狗策略

我接触最多的操作系统就是 FreeRtos,所以本文我使用了 FreeRtos 的 API,但代码可以很容易地移植到其他操作系统上。

策略一

先看 Demo:

1
2
3
4
5
6
7
8
9
10
11
12
void wdog_free_task(void *pvParameters)
{
/* 初始化看门狗 */
wdog_init();

/* 1 秒喂一次狗 */
while (true)
{
vTaskDelay(1000);
wdog_free();
}
}

上面的 Demo 使用最低优先级执行周期性任务,定时喂狗。

这种实现最为简单,保证了对硬件崩溃、程序跑飞的保护,一旦发生故障,该任务得不到执行,看门狗就会因为超时而复位,同样的,它还可以防止 RTOS 任务调度器故障。并且,因为该看门狗任务最低,所以如果有其他任务在执行的过程中跑飞、崩溃等,看门狗就会因为没有喂狗而复位。

不过这个策略也有它的不足之处,它没办法保证所有的任务都按照你所理想的执行,假设我们有个定时任务从传感器获取数据,我们不能监控到这个任务是否按我们的逻辑在运行或者因为硬件或软件的 BUG 卡住了。所以,这种策略实现起来非常简单,但它不具备大多数嵌入式系统设计所需要的鲁棒性。

策略二

同样的,我们先看 Demo:

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
void task_1(void *pvParameters)
{
struct wdog_counter *wdt;

task_1_init();

wdt = wdog_count_allocate(pcTaskGetName(NULL));

while (true)
{
// 做一些事情

// 完成后任务计数值递增
wdog_count_increment(wdt);
}
}

void wdog_free_task(void *pvParameters)
{
uint32_t err;

/* 初始化看门狗 */
wdog_init();

while (true)
{
vTaskDelay(1000);
err = wdog_counter_check();
if (err == 0)
{
/* 所有任务正常运行,则正常喂狗并且清除任务计数 */
wdog_free();
wdog_counter_restart();
}
else
{
/* 有一个或多个任务发生错误,记录错误并且调用复位 */
wdog_error_log(err);
wdog_reset_system();
}
}
}

策略一因为没法监控到每个任务的运行情况,所以策略二则为了解决策略一的不足,单独监控每个任务,只有当所有被监控的任务都按我们的逻辑执行时,才会喂狗,为了监控每一个任务,我们需要给它关联一个计数器,这个计数器由任务本身定期递增,以表明它的执行一切正常。上面的 Demo 使用了一个结构体来存储任务的计数器,FreeRtos 提供了 pcTaskGetName 用以获取任务句柄所对应的任务名,当传入参数为 NULL 时表示查询自身任务的任务名,我们可以将任务名传入 wdog_count_allocate 使之与分配的计数器相关联,并且在每个任务正常运行时,调用 wdog_count_increment 来递增我们的计数值。

而看门狗任务,则定期的调用 wdog_counter_check 对每个任务的计数值是否递增来判断当下所有的任务是否在正常运行,是的话则正常喂狗并且调用 wdog_counter_restart 将任务的计数器清零。当有任务没有正常递增时,说明发生了错误,则相应的调用 wdog_error_log 将发生错误的任务进行记录,随后调用 wdog_reset_system 进行重启。

上面的看门狗相关的接口都可以封装到看门狗模块中,而任务的错误记录,则可以保存到 flash 等地方,方便下次读取,对该任务进行排错。

当然也不一定要用我上述的方法,RTOS 提供了通知的 API,同样可以使用通知的方式,周期性的通知看门狗,如果看门狗收到所有任务的通知则说明正常,正常喂狗清除计数,若某个任务发生故障,则用同样的方式进行排错处理。

结尾

上一篇文章推荐的软件被某苏州公司举报软件侵权了,所以没得,微信把文章给删了,后面多半不写这种文章了。

本文如果哪里有误,欢迎在下方留言指正,我会及时修改的!