下面是对删除任务函数、空闲任务、相对延时函数的一些应用与说明。
第一节 Delete任务
任务的删除需要使用vTaskDelete()函数,主要分为两种删除情况:自删除与强制删除。
自删除表示在任务本身的执行中调用 vTaskDelete(NULL)来删除自身。
强制删除表示在其他任务BB中,删除另一个任务AA vTaskDelete(AA)。
A. 当删除一个任务时,则该任务将在FreeRTOS系统中完全消失,所以需要将任务从列表中(就绪,阻塞,挂起和事件链表)中移除。
B. 任务删除也要删除任务的信息,即任务结构体TCB。(前提任务是动态创建的,TCB与任务的栈都是动态分配的也就是说可以回收)
C. 任务的栈中还保存着任务函数那些局部变量等,任务删除一样也要释放任务的栈
D. 当一个正在运行的任务调用了vTaskDelete(NULL)删除自己时,只会将任务的从列表(就绪,阻塞,挂起和事件链表)中移除,而任务结构体TCB与栈会交给空闲任务去释放,这样才算完成任务的删除。
void vTaskDelete( TaskHandle_t xTaskToDelete ) //传入的参数是要删除任务对应的句柄
{
TCB_t *pxTCB;
taskENTER_CRITICAL(); //关闭中断
{
pxTCB = prvGetTCBFromHandle( xTaskToDelete ); //获取要删除的任务控制块,NULL代表任务自身
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ) //从就绪(延时)列表中移除任务
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL ) //从就事件列表中移除任务
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
/* Increment the uxTaskNumber also so kernel aware debuggers can
detect that the task lists need re-generating. This is done before
portPRE_TASK_DELETE_HOOK() as in the Windows port that macro will
not return. */
uxTaskNumber++;
if( pxTCB == pxCurrentTCB )
{
/*下面是任务删除自身的情况,任务删除自身时需要先放入等待删除的列表中,等切换到空闲任务时,空闲任务会将等待删除的列表中的任务控制块与堆栈进行释放*/
vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );
++uxDeletedTasksWaitingCleanUp;
portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
}
else
{
/*删除其他任务*/
--uxCurrentNumberOfTasks;
prvDeleteTCB( pxTCB ); //用来删除任务块与任务的栈空间
prvResetNextTaskUnblockTime(); //重置下一个任务的解除阻塞事件
}
traceTASK_DELETE( pxTCB );
}
taskEXIT_CRITICAL(); //打开中断
/* 如果任务删除自身,则需要下面的code来进行一次任务切换*/
if( xSchedulerRunning != pdFALSE )
{
if( pxTCB == pxCurrentTCB )
{
configASSERT( uxSchedulerSuspended == 0 );
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
第二节 空闲任务
当FreeRTOS的任务调度器开始工作后,为了保证至少有一个任务在运行,空闲任务被自动创建的目的就是要保证FreeRTOS系统中必须至少有一个任务处于运行状,空闲任务占用最低优先级。
空闲任务的主要作用:
1. 释放被删除任务的内存
如果一个任务删除自身,则任务的堆栈和任务控制块TCB需要通过空闲任务删除。所以空闲任务开始就会检查是否有任务删除了自己,空闲任务负责删除此任务的TCB和堆栈空间。(如果一个任务删除另外一个任务,被删除任务的堆栈和TCB立即释放。)
2. 处理空闲优先级任务
当使用抢占式内核调度时,如果有任务与空闲任务共享一个优先级,并且宏configIDLE_SHOULD_YIELD设置为1,那么空闲任务不必等到时间片耗尽再进行任务切换。
用户任务使用空闲优先级,空闲任务检查空闲优先级下的就绪列表中是否有多个任务,有的话则执行任务切换,让用户任务获得CPU权限。
3. 运行用户设置的空闲任务钩子函数
设置了configUSE_IDLE_HOOK == 1正常空闲任务钩子函数,这个钩子函数由用户来编写。钩子函数不可以调用会引起空闲任务阻塞的API函数(例如:vTaskDelay()、带有阻塞时间的队列和信号量函数),在钩子函数内部使用协程是被允许的。使用空闲钩子函数设置CPU进入省电模式是很常见的。
4. 支持低功耗tickless模式
第三节 相对延时函数
相对延时是指每次延时都是从执行相对延时函数vTaskDelay()开始,直到延时指定的时间。在FreeRTOS系统中延时阻塞用的非常频繁,因为低优先级的任务想要得到运行,那么高优先级的任务就必须进入阻塞或者挂起的状态,所以高优先级的任务一定会有阻塞的情况。(一般可以调用vTaskDelay()或者vTaskDelayUntil(),将CPU使用权让给低优先级的任务)。
每次系统节拍时钟中断,中断服务函数都会检查这两个延时列表,查看延时的任务是否到期,如果时间到期,则将任务从延时列表中删除,重新加入就绪列表。如果新加入就绪列表的任务优先级大于当前任务,则会触发一次上下文切换。
void vTaskDelay( const TickType_t xTicksToDelay )
{
BaseType_t xAlreadyYielded = pdFALSE;
if( xTicksToDelay > ( TickType_t ) 0U )
{
configASSERT( uxSchedulerSuspended == 0 );
vTaskSuspendAll(); //挂起任务调度器
{
traceTASK_DELAY();
prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE ); //将当前任务放入延迟列表中,经过延时时间后在移除延时列表
}
xAlreadyYielded = xTaskResumeAll(); //开启任务调度器
}
if( xAlreadyYielded == pdFALSE )
{
portYIELD_WITHIN_API(); //因为当前任务别放入了延时列表中,所以要进行任务切换来运行其他任务
}
}