浅谈FreeRTOS中vTaskSuspendAll()/xTaskResumeAll()临界区问题

vTaskSuspendAll()

在阅读FreeRTOS的源代码的时候,我无意中发现vTaskSuspendAll()这个函数居然没有进行临界区的保护,一开始我以为是个bug, 但仔细看一下注释,似乎这个问题已经被反馈给了作者,而作者是有意为之,这样做并没有任何问题。但其最终的原因是什么呢?

我们先来看下FreeRTOS中vTaskSuspendAll()函数的具体实现:

void vTaskSuspendAll( void )
{
    /* A critical section is not required as the variable is of type
     * BaseType_t.  Please read Richard Barry's reply in the following link to a
     * post in the FreeRTOS support forum before reporting this as a bug! -
     * https://goo.gl/wu4acr */

    /* portSOFRWARE_BARRIER() is only implemented for emulated/simulated ports that
     * do not otherwise exhibit real time behaviour. */
    portSOFTWARE_BARRIER();

    /* The scheduler is suspended if uxSchedulerSuspended is non-zero.  An increment
     * is used to allow calls to vTaskSuspendAll() to nest. */
    ++uxSchedulerSuspended;

    /* Enforces ordering for ports and optimised compilers that may otherwise place
     * the above increment elsewhere. */
    portMEMORY_BARRIER();
}

从注释中可以看到,对于BaseType_t类型的变量是不需要进行临界区保护的。似乎作者默认了BaseType_t的变量是原子操作的。其实这里可以仔细想下,如果BaseType_t在这里不是原子操作会有问题么?答案是不会。为什么?我们下面仔细分析一下:

  1. vTaskSuspendAll()这个函数是不带ISR版本的,也就是说它不可以中断上下文中被调用,这样一来uxSchedulerSuspended变量是不会在中断中被修改的。
  2. 现在假定BaseType_t不保证原子操作,那么vTaskSuspendAll()在被某个Task调用时,uxSchedulerSuspended的值有可能在没有修改完成时被切换到其它Task。
  3. 对于FreeRTOS来说,task的切换是在中断中发生的,如果vTaskSuspendAll在调用时有中断进来,中断中会对uxSchedulerSuspended这个变量的值进行判断,从而决定是否需要进行Task切换。
  4. 如果uxSchedulerSuspended的值被修改成功,中断中读到它的值大于0,那么不会进行Task切换,中断结束后返回原线程,这没有任何问题。
  5. 如果uxSchedulerSuspended的值没有改成功,中断中读到它的值等于0,进行Task切换,此时uxSchedulerSuspended的值也确实是0,而之前调用vTaskSuspendAll()的线程在此时没有被执行,也不会带来任何问题。当中断到来,调度器再切回原调有vTaskSuspendAll()的Task时,我们又来到了和第3步一样的情形。
  6. 综上,vTaskSuspendAll()并不需要进行临界区保护。

xTaskResumeAll()

再看xTaskResumeAll()这个函数,它是有临界区保护的。这又是为什么呢?注释中解释得很清楚,当调度器被挂起的时候,ISR有可能会导致某些Task从事件队列中删除,而加入到xPendingReadyList中。当调度器恢复调度时,需要把这些在xPendingReadyList中的任务加入到其对应的ready list中去。
由于有上面提到的要求,这时修改uxSchedulerSuspended就不能直接改了,因为一旦把这个值设成0,调度器就会开始工作,而上面提到的调整队列的操作就无法完成。所以在修改之前需要先进行临界区保护。

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

	/* If uxSchedulerSuspended is zero then this function does not match a
	previous call to vTaskSuspendAll(). */
	configASSERT( uxSchedulerSuspended );

	/* It is possible that an ISR caused a task to be removed from an event
	list while the scheduler was suspended.  If this was the case then the
	removed task will have been added to the xPendingReadyList.  Once the
	scheduler has been resumed it is safe to move all the pending ready
	tasks from this list into their appropriate ready list. */
	taskENTER_CRITICAL();
	{
		--uxSchedulerSuspended;

		if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
		{
			if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U )
			{
				/* Move any readied tasks from the pending list into the
				appropriate ready list. */
				while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE )
				{
					pxTCB = listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) ); /*lint !e9079 void * is used as this macro is used with timers and co-routines too.  Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
					( void ) uxListRemove( &( pxTCB->xEventListItem ) );
					( void ) uxListRemove( &( pxTCB->xStateListItem ) );
					prvAddTaskToReadyList( pxTCB );

					/* If the moved task has a priority higher than the current
					task then a yield must be performed. */
					if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
					{
						xYieldPending = pdTRUE;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}

				if( pxTCB != NULL )
				{
					/* A task was unblocked while the scheduler was suspended,
					which may have prevented the next unblock time from being
					re-calculated, in which case re-calculate it now.  Mainly
					important for low power tickless implementations, where
					this can prevent an unnecessary exit from low power
					state. */
					prvResetNextTaskUnblockTime();
				}

				/* If any ticks occurred while the scheduler was suspended then
				they should be processed now.  This ensures the tick count does
				not	slip, and that any delayed tasks are resumed at the correct
				time. */
				{
					UBaseType_t uxPendedCounts = uxPendedTicks; /* Non-volatile copy. */

					if( uxPendedCounts > ( UBaseType_t ) 0U )
					{
						do
						{
							if( xTaskIncrementTick() != pdFALSE )
							{
								xYieldPending = pdTRUE;
							}
							else
							{
								mtCOVERAGE_TEST_MARKER();
							}
							--uxPendedCounts;
						} 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();

	return xAlreadyYielded;
}
/*-----------------------------------------------------------*/

  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值