Bootstrap

FreeRTOS原理剖析:空闲任务分析

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;
}

;