一、执行过程
static portTASK_FUNCTION( prvIdleTask, pvParameters ) (1)
{
( void ) pvParameters; //防止报错
//本函数为 FreeRTOS 的空闲任务任务函数,当任务调度器启动以后空闲任务会自动
//创建
for( ;; )
{
//检查是否有任务要删除自己,如果有的话就释放这些任务的任务控制块 TCB 和
//任务堆栈的内存
prvCheckTasksWaitingTermination(); (2)
#if ( configUSE_PREEMPTION == 0 )
{
//如果没有使用抢占式内核的话就强制进行一次任务切换查看是否有其他
//任务有效,如果有使用抢占式内核的话就不需要这一步,因为只要有任
//何任务有效(就绪)之后都会自动的抢夺 CPU 使用权
taskYIELD();
}
#endif /* configUSE_PREEMPTION */
#if ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) ) (3)
{
//如果使用抢占式内核并且使能时间片调度的话,当有任务和空闲任务共享
//一个优先级的时候,并且此任务处于就绪态的话空闲任务就应该放弃本时
//间片,将本时间片剩余的时间让给这个就绪任务。如果在空闲任务优先级
//下的就绪列表中有多个用户任务的话就执行这些任务。
if( listCURRENT_LIST_LENGTH( \ (4)
&( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) )> ( UBaseType_t ) 1 )
{
taskYIELD();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
#if ( configUSE_IDLE_HOOK == 1)
{
extern void vApplicationIdleHook( void );
//执行用户定义的空闲任务钩子函数,注意!钩子函数里面不能使用任何
//可以引起阻塞空闲任务的 API 函数。
vApplicationIdleHook(); (5)
}
#endif /* configUSE_IDLE_HOOK */
//如果使能了 Tickless 模式的话就执行相关的处理代码
#if ( configUSE_TICKLESS_IDLE != 0 ) (6)
{
TickType_t xExpectedIdleTime;
xExpectedIdleTime = prvGetExpectedIdleTime(); (7)
if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP ) (8)
{
vTaskSuspendAll(); (9)
{
//调度器已经被挂起,重新采集一次时间值,这次的时间值可以
//使用
configASSERT( xNextTaskUnblockTime >= xTickCount );
xExpectedIdleTime = prvGetExpectedIdleTime(); (10)
if( xExpectedIdleTime >=\
configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
{
traceLOW_POWER_IDLE_BEGIN();
portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ); (11)
traceLOW_POWER_IDLE_END();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
( void ) xTaskResumeAll(); (12)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_TICKLESS_IDLE */
}
}
1.检查是否有自杀任务的内存需要释放(被其它任务删除的任务,删除的时候就会释放TCB等创建任务时分配的空间)
2.检查是否使用抢占式内核,若没使用就调用taskYIELD()函数(此函数用于自主放出cpu控制权,给其他已就绪的用户任务)
3.若使用了抢占式内核,并且configIDLE_SHOULD_YIELD等于1,那么空闲任务就把CPU使用权让给同优先级的其他任务。
4.是否使能钩子任务,若使能,则调用。
5.看是否使能了Tickless(低功耗)模式,若使能,则配置为低功耗模式。
二、 作用
1.确保内核一直有任务能够执行
空闲任务是由内核在调用函数vTaskStartScheduler()启动任务调度时自动创建的一个优先级最低的函数,当用户任务都处于阻塞状态时,内核就可以去运行空闲任务。空闲任务只有两个状态:运行和就绪。
2.清除已销毁任务所占的内存空间
当我们调用vTaskDelete()函数将任务销毁后,该任务的TCB和栈不会同时释放,只有运行空闲任务时,空闲任务会去清理内存。
3.执行空闲任务钩子函数
使能后的空闲任务的钩子函数在每个空闲任务执行周期内都会被调用,此函数常用于,执行低优先级功能代码;测试系统处理裕量;将处理器配置到低功耗模式。
要想执行空闲任务的钩子函数,还需要先将FreeRTOSConfig.h文件中的宏configUSE_IDLE_HOOK配置为1
4.空闲任务期间进入低功耗省电模式
因为没有用户任务需要执行占用CPU,所以在被使能的情况下可以配置进入低功耗模式。
三、空闲任务钩子函数详解
1. 钩子函数
FreeRTOS 中有多个钩子函数,钩子函数类似回调函数,当某个功能(函数)执行的时候就会调用钩子函数,至于钩子函数的具体内容那就由用户来编写。如果不需要使用钩子函数的话就什么也不用管,钩子函数是一个可选功能,可以通过宏定义来选择使用哪个钩子函数,可选的钩子函数如下表所示
四、使用空闲任务的钩子函数打印所有任务的栈信息
/* Hook prototypes */
void vApplicationIdleHook(void);
/* USER CODE BEGIN 2 */
void vApplicationIdleHook( void )
{
/* vApplicationIdleHook() will only be called if configUSE_IDLE_HOOK is set
to 1 in FreeRTOSConfig.h. It will be called on each iteration of the idle
task. It is essential that code added to this hook function never attempts
to block in any way (for example, call xQueueReceive() with a block time
specified, or call vTaskDelay()). If the application makes use of the
vTaskDelete() API function (as this demo application does) then it is also
important that vApplicationIdleHook() is permitted to return to its calling
function, because it is the responsibility of the idle task to clean up
memory allocated by the kernel to any task that has since been deleted. */
int i;
/*将任务列表的信息写入一个缓冲区pcWriteBuffer。*/
vTaskList(pcWriteBuffer);
for (i = 0; i < 16; i++)
printf("-");
printf("\n\r");
/*通过printf函数将任务列表信息打印出来,格式为”%s\n\r"*/
printf("%s\n\r", pcWriteBuffer);
}
/* USER CODE END 2 */
注意:
要使用vTaskList()函数,需要使能FreeRTOSConfig.h 中如下两个宏:
configUSE_TRACE_FACILITY 和 configUSE_STATS_FORMATTING_FUNCTIONS
要使用空闲任务的钩子函数,也需要使能宏USE_IDLE_HOOK
串口输出信息:
[15:57:47.800] ----------------
任务名称 任务状态 优先级 栈剩余空间 任务号
[15:57:47.803] IDLE X 0 60 5
[15:57:47.806] GameTask B 24 14 2
[15:57:47.810] ColorTask B 24 91 4
[15:57:47.817] LightTask B 24 59 3
[15:57:47.820] Tmr Svc B 2 165 6
[15:57:47.822] MPU6050Task B 24 54 8
[15:57:47.826] platform_task B 24 58 10
[15:57:47.826] InputTask B 24 90 9
五.注意事项
如果使能了空闲任务钩子函数的话就执行这个钩子函数,空闲任务钩子函数的函数名为 vApplicationIdleHook(),这个函数需要用户自行编写!在编写这个这个钩子函数的时候一定不能调用任何可以阻塞空闲任务的 API 函数。