FreeRTOS 删除任务
概述
任务的删除使用的 API 为:
void vTaskDelete( TaskHandle_t xTask );
任务删除主要是两种情况:
-
自删除,即在任务本身的 TaskCode 中调用 vTaskDelete(null)删除自身。
-
强制删除,即在其他任务中,删除另一个任务。
在 FreeRTOS 创建任务的 Static 版本 一节中介绍了创建任务需要的两块存储空间,根据创建函数时使用的 API,在调用 vTaskDelete() 后,对应存储资源被释放的时机有以下区别:
1)使用 xTaskCreate()
创建的任务,调用 vTaskDelete() 后,对应的存储资源将在 idle task(freertos 中系统自带的一个 task,我们将在后续小节中介绍它) 中被自动释放。因此,使用xTaskCreate()创建的任务,删除任务后若要立即分配资源,应当稍作延时,给予 idle task 一些回收资源的时间。否则可能不能更好的分配资源,也可能加重资源碎片的风险。
2)使用 xTaskCreateStatic()
静态创建的任务,存储资源并不会被释放,需要手动释放。
需求及功能解析
本小节主要介绍删除任务的方法:
1)自删除:
static void task2_process(void *arg)
{
static const char *TASK2_TAG = "TASK2";
while (1) {
ESP_LOGI(TASK2_TAG, "task2_flag = %d, arg2 = %s", task2_flag, (char *)arg);
task2_flag++;
if(task2_flag > 5) {
break;
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
vTaskDelete(NULL);
}
2)强制删除:
// Use the handle to delete the task.
if(task3 != NULL) {
vTaskDelete(task3);
}
删除任务时的常规考虑因素
删除任务实际要考虑的事情很多,因为任务删除时可能出现的后遗症问题:
- 占用共享资源未被释放,可能影响其他任务的运行
- 通信关系的上家(即发送消息或者数据的任务或 ISR)没有处理到位,导致上家的消息或者数据累积
- 通信关系的下家(即接受消息或者数据的任务)得不到消息或者数据,导致下家无法正常运行
- 申请的资源未释放、赋值的变量未复位
因此,如果该任务占用了共享资源、申请了新资源,则删除前必须复位、释放资源。 - 该任务有关联的任务或ISR,删除前请考虑对他们的影响.
- 在强制删除一个任务的时候,可以先获取下 task 的state,若该任务正在挂起或者延时,则可以尝试删除之。
总之,删除任务一定要本着“干干净净的来,干干净净的走“,推荐使用自删除的方式。
双核 ESP32 删除任务时的注意事项
删除固定到另一个 CPU 内核的任务时,该任务的内存总是由另一个内核的空闲任务(即 idle task)释放(因为需要清除FPU寄存器)。
删除当前在另一个核心上运行的任务时,会在另一个核心上触发让步(yield),任务的内存会被其中一个空闲任务释放(取决于任务的核心关联性)。
应避免对当前正在另一个内核上运行的任务调用vTaskDelete()。这是因为很难知道另一个内核上当前运行的任务正在执行什么,因此可能会导致不可预测的行为,例如:
1)删除包含互斥对象的任务
2)删除尚未释放之前分配的内存的任务
在可能的情况下,应该设计他们的应用程序,使vTaskDelete()只在已知状态下的任务上被调用。例如:
1)任务执行完成后会自动删除(通过vTaskDelete(NULL)),并已清理任务中使用的所有资源。
2)任务在被另一个任务删除之前(通过vTaskSuspend())将自身置于挂起状态。
这里介绍了很多,对初学者不必关心所有的方面,在开发中遇到这些问题时可以再看一下这些总结。
示例解析
示例输出:
this is esp32 chip with 2 CPU core(s), WiFi/BT/BLE, Minimum free heap size: 294424 bytes
I (336) TASK2: task2_flag = 0, arg2 = 2
I (336) TASK3: task3_flag = 0, arg3 = 3
I (1336) TASK2: task2_flag = 1, arg2 = 2
I (2336) TASK2: task2_flag = 2, arg2 = 2
I (3336) TASK2: task2_flag = 3, arg2 = 2
I (4336) TASK2: task2_flag = 4, arg2 = 2
I (5336) TASK2: task2_flag = 5, arg2 = 2
如上所述,无论是自删除还是强制删除,任务删除后,将停止运行。
讨论
删除任务与挂起任务的异同
上一节介绍了任务的挂起与恢复。这里删除任务、挂起任务做一些对比:
相同点:
- 删除任务、挂起任务后,任务都将停止执行。
不同点:
-
删除任务后,任务的资源就被释放了,包括任务的状态、局部变量等信息;挂起任务则会保留任务的资源以及与任务相关的信息。
-
删除任务后,若想让任务继续执行,需要重新创建任务,且重新创建的任务会重新从 TaskCode 的第一行代码执行。挂起的任务只需要重新恢复执行就可以重新执行,且从 TaskCode 的暂停处的代码继续执行。另外,重新创建任务相比恢复任务,前者更加消耗时间。
不同删除模式对应的资源释放机制
- 任务删除自身,则在空闲任务运行之前,不会删除用于保存该任务的数据结构和堆栈的内存。
- 如果一个任务删除了另一个任务,则会立即释放要删除的任务的数据结构和堆栈。
从创建-删除模式 到 停止-重启模式
某些危急情况下不建议使用创建-删除模式来执行某个功能,然后删除执行此功能的任务。如果你总是怀疑强制删除带来了当前的系统问题(比如错误总是发生在创建-删除某个任务的时机),那么请重新考虑你的架构,让这个单独的任务永远存在来控制想要实现的功能,如控制某个外围设备,需要的时候唤醒它,不需要的时候就让它在那里等待。我相信,只要这个任务足够专用化(不负责其他功能),那么它带来的开销是可以接收的,毕竟换来了系统的强大的稳定性。
总结
1)任务删除主要是两种情况:自删除、强制删除。
2)删除任务后若要立即分配资源,应当稍作延时,给予 idle task 一些回收资源的时间。否则可能不能更好的分配资源,也可能加重资源碎片的风险。
3)删除任务需要考虑资源回收、共享资源、消息通信相关的处理问题。
4)ESP32 是双核系统,应避免对当前正在另一个内核上运行的任务调用vTaskDelete()。
资源链接
1)Learning-FreeRTOS-with-esp32 系列博客介绍
2)对应示例的 code 链接 (点击直达代码仓库)