空闲任务
在前面的学习中我们提到,空闲任务会负责释放一些被删除任务的内存,在FreeRTOS中,用户分配的内存通常也是在空闲任务中释放的。空闲任务是一个特殊的任务,当没有其他任务需要运行时,系统将会调度空闲任务来执行。在空闲任务中可以执行一些低优先级的任务,比如资源的释放和内存的回收。因此,一般建议将用户分配的内存释放操作放在空闲任务中执行。这样可以确保在系统没有其他任务需要运行时,内存得到及时释放。
空闲任务的主要职责
-
内存管理:空闲任务负责释放那些已经被删除的任务的资源,包括任务的堆栈和任务控制块(TCB)。这是通过检查是否有任务删除了自己并由空闲任务来清理资源来实现的。
-
处理空闲优先级任务:当系统中存在与空闲任务相同优先级的其他任务时,空闲任务会根据配置(configIDLE_SHOULD_YIELD)决定是否立即让出CPU。这有助于确保用户任务能够获得及时的CPU时间。
-
执行空闲任务钩子函数:用户可以定义一个空闲钩子函数(Idle Hook),这个函数在每个空闲任务周期都会被调用。这可以用于执行如进入低功耗模式等轻量级操作。
-
低功耗tickless模式:在空闲周期,FreeRTOS可以停止周期性的系统节拍中断,允许微控制器进入低功耗模式,从而节省能源。
空闲任务的创建
空闲任务是在调度器启动时自动创建的。它的创建过程涉及到为其分配一个任务堆栈和任务控制块,并将其添加到就绪列表中。空闲任务的创建代码通常如下所示:
xTaskCreate( prvIdleTask, "IDLE", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, &xIdleTaskHandle );
在这段代码中,prvIdleTask 是空闲任务的入口函数,configMINIMAL_STACK_SIZE 是任务堆栈大小,tskIDLE_PRIORITY 是任务优先级(最低),xIdleTaskHandle 是任务句柄。
而静态创建空闲函数则需要在调度器启动前由用户定义一个接口函数,为空闲任务分配任务堆栈和TCB结构体的空间,如下:
定义vApplicationGetIdleTaskMemory:
//获取空闲任务地任务堆栈和任务控制块内存,因为本例程使用的
//静态内存,因此空闲任务的任务堆栈和任务控制块的内存就应该
//有用户来提供,FreeRTOS提供了接口函数vApplicationGetIdleTaskMemory()
//实现此函数即可。
//ppxIdleTaskTCBBuffer:任务控制块内存
//ppxIdleTaskStackBuffer:任务堆栈内存
//pulIdleTaskStackSize:任务堆栈大小
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize)
{
*ppxIdleTaskTCBBuffer=&IdleTaskTCB;
*ppxIdleTaskStackBuffer=IdleTaskStack;
*pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;
}
空闲任务的实现
空闲任务的实现涉及到几个关键步骤,包括检查是否有任务需要被删除、处理与空闲任务同优先级的任务、执行空闲钩子函数,以及在适当的情况下进入低功耗模式。空闲任务的核心循环可能如下所示:
for( ;; )
{
// 检查是否有任务删除了自己
prvCheckTasksWaitingTermination();
// 如果有与空闲任务同优先级的任务,根据配置决定是否立即让出CPU
if (listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) ) > 1)
{
taskYIELD();
}
// 调用空闲钩子函数
if( configUSE_IDLE_HOOK == 1 )
{
vApplicationIdleHook();
}
// 进入低功耗模式
if( configUSE_TICKLESS_IDLE != 0 )
{
// 相关代码省略
}
}
在这个循环中,prvCheckTasksWaitingTermination 检查是否有任务需要被删除,taskYIELD 根据配置决定是否让出CPU,vApplicationIdleHook 调用用户定义的空闲钩子函数,而低功耗模式的代码则根据具体的硬件实现可能会有所不同。
如果删除的任务是使用动态方法创建的,那么该任务所用到的堆栈和任务控制块会被空闲任务自动回收,任务删除自身,其内存不会立即释放,会在执行空闲任务时释放该任务内存。 但是,有一点需要特别注意,那就是使用vTaskDelete ()函数的任务不能影响空闲任务的执行,否则空闲任务得不到执行,被删除的任务内存也就无法回收。
总的来说,空闲任务在FreeRTOS中扮演着重要的角色,它不仅负责在系统空闲时保持CPU的忙碌状态,还负责资源的清理、低功耗模式的管理以及提供一个执行用户定义轻量级操作的地方。
钩子函数
钩子函数定义
钩子函数是操作系统(FreeRTOS)满足某些功能的机制,FreeRTOS调用钩子函数,但钩子函数的实现由开发者(用户)完成。
可以将之理解成回调函数。
大部分的钩子函数都能在“FreeRTOSConfig.h”中通过宏进行剪裁。
钩子函数的种类和使用条件
FreeRTOS提供了多种钩子函数,每种钩子函数都有其特定的使用条件和目的。以下是一些常见的钩子函数及其使用条件:
-
vApplicationIdleHook():当系统空闲时,该函数被调用。要使用它,需要在FreeRTOSConfig.h中将configUSE_IDLE_HOOK设置为1,并实现void vApplicationIdleHook()函数。
-
vApplicationTickHook():该函数在每个Tick中断发生时被调用。要启用它,需要在FreeRTOSConfig.h中将configUSE_TICK_HOOK设置为1,并实现void vApplicationTickHook()函数。
-
vApplicationStackOverflowHook():当任务栈溢出时,该函数被调用。要使用它,需要在FreeRTOSConfig.h中将configCHECK_FOR_STACK_OVERFLOW设置为1或2,并实现void vApplicationStackOverflowHook(TaskHandle_t pxTask, char *pcTaskName)函数。
-
vApplicationDaemonTaskStartupHook():该函数在守护任务(如Timer服务)启动时被调用。要使用它,需要在FreeRTOSConfig.h中将configUSE_DAEMON_TASK_STARTUP_HOOK和configUSE_TIMER都设置为1,并实现void vApplicationDaemonTaskStartupHook()函数。
钩子函数的特点和注意事项
钩子函数的运行周期和使用细节因其类型而异。例如,vApplicationIdleHook()可能会在没有其他任务运行时持续被调用,类似于主函数中的无限循环。 空闲任务的钩子函数不可以调用会引起空闲任务阻塞的API函数(例如:vTaskDelay()、带有阻塞时间的队列和信号量函数),在钩子函数内部使用协程是被允许的。
vApplicationTickHook()的调用周期由configTICK_RATE_HZ决定,通常设置为1毫秒。由于该函数在Tick中断中调用,其执行时间应尽可能短,不能包含任何延迟操作。
vApplicationStackOverflowHook()只在任务栈溢出时调用,但并非所有栈溢出都会触发该钩子函数。如果栈溢出破坏了系统栈溢出检测代码的数据,栈溢出检测可能失效,导致钩子函数无法被调用。
实现钩子函数
要实现钩子函数,开发者需要在FreeRTOSConfig.h中启用相应的宏定义,如果是想实现空闲任务的钩子函数,则需要将下面的宏置位:
并提供钩子函数的具体实现。例如,如果启用了vApplicationIdleHook(),则需要在代码中定义该函数:
void vApplicationIdleHook(void) {
// 用户自定义的空闲任务代码
}
通过实现钩子函数,开发者可以在系统空闲时执行低优先级的维护任务,或在Tick中断中执行周期性的检查,或在任务栈溢出时进行错误处理和资源回收。
总的来说,FreeRTOS的钩子函数为开发者提供了一个强大的工具,用于增强系统的可靠性和灵活性。通过合理使用钩子函数,可以优化系统性能,提高任务管理的效率,并为系统的调试和维护带来便利。