【问题标题】:How to use the watchdog timer in a RTOS?如何在 RTOS 中使用看门狗定时器?
【发布时间】:2012-10-24 10:53:19
【问题描述】:

假设我在嵌入式环境中有一个协作调度程序。我有很多进程正在运行。我想利用看门狗定时器,以便我可以检测到进程何时因任何原因停止运行并重置处理器。

在没有 RTOS 的简单应用程序中,我总是会从主循环中接触看门狗,这总是足够的。但是,在这里,有许多进程可能会挂起。什么是定期触摸看门狗定时器同时确保每个进程都处于良好状态的干净方法?

我在想我可以为每个进程提供一个回调函数,这样它就可以让另一个监督所有进程的函数知道它还活着。回调将传递一个参数,该参数将是任务的唯一 ID,因此监督者可以确定谁在回调。

【问题讨论】:

  • 我们是在考虑作为 RTOS 一部分的看门狗还是 RTOS 服务的实际硬件看门狗计时器?

标签: c embedded rtos watchdog


【解决方案1】:

传统的方法是让看门狗进程具有尽可能低的优先级

PROCESS(watchdog, PRIORITY_LOWEST) { while(1){reset_timer(); sleep(1);} }

实际的硬件计时器可能每 3 或 5 秒重置一次 CPU。

跟踪单个进程可以通过逆向逻辑实现:每个进程都会设置一个计时器,其回调会向看门狗发送“停止”消息。然后每个进程都需要取消前一个计时器事件并在“从队列接收事件/消息”循环中的某处设置一个新事件。

PROCESS(watchdog, PRIORITY_LOWEST) {
    while(1) { 
       if (!messages_in_queue()) reset_timer();
       sleep(1);
    }
}
void wdg_callback(int event) { 
    msg = new Message();
    send(&msg, watchdog);
};
PROCESS(foo, PRIORITY_HIGH) {
     timer event=new Timer(1000, wdg_callback);
     while (1) {
        if (receive(msg, TIMEOUT)) {
           // handle msg       
        } else { // TIMEOUT expired 
           cancel_event(event);
           event = new Timer(1000,wdg_callback);
        }
     }
}

【讨论】:

  • 但这种方法的问题在于,只有当看门狗进程被饿死或 RTOS 存在更大问题时,它才会发现问题。它不会发现任何特定过程的问题。还是我错过了什么?
  • @user946230:不,你是对的。该问题已在更新中得到解决。这减少了发送的消息数量。还可以在“空闲”进程中编写看门狗功能,该进程通常具有 PID=0 并同时捕获最典型的丢失消息情况。
【解决方案2】:

一种常见的方法是将看门狗委托给特定任务(通常是最高优先级或最低优先级,每种方法的权衡/动机),然后让所有其他任务“签入”该任务。

这边:

  • 如果一个中断被挂起(100% CPU),kicker 任务将不会运行,你重置

  • 如果踢球任务被挂起,则重置

  • 如果另一个任务挂起,kicker 任务看不到签入,kicker 任务没有启动 WDG,你重置

现在当然需要考虑实施细节。有些人让每个任务在全局变量中(原子地)设置自己的专用位; kicker 任务以特定的速率检查这组位标志,并在每个人都签入时清除/重置(当然还有踢 WDG。)我避开像瘟疫这样的全局变量并避免这种方法。 RTOS 事件标志提供了一种更优雅的类似机制。

我通常将我的嵌入式系统设计为事件驱动系统。在这种情况下,每个任务都会阻塞在一个特定的位置——消息队列上。所有任务(和 ISR)通过发送事件/消息相互通信。这样,您不必担心任务不签入,因为它被信号量阻塞在“那里”(如果这没有意义,对不起,不写更多我无法更好地解释它)。

还有一个考虑因素——让任务“自主”签入,或者他们是否回复/响应来自踢球者任务的请求。自治 - 例如,每个任务每秒都会在其队列中接收一个事件“告诉 kicker 任务你还活着”。回复请求 - 每秒一次(或其他),kicker 任务告诉每个人(通过队列)“签到时间” - 最终每个任务都运行它的队列,获取请求并回复。需要考虑任务优先级、排队理论等。

有 100 种方法可以给这只猫剥皮,但负责踢 WDG 并让其他任务接续踢球任务的单个任务的基本原则是相当标准的。

至少还有一个方面需要考虑——在这个问题的范围之外——那就是处理中断。如果 ISR 占用 CPU(很好),我上面描述的方法将触发 WDG 重置,但是相反的情况呢 - ISR (遗憾地)意外地被禁用了。在许多情况下,这不会被捕获,并且您的系统仍然会踢 WDG,但您的系统的一部分已瘫痪。有趣的东西,这就是我喜欢嵌入式开发的原因。

【讨论】:

  • 我认为您要考虑的方面是仅围绕任务间消息同步任务的缺点。如果 ISR 对任务的操作至关重要,则该 ISR 应该向该任务发送一条消息(或阻塞信号量或某种其他机制),该消息在 ISR 运行之前禁止该任务。如果 ISR 停止,则该任务无法向看门狗任务发出信号,而您会捕获该事件。我猜这就是你所说的“往下走”,虽然我不确定你为什么提倡避免它。
  • 这里的另一件事是仅仅因为任务确实与 ISR 一起工作并不意味着它应该被 ISR 单独阻止。例如,通信协议任务可能依赖 UART ISR 来接收字节。但是,该任务不应仅在 UART 上阻塞等待。在这种情况下,它应该同时阻塞 UART 和定时器(或其他事件),因为正确的操作可能是周围没有人可以说话。 [1/2]
  • 另一方面,需要通过 ISR 以每秒 5-10 倍的速度读取环境温度读数的任务可能会单独阻止该 ISR,因为 SR 没有有效条件不运行。是的,您无法在通信协议案例中检测到失败的 ISR,但这是由于无法区分正确运行的不带通信的 ISR 和无法运行的 ISR。 [2/2]
  • @iheanyi -- 你的第一条评论 -- 是的,我的意思是我倾向于编写阻塞在一个地方的代码 -- 在任务循环的顶部 -- 而不是阻塞在其他地方位置(例如,信号量中可能永远不会出现的 3 层深度的函数调用)。通过在一个地方阻塞,任务可以在等待某事完成时保持对“新事物”的响应。
  • @iheanyi - 我同意任务不仅仅在 ISR 上阻塞,我希望你没有解释我写的意思。我在讨论 ISR 时的意思是,我的看门狗方法只捕获挂起的任务,而不是挂起/死的 ISR。但是仍然会接收和处理定时器超时。我不确定定时器是否始终是 UART RX 的解决方案,因为通信可能是零星的和不可预测的。如果你在低功耗下工作,你不想醒来很多次才发现每次都无事可做。我认为我们在同一页上。
【解决方案3】:

一种解决方案:

  • 每个希望被检查的线程都向看门狗线程显式注册其回调,看门狗线程维护此类回调的列表。
  • 当看门狗被调度时,它可能会迭代已注册任务的列表
  • 每个回调本身都被迭代调用,直到它返回一个健康状态。
  • 在列表的最后,硬件看门狗被踢出。

这样,任何永远不会返回健康状态的线程都会停止看门狗任务,直到发生硬件看门狗超时。

在抢占式操作系统中,看门狗线程将是最低优先级或空闲线程。在协作调度程序中,它应该在回调调用之间让步。

回调函数本身的设计取决于具体的任务及其行为和周期性。每个功能都可以根据任务的需要和特点进行定制。高周期性的任务可能只是增加一个计数器,当回调被调用时它被设置为零。如果计数器在进入时为零,则自上次看门狗检查以来任务没有调度。具有低或非周期性行为的任务可能会对其调度进行时间戳记,如果该任务尚未在某个指定时间段内调度,则回调可能会返回失败。可以以这种方式监视任务和中断处理程序。此外,由于向看门狗注册是线程的责任,因此您可能有一些线程根本没有注册。

【讨论】:

    【解决方案4】:

    每个任务都应该有自己的模拟看门狗。只有当所有模拟的看门狗都没有超时时,真正的看门狗才会被高优先级的实时任务喂食。

    即:

    void taskN_handler()
    {
        watchdog *wd = watchdog_create(100); /* Create an simulated watchdog with timeout of 100 ms */
        /* Do init */
        while (task1_should_run)
        {
            watchdog_feed(wd); /* feed it */
            /* do stuff */
        }
        watchdog_destroy(wd); /* destroy when no longer necessary */
    }
    
    void watchdog_task_handler()
    {
        int i;
        bool feed_flag = true;
        while(1)
        {
            /* Check if any simulated watchdog has timeout */
            for (i = 0; i < getNOfEnabledWatchdogs(); i++) 
            {
                if (watchogHasTimeout(i)) {
                       feed_flag = false;
                       break;
                }
             }
    
             if (feed_flag)
                 WatchdogFeedTheHardware();
    
             task_sleep(10);
    }
    

    现在,可以说系统受到了真正的保护,不会出现死机,甚至不会出现部分死机,而且大多数情况下不会出现不需要的看门狗触发。

    【讨论】:

      【解决方案5】:

      其他答案已经涵盖了您的问题,我只想建议您在旧程序中添加一些内容(没有 RTOS)。不要只从 main() 无条件地踢看门狗,有可能某些 ISR 会卡住,但系统会在没有通知的情况下继续工作(Dan 提到的问题也与 RTOS 有关)。

      我一直在做的是将主中断和定时器中断相关联,以便在定时器内对变量进行倒计时,直到它为零,然后从主中断检查它是否为零,然后才喂看门狗。当然,喂食后将变量返回初始值。很简单,如果变量停止递减,你就会得到重置。如果 main 停止为看门狗供电,您将得到复位。

      这个概念很容易只应用于已知的周期性事件,但它仍然比只从主要做所有事情要好。另一个好处是乱码不太可能踢看门狗,因为您在主程序中的看门狗馈送过程已在某个疯狂循环中结束。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-07-29
        • 2019-03-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多