# 引言 ESP32 作为双核 MCU,其 Wi-Fi 协议栈运行在 Core 0 的 FreeRTOS 任务中,而用户应用任务通常分布在两个核心。当用户任务与 Wi-Fi 任务共享资源(如 SPI Flash、I2C 总线或自定义互斥锁)时,优先级反转可能导致 Wi-Fi 任务被低优先级任务长时间阻塞,进而引发吞吐量骤降。本文将通过一个典型场景,演示如何系统排查并修复该问题。 # 1. 问题现象与根因分析 ## 1.1 现象描述 - Wi-Fi 吞吐量从 5 Mbps 骤降至 0.5 Mbps,且波动明显。 - 系统日志无报错,但 `esp_wifi_get_ps` 显示 Wi-Fi 处于活跃状态。 - 通过 `vTaskList` 发现高优先级任务频繁阻塞。 ## 1.2 根因:优先级反转 FreeRTOS 默认支持优先级继承,但 ESP-IDF 中 Wi-Fi 任务(优先级 23)与用户任务(如优先级 10)共享一个互斥锁时,若低优先级任务(优先级 5)持有锁,高优先级任务(优先级 20)等待,则低优先级任务会被临时提升至 20,但 Wi-Fi 任务(优先级 23)仍可能被阻塞,导致协议栈无法及时处理数据包。 更隐蔽的是,ESP32 双核下,若低优先级任务与 Wi-Fi 任务运行在不同核心,优先级继承无法跨核生效(FreeRTOS 的优先级继承仅限同核调度器),从而引发长时间反转。 # 2. 排查步骤 ## 2.1 启用内核追踪 在 `menuconfig` 中开启: ```c Component config → FreeRTOS → Kernel → Tracing → FreeRTOS system view tracing ``` 同时启用互斥锁追踪: ```c Component config → FreeRTOS → Kernel → Mutex → Include mutex priority inheritance ``` ## 2.2 添加追踪代码 在共享资源访问处添加日志: ```c // 共享资源结构体 typedef struct { SemaphoreHandle_t lock; uint32_t data; } shared_res_t; void shared_res_write(shared_res_t *res, uint32_t val) { ESP_LOGI("SHARED", "Task %s trying to lock, prio=%d", pcTaskGetName(NULL), uxTaskPriorityGet(NULL)); xSemaphoreTake(res->lock, portMAX_DELAY); ESP_LOGI("SHARED", "Task %s got lock, prio=%d", pcTaskGetName(NULL), uxTaskPriorityGet(NULL)); // 模拟长时间操作 vTaskDelay(pdMS_TO_TICKS(100)); res->data = val; xSemaphoreGive(res->lock); } ``` ## 2.3 使用 SystemView 分析 将追踪数据导入 SystemView,观察任务状态: - 若发现 Wi-Fi 任务(`wifi_task`)处于 Blocked 状态,且等待的互斥锁被低优先级任务持有,则确认反转。 - 检查核心 ID:若持有者运行在 Core 1,而 Wi-Fi 任务在 Core 0,则跨核反转。 # 3. 解决方案与代码示例 ## 3.1 方案一:使用递归互斥锁(不推荐) 递归锁可避免同一任务重复获取,但无法解决跨核问题。 ## 3.2 方案二:显式提升优先级(推荐) 在低优先级任务访问共享资源前,临时提升其优先级至 Wi-Fi 任务之上: ```c #define WIFI_TASK_PRIO 23 #define USER_TASK_PRIO 10 #define LOW_TASK_PRIO 5 void low_prio_task(void *arg) { shared_res_t *res = (shared_res_t*)arg; while(1) { // 提升优先级 vTaskPrioritySet(NULL, WIFI_TASK_PRIO + 1); shared_res_write(res, 0x1234); // 恢复优先级 vTaskPrioritySet(NULL, LOW_TASK_PRIO); vTaskDelay(pdMS_TO_TICKS(1000)); } } void app_main() { shared_res_t res = { .lock = xSemaphoreCreateMutex(), .data = 0 }; xTaskCreatePinnedToCore(low_prio_task, "low", 2048, &res, LOW_TASK_PRIO, NULL, 1); // 创建其他任务... } ``` ## 3.3 方案三:使用无锁设计或专用队列 对于 Wi-Fi 相关数据,使用 `xQueueSend` 代替共享内存,避免锁竞争。 # 4. 验证与注意事项 - 修改后,用 `vTaskList` 观察任务状态,确认 Wi-Fi 任务不再长时间阻塞。 - 吞吐量测试:使用 `iperf` 工具,连续测试 5 分钟,确保稳定。 - 注意:提升优先级需谨慎,避免高优先级任务饥饿。 - 跨核问题:若任务固定在不同核心,建议将共享资源访问任务绑定到同一核心。 # 5. 总结 优先级反转在双核环境下更隐蔽,通过内核追踪和日志分析可快速定位。推荐采用“临时提升优先级”或“队列通信”方案,并注意核心亲和性。掌握这些技巧,能显著提升 ESP32 应用的稳定性与性能。