ESP32双核任务看门狗超时:从任务饥饿到TWDT触发路径的逐层定位
一、问题背景:TWDT超时为何难定位
ESP32的任务看门狗(Task Watchdog Timer,TWDT)默认监控IDLE任务与用户注册任务。当被监控任务在设定周期内未“喂狗”,TWDT会打印各CPU当前运行任务与调用栈,随后复位。
难点在于:超时点往往不是根因点。真正的问题可能是某高优先级任务长期占用CPU(任务饥饿)、临界区过长、或双核间共享资源竞争导致低优先级喂狗任务得不到调度。
二、TWDT与调度器原理速览
- TWDT由定时器组驱动,周期默认5s,可配置。
- 每个被监控任务需周期性调用
esp_task_wdt_reset()。 - IDLE任务默认被监控:若某CPU的IDLE任务长时间得不到运行,说明该核被高优先级任务占满。
- FreeRTOS为抢占式调度,同优先级时间片轮转;高优先级任务不阻塞则低优先级永远得不到CPU。
- 双核下
xTaskCreatePinnedToCore决定任务绑定核,跨核竞争会放大饥饿概率。
三、触发路径逐层拆解
- 应用层:某任务进入长循环或忙等,未调用阻塞API。
- 调度层:该任务优先级高于喂狗任务,且不主动让出CPU,喂狗任务处于Ready但不可运行。
- TWDT层:定时器到期,检查各监控任务时间戳,发现超时。
-
异常层:打印
Task watchdog got triggered及各核任务列表,触发panic复位。
关键判据:日志中若显示某核当前任务为业务任务而非IDLE,且喂狗任务在同一核处于Ready态,基本可判定为任务饥饿。
四、配置步骤
-
menuconfig中开启Component config → ESP System Settings → Task Watchdog,设置周期。 - 初始化TWDT并添加需监控的任务句柄。
- 在监控任务循环内调用复位接口。
- 保留IDLE监控以检测CPU饥饿。
五、完整代码示例
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_task_wdt.h"
#include "esp_log.h"
static const char *TAG = "TWDT_DEMO";
static TaskHandle_t s_feed_task = NULL;
// 喂狗任务:低优先级,绑定CPU0
static void feed_task(void *arg)
{
esp_task_wdt_add(NULL); // 监控自身
while (1) {
esp_task_wdt_reset();
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// 饥饿制造任务:高优先级忙等,绑定同一CPU
static void hog_task(void *arg)
{
ESP_LOGW(TAG, "hog task start, will starve feed task");
volatile uint32_t x = 0;
while (1) {
x++; // 忙等,不让出CPU
if ((x & 0xFFFFFF) == 0) {
ESP_LOGI(TAG, "hog alive");
}
}
}
void app_main(void)
{
esp_task_wdt_config_t cfg = {
.timeout_ms = 5000,
.idle_core_mask = (1 << 0), // 监控CPU0的IDLE
.trigger_panic = true,
};
ESP_ERROR_CHECK(esp_task_wdt_init(&cfg));
xTaskCreatePinnedToCore(feed_task, "feed", 2048, NULL, 3, &s_feed_task, 0);
vTaskDelay(pdMS_TO_TICKS(100));
// 优先级5高于feed的3,且忙等不阻塞 → feed饥饿
xTaskCreatePinnedToCore(hog_task, "hog", 2048, NULL, 5, NULL, 0);
}
运行后约5s触发TWDT,日志会显示CPU0当前任务为hog,feed处于Ready,IDLE未运行,直接印证任务饥饿路径。
六、定位技巧
- 看日志中
CPU0/CPU1当前任务名与状态,区分“忙等”与“阻塞”。 - 用
uxTaskGetSystemState()周期性打印各任务运行计数,观察喂狗任务计数是否停滞。 - 检查临界区:
portENTER_CRITICAL内禁止长耗时操作。 - 双核场景确认喂狗任务与业务任务是否同核,必要时分离到不同核。
七、注意事项
- 喂狗任务优先级不宜过低,否则易被业务任务饿死;也不宜过高,避免掩盖真实阻塞。
- 忙等循环中插入
vTaskDelay(0)或taskYIELD()可让出CPU,但治标不治本,应重构为事件驱动。 - 临界区与关中断时间必须远小于TWDT周期。
- 调试阶段可先设
trigger_panic=false仅打印,便于抓取现场。 - 生产环境建议结合
esp_task_wdt_add精确监控关键任务,而非仅依赖IDLE监控。
暂无评论,来抢沙发~