菜单

12.15 看门狗

下载

ARM 触摸屏看门狗详细使用说明

本文档依据当前 libstt_core 工程中的以下实现编写:

  • src/util/Util.h
  • src/util/Util.cpp
  • src/base/Interface.cpp
  • src/misc/misc.cpp

本文所述接口适用于当前运行库版本。其他版本的固件或 libstt_core.so 可能存在差异,部署前应使用目标工程实际携带的头文件和运行库复核。

1. 看门狗的作用

看门狗用于检测 HMI 主循环是否长时间无法运行。

正常情况下,运行库会周期性向 Linux 看门狗设备 /dev/watchdog 发送保活信号,通常称为“喂狗”。如果主循环因为死循环、同步大数据操作、死锁或其他严重阻塞而在指定时间内没有继续喂狗,硬件或内核看门狗会触发设备复位。

看门狗适合处理以下故障:

  • UI 线程进入死循环。
  • UI 事件中执行同步大查询、同步导入或同步网络调用,导致主循环长期阻塞。
  • 运行库主线程死锁或异常停止调度。
  • 进程严重异常后不再正常喂狗。

看门狗不能直接解决以下问题:

  • 某个后台线程卡死,但 UI 主循环仍正常运行。
  • 通信数据错误、数据库内容错误或业务状态错误。
  • 内存持续泄漏但程序暂时仍能运行。
  • U 盘、SD 卡写入失败。
  • 需要保证数据原子性、文件完整性或断电保护的场景。

看门狗只能在故障发生后尝试让设备复位,不能替代异步设计、业务超时、错误处理、数据库事务和数据备份。

2. 公开接口

2.1 头文件与命名空间

cpp 复制代码
#include "util/Util.h"

using stt::util::Util;

公开接口只有一个:

cpp 复制代码
static bool Util::enableWatchdog(int timeout);

客户程序不需要也不应该自行打开 /dev/watchdog、调用 ioctl 或编写喂狗线程。打开设备和周期喂狗由 HmiAppBase 内部完成。

2.2 参数说明

timeout 的单位为秒。

参数值 含义
0 将看门狗配置为关闭
1..100 配置看门狗超时时间
小于 0 参数非法,返回 false
大于 100 参数非法,返回 false

建议现场初始值使用 30 秒或 60 秒。不要未经验证直接使用 1..5 秒等很短的时间。

2.3 返回值

返回值 含义
true 参数有效,并且看门狗配置值写入成功
false 参数越界,或者 ARM 环境下配置写入失败

必须注意:返回 true 只证明配置写入成功,不证明以下事项:

  • /dev/watchdog 已经成功打开。
  • 内核接受了指定的超时时间。
  • 目标硬件一定支持看门狗。
  • 当前正在运行的看门狗已经被立即重新配置。
  • 设备一定会在设定秒数后复位。

当前实现没有公开“看门狗是否已打开”或“驱动实际采用的超时时间”查询接口。因此最终必须在 ARM 真机上验证。

3. 内部工作流程

3.1 设置阶段

调用:

cpp 复制代码
Util::enableWatchdog(30);

ARM 真机上会把字符串 "30" 写入名为 watchdog 的持久化环境配置。设置 0 时会写入字符串 "0"。

该方法本身不会直接打开 /dev/watchdog,也不会启动独立喂狗线程。

3.2 应用启动阶段

在 ARM 应用完成主要初始化、即将进入 HMI 主循环之前,HmiAppBase 会读取 watchdog 配置:

  1. 配置值大于 0 时,打开 /dev/watchdog。
  2. 使用 WDIOC_SETTIMEOUT 设置超时时间。
  3. 成功取得设备句柄后进入自动喂狗流程。
  4. 配置为 0、配置不存在或设备打开失败时,不执行自动喂狗。

如果在应用的 onInit() 或 onReady() 中设置看门狗,当前实现会在随后进入主循环前读取到新值。但不建议每次开机都重复写同一个持久化配置,通常应在设备设置页面或首次安装流程中只写一次。

如果在按钮事件、页面定时器或其他运行期回调中修改配置,当前已经打开的看门狗不会被立即重新打开或重新设置。新配置应按“下一次应用启动生效”理解。

3.3 运行阶段

运行库在 HMI 主循环中执行以下工作:

  1. 处理运行时消息和短周期任务。
  2. 执行应用注册的循环处理。
  3. 执行 LVGL 定时器处理。
  4. 主循环累计超过 10 次后调用一次 WDIOC_KEEPALIVE。

因此,看门狗实际监控的是 HMI 主循环能否继续运行。喂狗周期不是独立的精确定时器;如果某个 UI 回调阻塞了主循环,喂狗也会一起停止。

3.4 正常退出阶段

HMI 主循环正常结束后,运行库会关闭看门狗文件描述符。关闭设备后是否立即停用还受目标内核驱动的 nowayout、magic close 等策略影响,不能只根据用户态代码推断。现场应以目标固件的实际测试结果为准。

4. 最基本用法

4.1 启用 30 秒看门狗

cpp 复制代码
#include "util/Util.h"

using stt::util::Util;

bool ok = Util::enableWatchdog(30);
if (!ok) {
    // 参数错误或ARM配置写入失败
}

如果这段代码在正常运行期间由按钮触发,应提示用户重新启动应用或设备,然后再验证看门狗是否生效。

4.2 关闭看门狗

cpp 复制代码
bool ok = Util::enableWatchdog(0);
if (!ok) {
    // ARM配置写入失败
}

运行期调用 enableWatchdog(0) 只会修改持久化配置,不会关闭当前实例已经打开的 /dev/watchdog。应正常重启应用或设备,让新配置在下次启动时生效。

不要通过强制杀进程来尝试关闭看门狗。若内核启用了 nowayout,进程退出后可能因停止喂狗而触发复位。

4.3 修改超时时间

cpp 复制代码
bool ok = Util::enableWatchdog(60);

如果当前实例已经使用 30 秒看门狗,运行期写入 60 并不会保证当前设备句柄立刻变成 60 秒。按下一次应用启动采用 60 秒处理。

5. 组态页面调用示例

下面示例适合放在“系统设置”页面的按钮事件中。假设工程已经建立 T_VCHAR 变量 系统提示。

5.1 启用按钮

cpp 复制代码
#include "util/Util.h"

void FrmSystem::wMEnableWatchdog_clk_cb(uint16_t code, LvEvent e) {
    const int timeoutSeconds = 30;
    bool ok = stt::util::Util::enableWatchdog(timeoutSeconds);

    if (ok) {
        vm->setChar("系统提示", "看门狗已设为30秒,重启后生效");
    } else {
        vm->setChar("系统提示", "看门狗设置失败");
    }
}

5.2 关闭按钮

cpp 复制代码
void FrmSystem::wMDisableWatchdog_clk_cb(uint16_t code, LvEvent e) {
    bool ok = stt::util::Util::enableWatchdog(0);

    if (ok) {
        vm->setChar("系统提示", "看门狗已设为关闭,重启后生效");
    } else {
        vm->setChar("系统提示", "关闭配置写入失败");
    }
}

5.3 用户输入超时时间

cpp 复制代码
#include "util/StringUtil.h"
#include "util/Util.h"

void FrmSystem::applyWatchdog(const std::string& input) {
    int timeout = stt::util::StringUtil::safeStoi(input);

    if (timeout < 0 || timeout > 100) {
        vm->setChar("系统提示", "超时时间必须为0到100秒");
        return;
    }

    if (!stt::util::Util::enableWatchdog(timeout)) {
        vm->setChar("系统提示", "看门狗配置保存失败");
        return;
    }

    if (timeout == 0) {
        vm->setChar("系统提示", "已设置为关闭,重启后生效");
    } else {
        vm->setChar(
            "系统提示",
            "已设置为" + std::to_string(timeout) + "秒,重启后生效");
    }
}

如果输入框允许空字符串,必须先单独判断空值。safeStoi("") 通常会得到 0,若直接使用可能把“没有输入”误解释为“关闭看门狗”。

6. 超时时间选择

6.1 选择原则

超时时间应大于主线程中所有合法操作的最长阻塞时间,并保留足够余量。

可采用以下经验值:

text 复制代码
看门狗超时 >= 已知最长合法主线程阻塞时间的 2~3 倍

例如,经过实测,最慢的页面切换和本地配置读取可能阻塞 6 秒,则不应设置成 5 秒;可以从 20 秒或 30 秒开始验证。

6.2 建议范围

场景 建议初始值 说明
普通 HMI 控制项目 30 秒 兼顾恢复速度和瞬时负载
数据库、U盘、SD卡操作较多 60 秒 仍应把耗时操作改为异步
调试阶段 60..100 秒或暂时关闭 避免断点暂停时设备反复复位
已完成压力测试的封闭设备 按实测确定 不应只凭经验缩短

不建议把 100 秒当作解决卡顿的方法。如果一个同步导入需要两分钟,把看门狗改成 100 秒仍可能复位,而且界面会持续无响应。正确做法是把导入改成后台异步、分批处理,并给业务任务增加超时和进度反馈。

7. 与异步任务的关系

耗时操作应放到 AsynWorker 或当前 SDK 支持的后台任务机制中。后台线程禁止直接操作 UI 控件;应在完成回调中更新 UI,或者把结果写入组态变量。

cpp 复制代码
AsynWorker* worker = new AsynWorker(
    getRuntime()->getAppInstance(std::this_thread::get_id()));

worker->invoke([]() {
    // 后台执行文件、数据库或网络操作
    // 禁止在这里直接操作页面和控件
})->onFinished([this]() {
    // 回到应用线程后更新UI
    vm->setChar("任务状态", "处理完成");
});

worker->start();

异步任务带来两个重要结果:

  • UI 主循环可以继续运行,因此运行库可以继续喂狗。
  • 如果只有后台任务自身卡死,而 UI 仍正常,看门狗不会复位设备。

所以后台任务仍应设置网络超时、文件大小上限、数据库分页或批量上限,以及取消机制。看门狗不能替代这些业务保护。

8. 禁止的用法

8.1 不要手动喂狗掩盖主线程卡死

不要另建一个与 UI 无关的线程持续调用 /dev/watchdog。否则即使界面和业务主线程完全死锁,独立线程仍可能继续喂狗,设备将无法自动恢复。

8.2 不要在循环或定时器中反复配置

以下写法是错误的:

cpp 复制代码
void HmiApp::loop() {
    stt::util::Util::enableWatchdog(30); // 错误:反复写持久化配置
}

enableWatchdog 是配置接口,不是喂狗接口。高频调用没有必要,还可能增加持久化存储写入次数。

8.3 不要在生产设备上用断点直接测试

调试器暂停 UI 主线程后,自动喂狗会停止。达到超时时间后真机可能复位,调试连接会断开。单步调试前应在受控条件下暂时关闭看门狗并正常重启,完成调试后再重新启用。

8.4 不要用看门狗保护正在写入的数据

复位可能发生在数据库提交、CSV 写入、配方保存或参数持久化过程中。重要数据应使用临时文件、事务、原子替换、校验和恢复机制。不能因为启用了看门狗就认为数据不会损坏。

9. ARM 真机验证方法

模拟器中的 Util::enableWatchdog() 直接返回 true,但不会写配置、打开设备或产生复位。因此看门狗只能在 ARM 真机上完成最终验证。

9.1 基本验证

  1. 确认当前工程使用的头文件和真机中的 libstt_core.so 属于同一版本。
  2. 调用 Util::enableWatchdog(30),确认返回 true。
  3. 正常重启应用或设备。
  4. 连续操作页面、通信、配方、历史曲线和存储功能,确认没有误复位。
  5. 保持运行至少一个完整业务周期,观察设备稳定性。

9.2 受控复位验证

只有在测试设备上才能执行以下验证:

  1. 先备份重要参数和数据库。
  2. 确保没有进行固件升级、数据库迁移、U盘写入或设备动作。
  3. 设置一个便于测试但有余量的超时,例如 15 秒。
  4. 正常重启,使配置生效。
  5. 使用专门的测试代码阻塞 HMI 主线程,并记录开始时间。
  6. 确认设备是否在接近驱动实际超时时间后复位。
  7. 复位后删除测试代码,并恢复正式超时时间。

实验室测试代码示例:

cpp 复制代码
void FrmTest::wMWatchdogTest_clk_cb(uint16_t code, LvEvent e) {
    // 仅限无生产数据、无设备动作的专用测试机。
    // timeout=15秒时,阻塞30秒应使主循环停止喂狗并触发复位。
    stt::util::Util::sleepfor(30000);
}

禁止把这段测试代码交付到生产工程。若测试后设备没有复位,应检查设备节点、驱动支持、权限、运行库日志和固件配置,不能只根据 enableWatchdog() 的返回值判断硬件看门狗已经生效。

9.3 关闭验证

  1. 调用 Util::enableWatchdog(0)。
  2. 正常重启应用或设备。
  3. 确认设备正常运行。
  4. 如需证明已经关闭,只能在受控测试机上重复阻塞测试,并确认超过原超时时间仍不复位。

10. 常见问题与排查

10.1 调用返回 true,但设备不复位

可能原因:

  • 只在 Windows 模拟器中测试。
  • 修改配置后没有重启,当前实例仍未打开看门狗。
  • /dev/watchdog 不存在或无法打开。
  • 目标固件没有启用对应驱动。
  • 实际卡住的是后台线程,HMI 主循环仍在喂狗。
  • 测试代码没有真正阻塞 HMI 主线程。
  • 内核驱动调整或忽略了请求的超时时间。

当前库在打开 /dev/watchdog 失败时会记录 open Watchdog error。需要结合真机运行日志检查。

10.2 设备频繁无故复位

可能原因:

  • 超时时间设置过短。
  • UI 回调中存在同步数据库查询、配方导入或大文件读写。
  • UI 回调中使用长时间 sleep。
  • 主线程中存在无限循环、等待锁或同步网络重试。
  • 存储设备响应很慢,操作又在主线程执行。
  • 调试器暂停了主线程。

处理顺序:先定位主线程阻塞点并改为异步,再评估超时时间。不要只把超时时间不断调大。

10.3 设置为 0 后仍发生复位

可能原因:

  • 只修改了配置,但当前实例中的看门狗仍然打开。
  • 应用没有正常重启。
  • 系统中还有其他进程或内核机制使用看门狗。
  • 内核启用了 nowayout,已经启动的看门狗不能由普通关闭立即停用。
  • 复位原因并不是本应用看门狗,例如电源、内核崩溃或硬件故障。

10.4 长任务改成异步后仍需要看门狗吗

需要。异步可以避免正常长任务阻塞主循环,看门狗用于兜底处理整个应用主循环失去响应。两者作用不同,应同时采用。

10.5 能否在后台任务里调用 enableWatchdog

技术上该方法只写配置,不操作 UI,但不建议由普通后台任务随意修改系统级看门狗设置。应由明确的系统设置流程调用,并防止多个线程同时重复写配置。

11. 上线检查表

  • 仅在系统设置或安装流程中写一次看门狗配置。
  • timeout 在 0..100 范围内。
  • 已明确运行期修改通常要重启后采用。
  • 没有自行打开 /dev/watchdog 或另建无条件喂狗线程。
  • UI 事件中没有长时间 sleep、无限循环或同步大数据操作。
  • 数据库、U盘、SD卡和网络长任务已改为异步并设置上限。
  • 后台任务有业务超时、错误反馈和必要的取消机制。
  • 调试前已评估断点暂停导致的复位风险。
  • 已在目标 ARM 真机和目标固件上验证,不以模拟器返回值代替。
  • 已测试正常启动、正常退出、长时间运行和受控主线程阻塞。
  • 已确认复位不会导致关键设备动作进入危险状态。
  • 已为数据库和关键文件设计事务、临时文件或恢复方案。

12. 推荐结论

普通项目可先使用 30 秒:

cpp 复制代码
stt::util::Util::enableWatchdog(30);

涉及较多本地存储、数据库或外设初始化的项目,可先使用 60 秒,并通过真机压力测试确定最终值。所有可能超过数百毫秒到数秒的数据库、文件、网络和配方操作都应优先改为异步;看门狗只负责在主循环已经无法恢复时触发设备复位。

最近修改: 2026-11-04Powered by