FreeRTOS原理剖析:任务挂起和恢复

1. 任务挂起和恢复相关API函数

函数 描述
vTaskSuspend() 挂起一个任务,使任务处于挂起态
vTaskSuspendAll() 调度锁关闭函数,即禁止调度器调用,相当于将所有的任务挂起
vTaskResume() 释放一个挂起的任务
xTaskResumeAll() 调度锁开启函数,即恢复调度器调用,同时将列表xPendingReadyList 中的任务插入到对应的就绪表中,可与函数vTaskSuspendAll()配合,使用在一个临界区太长而不适合简单地关中断的代码段
xTaskResumeFromISR() 在中断函数中使用,释放一个挂起的任务

任务挂起中其它重要的API函数(介绍过的函数不列出,请参考前面的文章):

函数 描述
prvTaskIsTaskSuspended() 判断任务是否挂起,内部调用函数

2. 任务的挂起函数vTaskSuspend()

任务函数挂起功能是将任务插入挂起列表中,此时任务处于挂起态,调度器不能对该任务进行调度。使用此函数必须将INCLUDE_vTaskSuspend设置为1,在FreeRTOSConfig.h中定义,函数原型如下:

/********************************************************
参数:xTaskToSuspend :需要挂起任务的句柄,若为NULL,则挂起任务本身
返回:无
*********************************************************/
void vTaskSuspend( TaskHandle_t xTaskToSuspend )

函数代码如下:

void vTaskSuspend( TaskHandle_t xTaskToSuspend )
{
	TCB_t *pxTCB;

	taskENTER_CRITICAL();	/* 进入临界状态 */
	{
		/* 等到挂起任务的TCB,若参数为NULL,则挂起任务为当前正在运行的任务(即自己删除自己) */
		pxTCB = prvGetTCBFromHandle( xTaskToSuspend );

		traceTASK_SUSPEND( pxTCB );

		/* 
		 * 删除任务的状态信息,不让任务的状态列表项挂接到任何一个列表上
		 * 不同优先级的任务放在不同任务就绪列表,用一个32位变量表示就绪位,相应Bit位为1表示对应就绪表中有任务
		 * 如返回值为0,则说明该任务优先级下,任务就绪表只有它一个任务,删除了该任务,就无其他任务
		 */
		if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
		{
			/* 清除相应的就绪标志位,防止调度器调用 */
			taskRESET_READY_PRIORITY( pxTCB->uxPriority );
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		/* 判断任务是否存在事件(如信号量,队列),若存在,则将事件从相应的列表中删除 */
		if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
		{
			( void ) uxListRemove( &( pxTCB->xEventListItem ) );
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
		
		/* 将任务的状态列表项插入挂起列表的末尾 */
		vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xStateListItem ) );
	}
	taskEXIT_CRITICAL();	/* 退出临界状态 */

	/* 如果任务调度器正在运行 */
	if( xSchedulerRunning != pdFALSE )
	{
		taskENTER_CRITICAL();	/* 进入临界状态 */
		{
			/* 重置下一个任务的解锁时间,防止下一个解锁任务为刚刚挂起的任务 */
			prvResetNextTaskUnblockTime();
		}
		taskEXIT_CRITICAL();	/* 退出临界状态 */
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}

	/* 如果TCB为当前正在运行的任务 */
	if( pxTCB == pxCurrentTCB )
	{
		/* 如果任务调度器正在运行 */
		if( xSchedulerRunning != pdFALSE )
		{
			configASSERT( uxSchedulerSuspended == 0 );
			
			/* 执行一次任务切换,如果需要挂起的任务是正在运行的任务,已经将它放在挂起列表中,就会切换到其它任务 */
			portYIELD_WITHIN_API();	
		}
		else	/* 如果任务调度器关闭 */
		{
			/* 如果就绪列表的任务数量等于任务总数量,一般不会执行这里,因为至少会有一个空闲任务 */
			if( listCURRENT_LIST_LENGTH( &xSuspendedTaskList ) == uxCurrentNumberOfTasks )
			{
				pxCurrentTCB = NULL;	/* 当前运行的任务TCB执行NULL */
			}
			else
			{
				vTaskSwitchContext();	/* 获取下一个任务(分析任务切换时具体说明) */
			}
		}
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
}

说明:

  • 若函数带入的任务句柄为NULL,则删除任务本身。其中,我们可以通过函数xTaskGetHandle()来根据任务名字来获取某个任务的任务句柄。
  • 进入挂起态任务必须使用vTaskResume()或xTaskResumeFromISR()去退出挂起态。

3. 调度锁开启函数vTaskSuspendAll()

调度锁就是RTOS提供的调度器开关函数,处于调度锁开和调度锁关之间的代码执行期间是不会被高优先级的任务抢占的,即任务调度器被禁止。调度锁只是禁止了任务调度,并没有关闭任何中断,中断还是正常执行的。调度锁和和临界段不同,临界段是通过开关中断实现。

函数源代码如下:

void vTaskSuspendAll( void )
{
	++uxSchedulerSuspended;
}

说明:

  • 系统维护一个uxSchedulerSuspended计数值,当其大于0时表示禁止调度,等于0时则表示允许调度。该函数将uxSchedulerSuspended加1,即禁止任务调度器调用(也称为锁定调度器),相当于挂起了所有的任务。
  • 在禁止调度任务期间,若ISR导致了一个任务的就绪,这个任务就会放到xPendingReadyList列表中,一旦调度允许运行,则会把所有xPendingReadyList中的任务移动到一个合适的就绪列表中。
  • 禁止调度由 vTaskSuspendAll实现,打开调度由xTaskResumeAll实现。
  • 如果上下文切换请求在xTaskResumeAll()返回前得到执行,则函数返回pdTRUE,表示有更高优先级的任务需要执行,在其它情况下,xTaskResumeAll()返回pdFALSE。然后根据返回值,判断是否需要执行任务切换。
  • 调度器挂起计数器减1后,如果仍大于0,则不止一个任务使调度器挂起。

4. 任务恢复函数vTaskResume()

4.1 任务恢复函数vTaskResume()分析

该函数将挂起的函数恢复,插入到就绪表中,供调度器调用。使用此函数需将INCLUDE_vTaskSuspend设置为1,在FreeRTOSConfig.h中定义。

函数原型如下:

/********************************************************
参数:xTaskToResume :需要恢复任务的句柄,若为NULL,不恢复任何任务
返回:无
*********************************************************/
void xTaskToResume ( TaskHandle_t xTaskToResume )

源代码如下:

void vTaskResume( TaskHandle_t xTaskToResume )
{
	TCB_t * const pxTCB = ( TCB_t * ) xTaskToResume;

	configASSERT( xTaskToResume );

	/* 
	 * 任务不需要恢复当前正在运行的任务,既然任务正在运行,就说明是运行态
	 * 如果指针变量pxTCB不为空且不指向当前正在运行的任务 
	 */
	if( ( pxTCB != NULL ) && ( pxTCB != pxCurrentTCB ) )
	{
		taskENTER_CRITICAL();	/* 任务进入临界状态 */
		{
			/* 如果任务处于挂起列表和不处于挂起等待列表,且无等待事件 */
			if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE )
			{
				traceTASK_RESUME( pxTCB );

				/* 将任务从状态列表中移除,即从挂起列表中移除 */
				( void ) uxListRemove(  &( pxTCB->xStateListItem ) );
				
				/* 将任务插入就绪列表中 */
				prvAddTaskToReadyList( pxTCB );

				/* 如果获取的任务优先级比当前运行的任务优先级高 */
				if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
				{
					taskYIELD_IF_USING_PREEMPTION();	/* 执行一次任务切换 */
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		taskEXIT_CRITICAL();	/* 推出临界状态 */
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
}

4.2 函数prvTaskIsTaskSuspended()分析

该函数的参数为任务的句柄,如果任务处于挂起列表中、和不在挂起等待列表中和任务不等待任何事件,则返回pdTRUE,其它情况返回pdFALSE。

函数原型如下:

/**************************************************************
参数:xTask :任务句柄
返回:pdTRUE:任务没有等待事件,且任务处于挂起列表中和不处于挂起等待列表中
	pdFALSE:任务没有处于挂起列表或任务处于挂起等待列表或任务有等待事件
***************************************************************/
static BaseType_t prvTaskIsTaskSuspended( const TaskHandle_t xTask )

函数源代码如下:

static BaseType_t prvTaskIsTaskSuspended( const TaskHandle_t xTask )
{
	BaseType_t xReturn = pdFALSE;
	const TCB_t * const pxTCB = ( TCB_t * ) xTask;

	configASSERT( xTask );

	/* 如果任务的状态列表项处于挂起列表中,即任务处于挂起列表中 */
	if( listIS_CONTAINED_WITHIN( &xSuspendedTaskList, &( pxTCB->xStateListItem ) ) != pdFALSE )
	{
		/* 
		 * 如果任务的状态列表项不在挂起等待列表中 
		 * 任务调度器未开启,但任务获取到了相应的事件而解除了阻塞状态,就会将任务暂时插入xPendingReadyList列表
		 */
		if( listIS_CONTAINED_WITHIN( &xPendingReadyList, &( pxTCB->xEventListItem ) ) == pdFALSE )
		{
			/* 
			 * 如果任务事件列表项中成员pvContainer指向NULL,即未指向列表 
			 * 也就是说任务没有等待任何事件,则返回pdTRUE
			 */
			if( listIS_CONTAINED_WITHIN( NULL, &( pxTCB->xEventListItem ) ) != pdFALSE )
			{
				xReturn = pdTRUE;
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}

	return xReturn;
}

5. 调度锁关闭函数xTaskResumeAll()

调度锁关闭,即允许调度器使用。可以调度锁开启函数vTaskSuspendAll(),
使用于一个临界区太长而不适合简单地关中断的代码段。任务调度器禁止期间,但任务获取到了相应的事件而解除了阻塞状态,就会将任务暂时插入就绪列表xPendingReadyList列表,该函数会将列表xPendingReadyList的任务放入指定的就绪列表中。

函数原型如下:

/**************************************************************
参数:无
返回:BaseType_t : pdTRUE表示任务已经切换
				   pdFALSE表示任务未发生切换
***************************************************************/
BaseType_t xTaskResumeAll( void )

函数源代码如下:

BaseType_t xTaskResumeAll( void )
{
	TCB_t *pxTCB = NULL;
	BaseType_t xAlreadyYielded = pdFALSE;

	configASSERT( uxSchedulerSuspended );

	taskENTER_CRITICAL();	/* 进入临界区 */
	{
		 /* 
		  * 系统维护一个uxSchedulerSuspended计数值
		  * uxSchedulerSuspended > 0 时表示禁止调度
		  * uxSchedulerSuspended = 0 时表示允许调度 
		  * 调度器挂起计数器减1,若仍大于0,则不止一个任务使调度器挂起
		  */	
		--uxSchedulerSuspended;
		
		/* 如果任务调度器没有挂起,即uxSchedulerSuspended为0 */
		if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
		{
			/* 如果当前任务总数大于0 */
			if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U )
			{
				/* 
				 * 任务调度器未开启,但任务获取到了相应的事件而解除了阻塞状态,就会将任务暂时插入xPendingReadyList列表
				 * 将挂起等待列表所有任务移到对应的就绪列表中,直到等待列表为空
				 * 如果挂起等待列表不为空,则进入循环
				 */
				while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE )
				{
					/* 获取挂起等待列表的第一个列表项TCB */
					pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) );	
					
					( void ) uxListRemove( &( pxTCB->xEventListItem ) );	/* 移除任务事件 */
					( void ) uxListRemove( &( pxTCB->xStateListItem ) );	/* 移除任务状态 */
					
					prvAddTaskToReadyList( pxTCB );							/* 将任务插入就绪列表中,且置位相应的就绪位 */

					/* 如果获取的任务比当前运行的任务优先级高 */
					if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
					{
						xYieldPending = pdTRUE;		//标记需要进行任务切换
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}

				if( pxTCB != NULL )
				{
					/* 重置下一个任务的解锁时间 */
					prvResetNextTaskUnblockTime();
				}

				{
					/* 在调度器被挂起时,uxPendedTicks为丢失未处理的滴答数 */
					UBaseType_t uxPendedCounts = uxPendedTicks; 
					
					/* 如果丢失了滴答数 */
					if( uxPendedCounts > ( UBaseType_t ) 0U )
					{
						do
						{
							/* 
							 * 模拟进入系统时钟中断,若返回值为pdTRUE,则说明进行任务切换,即:补齐缺失的滴答数
							 * 保证了所有任务的延时量不会出现偏差,它们将在正确的时间被唤醒
							 */
							if( xTaskIncrementTick() != pdFALSE )
							{
								xYieldPending = pdTRUE;	/* 使能任务切换,即允许任务切换 */
							}
							else
							{
								mtCOVERAGE_TEST_MARKER();
							}
							--uxPendedCounts;	/* 将丢失的时钟数减1,保证其准确性 */
						} while( uxPendedCounts > ( UBaseType_t ) 0U );

						uxPendedTicks = 0;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}
			
				/* 若允许任务切换 */
				if( xYieldPending != pdFALSE )
				{
					#if( configUSE_PREEMPTION != 0 )	/* 如果使用抢占式的系统 */
					{
						xAlreadyYielded = pdTRUE;	/* 标记任务已经切换 */
					}
					#endif
					taskYIELD_IF_USING_PREEMPTION();	/* 使用抢占式的方式执行任务切换 */
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	taskEXIT_CRITICAL();	/* 退出临界区 */
	
	/* pdTRUE表示任务已经切换,pdFALSE表示任务未发生切换 */
	return xAlreadyYielded;
}

6. xTaskResumeFromISR()

此函数是vTaskResume()的中断版本,用于在中断服务函数中恢复一个任务,该函数的主要功能是从中断处理函数中,恢复挂起的某个任务。
若使用此函数,需要将INCLUDE_vTaskSuspend和INCLUDE_xTaskResumeFromISR都置1,INCLUDE_vTaskSuspend在FreeRTOSConfig中定义,对于INCLUDE_xTaskResumeFromISR,若没定义,则会在FreeRTOS.h中默认定义为1。

函数原型如下:

/**************************************************************
参数:xTaskToResume :需要恢复的任务句柄
返回:BaseType_t:
				pdTRUE:表示恢复任务优先级比当前运行任务优先级高(被中断打断的任务),则使能任务切换标识,即退出中断后需要执行任务切换,可手动进行任务切换
			   pdFALSE:表示恢复任务优先级比当前运行任务优先级低(被中断打断的任务),退出中断后不需要执行任务切换
***************************************************************/
BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume )

函数源代码如下:

BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume )
{
	BaseType_t xYieldRequired = pdFALSE;
	TCB_t * const pxTCB = ( TCB_t * ) xTaskToResume;
	UBaseType_t uxSavedInterruptStatus;

	configASSERT( xTaskToResume );

	portASSERT_IF_INTERRUPT_PRIORITY_INVALID();

	/*  
	 * 中断服务程序临界段,进入临界区
	 * 任务代码中和中断服务程序的临界段进入和退出是通过中断掩蔽寄存器 basepri 实现
	 * 将寄存器basepri 配置成系统调用最高中断优先级,从而掩蔽中断优先级低于系统调用最高优先级
	 */
	uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
	{
		/* 如果任务处于挂起列表和不处于挂起等待列表,且无等待事件 */
		if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE )
		{
			traceTASK_RESUME_FROM_ISR( pxTCB );	/* 调试代码,空代码 */

			/* 如果调度器正在运行 */
			if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
			{
				/* 如果恢复任务的优先级大于当前正在运行的任务优先级 */
				if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
				{
					/* 标记需要进行任务切换,该值作为函数的返回值,用户可自动执行任务切换保证任务实时性 */
					xYieldRequired = pdTRUE;	
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}

				( void ) uxListRemove( &( pxTCB->xStateListItem ) );	/* 移除恢复任务的事件 */
				prvAddTaskToReadyList( pxTCB );							/* 将要恢复任务插入到就绪列表中 */
			}
			else
			{
				/* 将恢复的任务插入挂起就绪列表末尾 */
				vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) );
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );	/* 退出临界区 */

	return xYieldRequired;	
}

你可能感兴趣的:(FreeRTOS原理剖析)