1. 空闲任务相关API函数
函数 | 描述 |
---|---|
portTASK_FUNCTION() | 宏定义,真正函数原型为void prvIdleTask(void * pvParameters) |
任务挂起中其它重要的API函数(介绍过的函数不列出,请参考前面的文章):
函数 | 描述 |
---|---|
prvCheckTasksWaitingTermination() | 回收等待列表xTasksWaitingTermination中任务的堆栈和任务控制块内存 |
vApplicationIdleHook() | 任务钩子函数,由用户提供 |
prvGetExpectedIdleTime() | 获取下一个唤醒任务的时钟节拍数,即获取了处理器进入低功耗模式的时长 |
2. 空闲任务基本概念
在RTOS调度器开启后,为了确保至少有一个任务执行,FreeRTOS中会在开启调度器时自动创建空闲任务。
动态方式创建任务:
//动态方式创建空闲任务
xReturn = xTaskCreate( prvIdleTask,
"IDLE",
configMINIMAL_STACK_SIZE,
( void * ) NULL,
( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
&xIdleTaskHandle );
如果使用静态方式创建任务,需要用户自己定义空闲任务所需要的内存空间,如下:
//空闲任务
static StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE];
static StaticTask_t IdleTaskTCB;
//空闲任务所需内存
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize )
{
*ppxIdleTaskTCBBuffer=&IdleTaskTCB;
*ppxIdleTaskStackBuffer=IdleTaskStack;
*pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;
}
//静态方式创建
xIdleTaskHandle = xTaskCreateStatic(prvIdleTask,
"IDLE",
ulIdleTaskStackSize,
( void * ) NULL,
( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
pxIdleTaskStackBuffer,
pxIdleTaskTCBBuffer );
说明:
- 如果一个任务删除自己,则任务先放入列表xTasksWaitingTermination,等到空闲任务时再回收堆栈和TCB内存。
- 低功耗目的尽量使MCU在空闲状态时处于低功耗模式。
- 空闲任务的优先级一般是最低的,设置优先级为0。如果有任务与空闲任务优先级相同,并且宏configIDLE_SHOULD_YIELD设置为1,则空闲任务直接让出CPU资源给其它任务。
3. 空闲任务函数portTASK_FUNCTION()
3.1 函数portTASK_FUNCTION()分析
该函数原型如下:
static portTASK_FUNCTION( prvIdleTask, pvParameters )
它是一个宏定义,如下:
#define portTASK_FUNCTION( vFunction, pvParameters ) void vFunction( void *pvParameters )
即函数原型为:void prvIdleTask(void * pvParameters)
函数源代码如下:
static portTASK_FUNCTION( prvIdleTask, pvParameters )
{
/* 防止编译器警告 */
( void ) pvParameters;
for( ;; )
{
/* 检查是否有任务删除本身(即自己删除自己),如果有释放控制块和堆栈内存 */
prvCheckTasksWaitingTermination();
/* 如果使用抢占式内核 */
#if ( configUSE_PREEMPTION == 0 )
{
taskYIELD(); /* 执行一次任务切换 */
}
#endif /* configUSE_PREEMPTION */
/*
* 相同优先级的任务会使用时间片方式获取CPU资源
* 如果设置configUSE_PREEMPTION 为1,即使用抢占式内核
* 同时,设置configIDLE_SHOULD_YIELD 为1,则与空闲任务优先级相同,空闲任务放弃CPU使用权给其它任务
*/
#if ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) )
{
/* 如果空闲任务优先级下有多个任务 */
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) ) > ( UBaseType_t ) 1 )
{
taskYIELD(); /* 执行任务切换,将CPU资源给其它任务 */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) ) */
/* 如果使能空闲任务钩子函数,需要用户提供,这样用户不增加任务开销的情况下实现后台功能,如设置CPU进入省电模式 */
#if ( configUSE_IDLE_HOOK == 1 )
{
extern void vApplicationIdleHook( void );
/* 这个函数中绝对不允许调用可能引起阻塞任务的函数 */
vApplicationIdleHook();
}
#endif /* configUSE_IDLE_HOOK */
/* 如果使能了低功耗Tickless模式功能 */
#if ( configUSE_TICKLESS_IDLE != 0 )
{
TickType_t xExpectedIdleTime;
/*
* 执行两次同样的比较(xExpectedIdleTime和configEXPECTED_IDLE_TIME_BEFORE_SLEEP)
* 第一次比较是测试一下是否达到预期的空闲时间,避免每次执行空闲任务都挂起调度器,然后再解除调度器
*/
xExpectedIdleTime = prvGetExpectedIdleTime();
if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
{
vTaskSuspendAll(); /* 挂起调度器 */
{
configASSERT( xNextTaskUnblockTime >= xTickCount );
/* 再次测试一下是否达到预期的空闲时间 */
xExpectedIdleTime = prvGetExpectedIdleTime();
if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
{
traceLOW_POWER_IDLE_BEGIN();
/*
* 调用宏,进入低功耗模式,和如何退出低功耗模式
* 系统时间补偿
* 根据MCU功耗模式编写的代码,由移植层提供
*/
portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
traceLOW_POWER_IDLE_END();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
( void ) xTaskResumeAll(); /* 恢复调度器 */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_TICKLESS_IDLE */
}
}
3.2 函数prvCheckTasksWaitingTermination()
该函数主要功能是回收等待列表xTasksWaitingTermination中任务的堆栈和任务控制块内存。当任务删除本身时(即自己删除自己),由于任务可能没执行完,不能立即释放内存空间,就先放入该列表,等到空闲任务时再删除。
函数源代码如下:
static void prvCheckTasksWaitingTermination( void )
{
/* 如果使能了任务删除功能 */
#if ( INCLUDE_vTaskDelete == 1 )
{
BaseType_t xListIsEmpty;
/* 等待删除列表中删除的任务大于0,即有需要删除的任务 */
while( uxDeletedTasksWaitingCleanUp > ( UBaseType_t ) 0U )
{
vTaskSuspendAll(); /* 调度锁开启 */
{
/* 列表xTasksWaitingTermination列表项的数量不为0,返回pdFALSE */
xListIsEmpty = listLIST_IS_EMPTY( &xTasksWaitingTermination );
}
( void ) xTaskResumeAll(); /* 调度锁关闭 */
/* 如果列表xTasksWaitingTermination有需要删除的任务 */
if( xListIsEmpty == pdFALSE )
{
TCB_t *pxTCB;
taskENTER_CRITICAL(); /* 进入临界区 */
{
/* 获取列表xTasksWaitingTermination第一个列表项,即第一个需要删除的任务 */
pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xTasksWaitingTermination ) );
/* 将任务从列表xTasksWaitingTermination移除 */
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
--uxCurrentNumberOfTasks; /* 当前系统总任务数减1 */
--uxDeletedTasksWaitingCleanUp; /* 列表xTasksWaitingTermination需要删除的任务数减1 */
}
taskEXIT_CRITICAL(); /* 退出临界区 */
prvDeleteTCB( pxTCB ); /* 根据任务堆栈和控制块申请内存的方式,释放任务的堆栈和控制块内存 */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
#endif /* INCLUDE_vTaskDelete */
}
3.3 函数prvGetExpectedIdleTime()
如果此时无其它任务执行,只有空闲任务在执行,将返回下一个唤醒任务的时钟节拍数,相当于获取了处理器进入低功耗模式的时长。
函数原型如下:
/********************************************************
参数:无
返回:TickType_t:返回下一个唤醒任务的时钟节拍数
*********************************************************/
static TickType_t prvGetExpectedIdleTime( void )
函数源代码如下:
static TickType_t prvGetExpectedIdleTime( void )
{
TickType_t xReturn;
UBaseType_t uxHigherPriorityReadyTasks = pdFALSE;
/* 选择下一个要执行的任务,0:通用方法,1:特殊方法,系统架构提供 */
#if( configUSE_PORT_OPTIMISED_TASK_SELECTION == 0 )
{
if( uxTopReadyPriority > tskIDLE_PRIORITY )
{
uxHigherPriorityReadyTasks = pdTRUE;
}
}
#else
{
/* 特殊方式选择下一个执行的任务,每一位表示一个优先级 */
const UBaseType_t uxLeastSignificantBit = ( UBaseType_t ) 0x01;
if( uxTopReadyPriority > uxLeastSignificantBit )
{
uxHigherPriorityReadyTasks = pdTRUE;
}
}
#endif
/* 如果当前优先级大于空闲任务优先级 */
if( pxCurrentTCB->uxPriority > tskIDLE_PRIORITY )
{
xReturn = 0;
}
/* 空闲任务优先级下,不止空闲任务一个优先级 */
else if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) ) > 1 )
{
xReturn = 0;
}
/* 有更高优先级任务就绪 */
else if( uxHigherPriorityReadyTasks != pdFALSE )
{
xReturn = 0;
}
/* 空闲任务优先级最高 */
else
{
xReturn = xNextTaskUnblockTime - xTickCount; /* 计算下一个唤醒任务的时间节拍 */
}
return xReturn;
}