ESP32双核下FreeRTOS任务看门狗(TWDT)误触发:从IDLE任务饿死到Core亲和性配置
1. 问题现象与背景
在ESP32双核开发中,许多开发者遇到过这样的场景:系统运行一段时间后突然重启,串口打印Task watchdog got triggered,提示某个任务(通常是IDLE)长时间未喂狗。即使代码中并未显式启用TWDT,ESP-IDF默认在启动时初始化并启用任务看门狗,监控每个CPU核心的IDLE任务。一旦IDLE任务得不到调度,TWDT便会触发panic。
本文从IDLE任务饿死的根因出发,剖析TWDT工作机制,并给出基于CPU亲和性配置的解决方案。
2. TWDT与IDLE任务的关系
2.1 任务看门狗工作原理
ESP-IDF的TWDT(Task Watchdog Timer)是一个硬件定时器,用于监控任务是否正常运行。默认配置下:
- 监控每个CPU核心的IDLE任务(
IDLE0和IDLE1)。 - 超时时间默认5秒(可配置)。
- 若IDLE任务在超时时间内未运行(即未“喂狗”),TWDT触发中断并打印错误信息,最终重启系统。
2.2 IDLE任务的作用
FreeRTOS要求每个核心至少有一个空闲任务,用于:
- 回收已删除任务的内存(清理TCB和栈)。
- 进入低功耗模式(若启用tickless idle)。
- 维持系统调度器正常运行。
IDLE任务优先级为0(最低),当有更高优先级任务就绪时,IDLE任务无法运行。
2.3 IDLE任务饿死的典型原因
-
高优先级任务忙循环:任务中无阻塞调用(如
vTaskDelay),一直占用CPU。 - 中断风暴:高频中断导致任务无法调度。
- 错误的任务亲和性配置:所有高优先级任务绑定到同一核心,导致该核心IDLE任务饿死,而另一核心空闲。
3. 复现IDLE任务饿死
以下代码创建一个高优先级任务,绑定到Core 1,且内部无阻塞,导致Core 1的IDLE1任务无法运行,TWDT触发。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_task_wdt.h"
void busy_task(void *pvParameters) {
while (1) {
// 忙等待,无阻塞
volatile int i = 0;
for (i = 0; i < 1000000; i++);
}
}
void app_main() {
// 默认TWDT已启用,监控IDLE任务
xTaskCreatePinnedToCore(busy_task, "busy_task", 4096, NULL, 10, NULL, 1);
}
运行后,约5秒后串口输出:
E (5000) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
E (5000) task_wdt: - IDLE1 (CPU 1)
E (5000) task_wdt: Tasks currently running:
E (5000) task_wdt: CPU 0: IDLE0
E (5000) task_wdt: CPU 1: busy_task
4. 解决方案:CPU亲和性与TWDT配置
4.1 合理分配任务到不同核心
避免所有高优先级任务集中在一个核心。使用xTaskCreatePinnedToCore的最后一个参数指定核心(0或1),或使用tskNO_AFFINITY让调度器自动分配。
4.2 为忙任务添加阻塞或主动喂狗
若任务必须长时间运行,可定期调用vTaskDelay(1)让出CPU,或使用esp_task_wdt_reset()手动喂狗(需先将任务添加到TWDT监控)。
4.3 调整TWDT超时或禁用IDLE监控
- 初始化时配置超时时间:
esp_task_wdt_init(10, true)(10秒超时,panic使能)。 - 禁用IDLE任务监控:
esp_task_wdt_delete(xTaskGetIdleTaskHandleForCPU(0))(不推荐,仅调试用)。
5. 完整示例:双核任务与TWDT和谐共处
以下代码创建两个任务,分别绑定到Core 0和Core 1,每个任务定期阻塞,确保IDLE任务有机会运行。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_task_wdt.h"
void task_core0(void *pvParameters) {
while (1) {
printf("Task on Core 0 running\n");
vTaskDelay(pdMS_TO_TICKS(1000)); // 阻塞1秒,让出CPU
}
}
void task_core1(void *pvParameters) {
while (1) {
printf("Task on Core 1 running\n");
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void app_main() {
// 可选:调整TWDT超时为10秒
esp_task_wdt_init(10, true);
xTaskCreatePinnedToCore(task_core0, "task_core0", 4096, NULL, 5, NULL, 0);
xTaskCreatePinnedToCore(task_core1, "task_core1", 4096, NULL, 5, NULL, 1);
}
此时代码正常运行,IDLE任务定期获得调度,TWDT不会误触发。
6. 注意事项
- 默认TWDT监控IDLE任务:ESP-IDF默认启用,且监控两个核心的IDLE任务,务必确保每个核心都有机会运行IDLE。
- 避免在中断中长时间处理:中断服务程序应尽量短小,必要时使用中断延迟处理。
-
谨慎使用
vTaskSuspendAll:挂起调度器会阻止任务切换,导致IDLE饿死。 -
调试时临时禁用TWDT:可通过
menuconfig关闭CONFIG_ESP_TASK_WDT,但量产固件应保持启用。 -
使用
esp_task_wdt_add监控自定义任务:若需监控关键任务,可将其加入TWDT,并定期调用esp_task_wdt_reset()。
通过理解TWDT与IDLE任务的关系,合理配置任务优先级和CPU亲和性,可有效避免看门狗误触发,提升系统稳定性。
暂无评论,来抢沙发~