FreeRTOS任务挂起和恢复源码分析

当一个任务暂时需要停止运行,那么就可以将任务挂起,在需要运行的时候再恢复就可以了。任务恢复运行以后是接着挂起时的状态继续运行,堆栈是保留的,如果是将一个任务删除以后再重新创建,那么任务是从头开始运行。这是挂起与删除的区别。
如果使用任务挂起函数需要将INCLUDE_vTaskSuspend定义为1
接下来就看任务挂起源码:

#if ( INCLUDE_vTaskSuspend == 1 )
	void vTaskSuspend( TaskHandle_t xTaskToSuspend )
	{
	TCB_t *pxTCB;

		taskENTER_CRITICAL();
		{
			/* If null is passed in here then it is the running task that is
			being suspended. */
			pxTCB = prvGetTCBFromHandle( xTaskToSuspend );

			traceTASK_SUSPEND( pxTCB );

			/* Remove task from the ready/delayed list and place in the
			suspended list. */
			//将任务状态列表项从所在的列表中删除
			if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
			{
				taskRESET_READY_PRIORITY( pxTCB->uxPriority );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}

			/* Is the task waiting on an event also? */
			//如果在等待事件也从等待事件列表中删除
			//其实是获取了事件列表项所在的列表,如果事件列表项在列表中,则将事件列表项从列表中移除
			if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
			{
				( void ) uxListRemove( &( pxTCB->xEventListItem ) );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
			//把要挂起任务的状态列表项添加到挂起列表中
			vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xStateListItem ) );
		}
		taskEXIT_CRITICAL();

		if( xSchedulerRunning != pdFALSE )
		{
			/* Reset the next expected unblock time in case it referred to the
			task that is now in the Suspended state. */
			taskENTER_CRITICAL();
			{
				//重新计算一次下一个任务解锁时间,如果下一个解锁的任务恰好是现在要挂起的任务,不重新计算就会出错
				prvResetNextTaskUnblockTime();
			}
			taskEXIT_CRITICAL();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		if( pxTCB == pxCurrentTCB )
		{
			if( xSchedulerRunning != pdFALSE )
			{
				/* The current task has just been suspended. */
				configASSERT( uxSchedulerSuspended == 0 );
				portYIELD_WITHIN_API();//如果调度器是开启的则会立即切换到别的任务
			}
			else
			{
				/* The scheduler is not running, but the task that was pointed
				to by pxCurrentTCB has just been suspended and pxCurrentTCB
				must be adjusted to point to a different task. */
				//当挂起列表中节点数量等于当前系统的任务数量 那么就是系统中所有任务都挂起了
				if( listCURRENT_LIST_LENGTH( &xSuspendedTaskList ) == uxCurrentNumberOfTasks )
				{
					/* No other tasks are ready, so set pxCurrentTCB back to
					NULL so when the next task is created pxCurrentTCB will
					be set to point to it no matter what its relative priority
					is. */
					pxCurrentTCB = NULL;//这种情况正常使用下不存在,除非用户把所有的任务都挂起(包括空闲任务)
				}
				else
				{
					vTaskSwitchContext();//查找下一个要运行的任务
				}
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}

#endif /* INCLUDE_vTaskSuspend */

1、pxTCB = prvGetTCBFromHandle( xTaskToSuspend );根据任务句柄获取任务控制块,其实现方法在前边讲解任务删除的时候说过了。
2、uxListRemove( &( pxTCB->xStateListItem ) ) 把要删除的任务的状态列表项从所在的列表中删除(因为不管任务现在是处于什么状态,都从所在的列表中移除,后续会把该列表项插入到挂起列表中,这样就实现了让一个任务从一个状态转换为另一个状态),如果挂起任务的优先级下就绪列表是空的,那么就复位优先级。
3、if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL ) 如果任务的事件列表项挂在了列表中,那么说明该任务正在等到事件。如果该任务正在等待事件,那么将该列表项从所在列表中删除,不再等待事件。
4、vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xStateListItem ) );在第2部中将任务的状态列表项从所在的列表中移除了,现在再将任务的状态列表项添加到挂起列表中,让任务处于挂起态。
5、如果任务调度器是运行的,就重新计算下一个任务解锁时间。为什么要重新计算下一个任务解锁时间呢?假设这样一种情况:如果挂起的任务恰好是下一个要解锁的任务,而此时任务挂起了,下一个运行任务的解锁时间却没有更新,那肯定是会出问题的。重新计算下一个任务解锁时间函数如下:

static void prvResetNextTaskUnblockTime( void )
{
TCB_t *pxTCB;

	if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )//判断延时列表是否为空
	{
		/* The new current delayed list is empty.  Set xNextTaskUnblockTime to
		the maximum possible value so it is	extremely unlikely that the
		if( xTickCount >= xNextTaskUnblockTime ) test will pass until
		there is an item in the delayed list. */
		xNextTaskUnblockTime = portMAX_DELAY;//如果延时列表为空,那么下次任务解锁时间就设置为最大值
	}
	else
	{
		/* The new current delayed list is not empty, get the value of
		the item at the head of the delayed list.  This is the time at
		which the task at the head of the delayed list should be removed
		from the Blocked state. */
		//获取延时列表中第一个列表项所属的TCB,列表排序是从小到大排序的,最近解锁的任务肯定排在列表的前边
		( pxTCB ) = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
		//获取状态列表项的值,状态列表项的值表示的是延时的时间
		xNextTaskUnblockTime = listGET_LIST_ITEM_VALUE( &( ( pxTCB )->xStateListItem ) );
	}
}

6、如果挂起的是当前正在运行的任务,分两种情况,第一种情况:任务调度器是开启的,做一次任务切换。第二种情况:任务调度器是关闭的,查找下一个运行的任务。
看完了任务挂起函数再来看任务恢复函数,任务解挂函数有两个,分别是任务级函数和中断级函数。
先来看任务级恢复函数

#if ( INCLUDE_vTaskSuspend == 1 )
	void vTaskResume( TaskHandle_t xTaskToResume )
	{
	TCB_t * const pxTCB = ( TCB_t * ) xTaskToResume;//将任务句柄指针强制转换为任务控制块指针

		/* It does not make sense to resume the calling task. */
		configASSERT( xTaskToResume );

		/* The parameter cannot be NULL as it is impossible to resume the
		currently executing task. */
		//判断一下任务控制块是不是为空 是不是当前正在运行任务的控制块
		//因为运行的任务肯定不是挂起态的所以任务不能解挂自己
		if( ( pxTCB != NULL ) && ( pxTCB != pxCurrentTCB ) )//此处的写法是将程序变得更健壮
		{
			taskENTER_CRITICAL();
			{
				//判断要解挂的任务是否处于挂起态的 
				if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE )
				{
					traceTASK_RESUME( pxTCB );

					/* As we are in a critical section we can access the ready
					lists even if the scheduler is suspended. */
					( void ) uxListRemove(  &( pxTCB->xStateListItem ) );//从挂起列表中删除
					prvAddTaskToReadyList( pxTCB );//添加到就绪列表中

					/* We may have just resumed a higher priority task. */
					//判断解挂任务的优先级  如果解挂任务的优先级大于等于当前任务的优先级
					//那么进行一次任务切换
					if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
					{
						/* This yield may not cause the task just resumed to run,
						but will leave the lists in the correct state for the
						next yield. */
						taskYIELD_IF_USING_PREEMPTION();
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			taskEXIT_CRITICAL();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}

#endif /* INCLUDE_vTaskSuspend */

1、细心的大兄弟发现了任务恢复函数中获取任务控制块直接是用任务句柄强制转换的,而在任务删除、挂起等函数中任务控制块都是通过prvGetTCBFromHandle()获取的,为什么呢?我们来看prvGetTCBFromHandle的实现。

#define prvGetTCBFromHandle( pxHandle ) ( ( ( pxHandle ) == NULL ) ? ( TCB_t * ) pxCurrentTCB : ( TCB_t * ) ( pxHandle ) )

当传入NULL的时候可以得到当前任务控制块的地址。我们想一下,任务挂起可以挂起任务自己,任务删除可以删除自己,但是任务恢复就没办法恢复自己了(任务是挂起的任务都得不到运行怎么可能恢复自己呢),就像我们在睡觉,没办法自己叫醒自己。
2、if( ( pxTCB != NULL ) && ( pxTCB != pxCurrentTCB ) ) 检查传入的参数是否有效,传入任务控制块的指针既不是NULL也不是当前任务控制块的指针才是有效参数。
3、if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE ) 判断要恢复的任务是否在挂起列表中,也就是判断要恢复的任务是否是挂起态的?我们来看一下该函数的内部实现。

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

		/* Accesses xPendingReadyList so must be called from a critical
		section. */

		/* It does not make sense to check if the calling task is suspended. */
		configASSERT( xTask );

		/* Is the task being resumed actually in the suspended list? */
		//判断任务的状态列表项是否在挂起列表中
		if( listIS_CONTAINED_WITHIN( &xSuspendedTaskList, &( pxTCB->xStateListItem ) ) != pdFALSE )
		{
			/* Has the task already been resumed from within an ISR? */
			//判断任务的事件列表项是否在挂起就绪列表中
			//在任务调度器未开启的情况下,如果任务等待到了事件(比如信号量有效或队列有效),就将任务的
			//事件列表项添加到xPendingReadyList列表中
			if( listIS_CONTAINED_WITHIN( &xPendingReadyList, &( pxTCB->xEventListItem ) ) == pdFALSE )
			{
				/* Is it in the suspended list because it is in the	Suspended
				state, or because is is blocked with no timeout? */
				//判断任务的事件列表项是否属于列表,也就是判断任务是否在等待事件,如果不等待事件就返回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;
	} /*lint !e818 xTask cannot be a pointer to const because it is a typedef. */

#endif /* INCLUDE_vTaskSuspend */

该函数内部做了3次判断,
第一判断任务的状态列表项是否是在挂起列表中的?在挂起列表中有效。
第二判断任务的事件列表项是否不在挂起就绪列表中?不在挂起就绪列表中有效。
第三判断任务的事件列表项是否不属于任何列表?事件列表项不属于任何列表有效。
满足这3个条件才返回pdTRUE,也就是说满足这3个条件时一个任务是才是挂起态的。
4、( void ) uxListRemove( &( pxTCB->xStateListItem ) );把任务从所在的列表中移除(此处也就是从挂起列表中移除),
prvAddTaskToReadyList( pxTCB );将任务添加到就绪列表中。至此任务已经从挂起态转变为就绪态了。后续任务调度器调度的时候就会考虑调度该任务。
5、if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ) 如果恢复任务的优先级大于等于当前正在运行任务的优先级就执行一次任务调度。因为RTOS在任何时刻都要保证就绪态中的优先级最高的任务是运行的。
再来看中断级任务恢复函数,

#if ( ( INCLUDE_xTaskResumeFromISR == 1 ) && ( INCLUDE_vTaskSuspend == 1 ) )
	BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume )
	{
	BaseType_t xYieldRequired = pdFALSE;
	TCB_t * const pxTCB = ( TCB_t * ) xTaskToResume;//将任务句柄指针强制转换为任务控制块指针
	UBaseType_t uxSavedInterruptStatus;

		configASSERT( xTaskToResume );//断言

		/* RTOS ports that support interrupt nesting have the concept of a
		maximum	system call (or maximum API call) interrupt priority.
		Interrupts that are	above the maximum system call priority are keep
		permanently enabled, even when the RTOS kernel is in a critical section,
		but cannot make any calls to FreeRTOS API functions.  If configASSERT()
		is defined in FreeRTOSConfig.h then
		portASSERT_IF_INTERRUPT_PRIORITY_INVALID() will result in an assertion
		failure if a FreeRTOS API function is called from an interrupt that has
		been assigned a priority above the configured maximum system call
		priority.  Only FreeRTOS functions that end in FromISR can be called
		from interrupts	that have been assigned a priority at or (logically)
		below the maximum system call interrupt priority.  FreeRTOS maintains a
		separate interrupt safe API to ensure interrupt entry is as fast and as
		simple as possible.  More information (albeit Cortex-M specific) is
		provided on the following link:
		http://www.freertos.org/RTOS-Cortex-M3-M4.html */
		portASSERT_IF_INTERRUPT_PRIORITY_INVALID();//空的宏定义

		uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();//进入临界区
		{
			//检查任务是否真的被挂起
			if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE )
			{
				traceTASK_RESUME_FROM_ISR( pxTCB );

				/* Check the ready lists can be accessed. */
				if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )//检查调度器是否被挂起
				{
					/* Ready lists can be accessed so move the task from the
					suspended list to the ready list directly. */
					//解挂任务的优先级是否大于等于当前任务的优先级
					if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
					{
						xYieldRequired = pdTRUE;//进行一次任务切换
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}

					( void ) uxListRemove( &( pxTCB->xStateListItem ) );
					prvAddTaskToReadyList( pxTCB );
				}
				else
				{
					/* The delayed or ready lists cannot be accessed so the task
					is held in the pending ready list until the scheduler is
					unsuspended. */
					vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) );
				}
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );

		return xYieldRequired;
	}

#endif /* ( ( INCLUDE_xTaskResumeFromISR == 1 ) && ( INCLUDE_vTaskSuspend == 1 ) ) */

1、if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE )检查任务是否在挂起列表中。
2.if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )检查调度器是否被挂起。
如果调度器是开启的,xYieldRequired = pdTRUE;标记xYieldRequired,退出该函数做一次任务切换,
( void ) uxListRemove( &( pxTCB->xStateListItem ) );把任务从所在的列表中移除(此处也就是从挂起列表中移除),
prvAddTaskToReadyList( pxTCB );将任务添加到就绪列表中。至此任务已经从挂起态转变为就绪态了。后续任务调度器调度的时候就会考虑调度该任务。
如果调度器是未开启的,执行vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) );将任务的事件列表项添加到xPendingReadyList 列表中。在任务调度器开启函数中会处理xPendingReadyList()列表。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值