Free-RTOS任务删除函数vTaskDelete()解析
函数 vTaskDelete()
此函数用于删除已被创建的任务,被删除的任务将从就绪态任务列表、阻塞态任务列表、挂起态任务列表和事件列表中移除,要注意的是,空闲任务会负责释放被删除任务中由系统分配的内存,但是由用户在任务删除前申请的内存,则需要由用户在任务被删除前提前释放,否则将导致内存泄露。若使用此函数,需要在FreeRTOSConfig.h文件中将宏INCLUDE_vTaskDelete配置为 1。
使用vTaskDelete函数删除任务时,如果参数给的是NULL,代表删除的是任务本身,任务的任务控制块内存和任务堆栈内存是由空闲任务释放的,而若参数不是NULL,是其他任务的任务句柄时,其他任务的控制块内存和任务堆栈内存是由该任务删除函数进行释放的。vTaskDelete()函数如下:
void vTaskDelete(TaskHandle_t xTaskToDelete)
{
TCB_t *pxTCB;
taskENTER_CRITICAL();
{
/* 得到要删除的任务句柄(任务控制块)
该函数实际上是一个宏定义,并使用三目运算符,如果传入NULL则返回的是自身的任务控制块*/
pxTCB = prvGetTCBFromHandle(xTaskToDelete);
/* 将任务从任务所在任务状态列表(就绪态任务列表或阻塞态任务列表)中移除
如果移除后列表中的列表项数量为 0,那么就需要更新任务优先级记录,
因为此时系统中可能已经没有和被删除任务相同优先级的任务了 */
if (uxListRemove(&(pxTCB->xStateListItem)) == (UBaseType_t)0)
{
taskRESET_READY_PRIORITY(pxTCB->uxPriority);
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 任务是否在等待事件 */
if (listLIST_ITEM_CONTAINER(&(pxTCB->xEventListItem)) != NULL)
{
/* 将被删除任务的事件列表项,从所在事件列表中移除 */
(void)uxListRemove(&(pxTCB->xEventListItem));
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 递增 uxTaskNumber 变量以通知内核调试器重新生成任务列表。
这些操作有助于维护任务管理和调试的一致性和准确性 */
uxTaskNumber++;
/* 如果是删除自身 */
if (pxTCB == pxCurrentTCB)
{
/* 任务是无法删除任务本身的,因为需要将上下文切换到另一个任务,
于是需要将任务添加到任务待删除列表中,空闲任务将检查待删除列表,
有空闲任务释放调度器为待删除任务分配的TCB和堆栈内存空间 */
vListInsertEnd(&xTasksWaitingTermination, &(pxTCB->xStateListItem));
/* 等待删除列表+1,它用来告诉空闲任务有多少个待删除任务需要被删除 */
++uxDeletedTasksWaitingCleanUp;
/* 调试用的 */
portPRE_TASK_DELETE_HOOK(pxTCB, &xYieldPending);
}
else
{
/* 任务总数减一 */
--uxCurrentNumberOfTasks;
/* 删除给定的任务控制块 */
prvDeleteTCB(pxTCB);
/* 更新下一个任务的阻塞超时时间,因为被删除任务的阻塞超时时间已经无效
需要更新下个任务的阻塞时间 */
prvResetNextTaskUnblockTime();
}
traceTASK_DELETE(pxTCB);
}
/* 退出临界区 */
taskEXIT_CRITICAL();
/* 如果当前正在运行的任务被删除,则需要进行任务调度切换 */
if (xSchedulerRunning != pdFALSE)
{
/* 删除的是自身 */
if (pxTCB == pxCurrentTCB)
{
/* 此时任务调度器不能处于挂起状态 */
configASSERT(uxSchedulerSuspended == 0);
/* 进行任务切换,不会马上删除自身,而是会在空闲任务的时候删除 */
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
1.该函数会先获取待删除任务的任务句柄,并将任务从所处的状态列表中移除。若该任务正在等待事件,将其从事件列表中移除。
2.判断删除的是否是任务本身,如果是任务本身需要进行一些动作,因为任务本身是无法删除任务本身的,需要将任务添加到待删除任务列表中,空闲任务会检查待删除任务列表并释放由调度器分配给待删除任务的内存。
3.如果删除的不是任务本身,调用 prvDeleteTCB() 函数删除对应的任务,该函数内部会对待删除的任务内存进行释放。
4.如果删除的是当前正在运行的任务,那么调度器就需要进行任务切换,且此时调度器不能处于挂起状态。
注意:空闲任务负责释放由 RTOS 内核分配给已删除任务的内存。因此,如果应用程序调用了 vTaskDelete()删除自身,请务必确保空闲任务获得足够的微控制器处理时间。
函数prvDeleteTCB()
static void prvDeleteTCB(TCB_t *pxTCB)
{
portCLEAN_UP_TCB(pxTCB);
#if (configUSE_NEWLIB_REENTRANT == 1)
{
_reclaim_reent(&(pxTCB->xNewLib_reent));
}
#endif /* configUSE_NEWLIB_REENTRANT */
/* 当系统只支持动态内存管理时, 待删除任务所占用的内存空间是通过动态内存管理分配的,
因此只需要将内存空间通过动态内存管理释放掉即可,当系统支持静态内存管理和动态内存管理时,
则需要分情况讨论 */
#if ((configSUPPORT_DYNAMIC_ALLOCATION == 1) && (configSUPPORT_STATIC_ALLOCATION == 0) && (portUSING_MPU_WRAPPERS == 0))
{
/* 该任务是被动态分配,所以直接释放堆栈和TCB */
vPortFree(pxTCB->pxStack);
vPortFree(pxTCB);
}
/* 任务可以静态分配,也可以动态分配,所以在尝试释放内存之前,需要检查静态分配的内容 */
#elif (tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE == 1)
{
/* 待删除任务的任务控制块和任务栈都是由动态内存管理分配的 */
if (pxTCB->ucStaticallyAllocated == tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB)
{
/* 堆栈和任务控制块是动态分配的,所以直接释放 */
vPortFree(pxTCB->pxStack);
vPortFree(pxTCB);
}
/* 待删除任务的任务控制块是由动态内存管理分配的 */
else if (pxTCB->ucStaticallyAllocated == tskSTATICALLY_ALLOCATED_STACK_ONLY)
{
/* 只需要释放TCB的内存 */
vPortFree(pxTCB);
}
/* 任务栈和控制块内存都不是动态分配的,这种情况下,
待删除任务的任务控制块和任务栈空间所占用的内存 由用户管理 */
else
{
configASSERT(pxTCB->ucStaticallyAllocated == tskSTATICALLY_ALLOCATED_STACK_AND_TCB)
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
}
该函数比较简单,就是判断任务是以何种方式创建的,如果是以动态方式创建的就直接释放掉相应内存,如果是以静态方式创建的那么内存的释放应由用户自行管控。
总结
可以看到其实删除任务函数的逻辑并不复杂,当传入其他任务的句柄时将会将其从状态列表中移除,并交由prvDeleteTCB()函数进行释放内存。如果传入的是NULL时即代表删除任务本身,此时将由空闲任务负责释放被删除的任务内存,所以需要确保空闲任务能够有足够的时间去执行删除任务的内存资源释放,并且空闲任务的优先级通常设置为最低的,如果空闲任务一直得不到执行那么被删除的任务的内存也就无法得到释放了。