FreeRTOS-空闲任务及钩子函数

FreeRTOS-空闲任务及钩子函数

  • FreeRTOS中空闲任务是开启任务调度器自动创建的一个任务,这样可以保证系统中有任务可以运行,这个任务优先级是最低的,如果有其他任务处于就绪态,那么空闲任务就会主动让出CPU使用权给其他任务。那么空闲任务只是为了保证系统中有任务可以运行吗?其实不是,空闲任务其实有两个重要功能,其一,如果某任务调用了vTaskDelete()将自身删除了,那么这个任务的任务控制块和任务堆栈不会同时释放,而是在空闲任务中释放掉,所以空闲任务可以理解为清理垃圾的。其二,由于不是所有的RTOS都有Tickless模式,所以在空闲任务期间可以用来进入低功耗模式,这种方法使用与几乎所以的RTOS系统。下面来看一下空闲任务创建过程及相关函数。

空闲任务创建及函数

vTaskStartScheduler()

  • 空闲任务是在开启任务调度器中创建的,函数定义如下。
void vTaskStartScheduler( void )
{
    
    
BaseType_t xReturn;

	
	#if( configSUPPORT_STATIC_ALLOCATION == 1 )
	{
    
    
		StaticTask_t *pxIdleTaskTCBBuffer = NULL;
		StackType_t *pxIdleTaskStackBuffer = NULL;
		uint32_t ulIdleTaskStackSize;

	
		vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );
		xIdleTaskHandle = xTaskCreateStatic(	prvIdleTask,
												"IDLE",
												ulIdleTaskStackSize,
												( void * ) NULL,
												( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
												pxIdleTaskStackBuffer,
												pxIdleTaskTCBBuffer ); 
		
		xReturn = xTaskCreate(	prvIdleTask,
								"IDLE", configMINIMAL_STACK_SIZE,
								( void * ) NULL,
								( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
								&xIdleTaskHandle ); 
	}
	#endif /* configSUPPORT_STATIC_ALLOCATION */
   //其余部分省略
   ...
   ...
	( void ) xIdleTaskHandle;
}
  • 从上面空闲任务创建可以看出,空闲任务函数名为prvIdleTask,接下来我们看一下该函数中进行了什么操作。

prvIdleTask()

  • 实际上,在寻找该函数的时候是找不到的,因为该函数是通过宏定义来实现的,其定义如下。
#define portTASK_FUNCTION( vFunction, pvParameters ) void vFunction( void *pvParameters )

  • 真正调用的函数是portTASK_FUNCTION(),函数定义如下。
static portTASK_FUNCTION( prvIdleTask, pvParameters )
{
    
    
	/* Stop warnings. */
	( void ) pvParameters;
	for( ;; )
	{
    
    
		prvCheckTasksWaitingTermination();----1

		#if ( configUSE_PREEMPTION == 0 )----2
		{
    
    
			taskYIELD();
		}
		#endif /* configUSE_PREEMPTION */

		#if ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) )----3
		{
    
    
			if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) ) > ( UBaseType_t ) 1 )----4
			{
    
    
				taskYIELD();
			}
			else
			{
    
    
				mtCOVERAGE_TEST_MARKER();
			}
		}
		#endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) ) */

		#if ( configUSE_IDLE_HOOK == 1 )----5
		{
    
    
			extern void vApplicationIdleHook( void );
			vApplicationIdleHook();
		}
		#endif /* configUSE_IDLE_HOOK */


		#if ( configUSE_TICKLESS_IDLE != 0 )
		{
    
    
		TickType_t xExpectedIdleTime;

			xExpectedIdleTime = prvGetExpectedIdleTime();----6

			if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )----7
			{
    
    
				vTaskSuspendAll();
				{
    
    

					configASSERT( xNextTaskUnblockTime >= xTickCount );
					xExpectedIdleTime = prvGetExpectedIdleTime();----8

					if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )----9
					{
    
    
						traceLOW_POWER_IDLE_BEGIN();
						portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
						traceLOW_POWER_IDLE_END();
					}
					else
					{
    
    
						mtCOVERAGE_TEST_MARKER();
					}
				}
				( void ) xTaskResumeAll();
			}
			else
			{
    
    
				mtCOVERAGE_TEST_MARKER();
			}
		}
		#endif /* configUSE_TICKLESS_IDLE */
	}
}
  1. 检查是否有已经删除的函数没有释放内存,该函数会在下面分析
  2. 如果没有使用抢占式内核,在这里进行一次任务调度
  3. 如果使用了抢占式内核,并且需要进行任务调度
  4. 判断是否有准备好的任务,如果有则进行任务调度
  5. 如果使用了钩子函数,则调用钩子函数
  6. 如果开启了低功耗模式,则计算下一次唤醒时间
  7. 判断唤醒时间是否有效,即是否大于2个时钟节拍
  8. 再次获取唤醒时间
  9. 再次判断,如果仍旧成立,则调用函数portSUPPRESS_TICKS_AND_SLEEP()进入低功耗模式
  • 从这里我们就可以知道,内存清除、钩子函数调用、低功耗模式的进入都是在空闲任务中进行的。下面来分析prvCheckTasksWaitingTermination()函数

prvCheckTasksWaitingTermination()

  • 函数定义如下。
static void prvCheckTasksWaitingTermination( void )
{
    
    
	#if ( INCLUDE_vTaskDelete == 1 )
	{
    
    
		BaseType_t xListIsEmpty;
		while( uxDeletedTasksWaitingCleanUp > ( UBaseType_t ) 0U )---1
		{
    
    
			vTaskSuspendAll();
			{
    
    
				xListIsEmpty = listLIST_IS_EMPTY( &xTasksWaitingTermination );---2
			}
			( void ) xTaskResumeAll();

			if( xListIsEmpty == pdFALSE )---3
			{
    
    
				TCB_t *pxTCB;

				taskENTER_CRITICAL();
				{
    
    
					pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xTasksWaitingTermination ) );---4
					( void ) uxListRemove( &( pxTCB->xStateListItem ) );---5
					--uxCurrentNumberOfTasks;---6
					--uxDeletedTasksWaitingCleanUp;---7
				}
				taskEXIT_CRITICAL();

				prvDeleteTCB( pxTCB );
			}
			else
			{
    
    
				mtCOVERAGE_TEST_MARKER();
			}
		}
	}
	#endif /* INCLUDE_vTaskDelete */
}
  1. 判断是否还有被删除了但没有释放内存的任务
  2. 判断等待释放内存的任务列表是否为空
  3. 如果等待释放内存的任务列表不为空
  4. 获取列表头指针
  5. 将任务从列表中移除
  6. 系统中任务数量-1
  7. 等待释放内存的任务数量-1

钩子函数

  • FreeRTOS中钩子函数如下表所示
描述
configUSE_IDLE_HOOK 空闲任务钩子函数,空闲任务执行时会调用
configSUE_TICK_HOOK 时间片钩子函数,xTaskIncrementTick()会调用该函数
configUSE_MALLOC_FAILED_HOOK 内存申请失败钩子函数,使用pvPortMalloc()申请内存失败的时候会调用该函数
configUSE_DAEMON_TASK_STARTUP_HOOK 定时器服务任务钩子函数
  • 这里我们仅就空闲任务钩子函数做一个用法示例,其他函数用法类似。使用空闲任务钩子函数的时候,当调用该函数的时候使系统进入低功耗模式,这种低功耗模式相对于tickless模式效果要差些,但是几乎适用于所有RTOS系统。其运行对比如下图所示。
    在这里插入图片描述
  • 从上面可以看出,调用空闲任务钩子函数实现的低功耗模式,在每个系统节拍处必然不在低功耗模式状态,这个即是没有其他任务处于就绪态,系统也会被反复唤醒。而FreeRTOS提供的tickless模式则要好很多。
  • 下面是通过空闲任务进入低功耗模式配置的源码
#define configUSE_IDLE_HOOK						1                       

//进入低功耗模式前的工作
void PreSleepProcessing(void)
{
    
    
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,DISABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,DISABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,DISABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD,DISABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,DISABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF,DISABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG,DISABLE);
}

//退出低功耗模式后的工作
void PostSleepProcessing(void)
{
    
    
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG,ENABLE);
}

void vApplicationIdleHook( void )
{
    
    
	__disable_irq();
	__dsb( portSY_FULL_READ_WRITE );
	__isb( portSY_FULL_READ_WRITE );
	PreSleepProcessing();
	__dsb( portSY_FULL_READ_WRITE );
	__wfi();
	__isb( portSY_FULL_READ_WRITE );
	PostSleepProcessing();
	__enable_irq();
}

  • 下面仅就vApplicationIdleHook()中的代码做一个简要分析。首先调用__disable_irq()函数关闭系统中断请求,然后调用PreSleepProcessing()做进入睡眠模式前的一些低功耗处理,这里仅关闭了GPIO的时钟,接着调用__wfi()进入WFI低功耗模式,当调用PostSleepProcessing()的时候说明已经退出低功耗模式了,然后恢复外设时钟,调用__enable_irq();开启中断请求

  • 至此就将FreeRTOS空闲任务及钩子函数讲述完了

猜你喜欢

转载自blog.csdn.net/dhejsb/article/details/120227808
今日推荐