对FreeRTOS的深入理解(三)

        从这篇文章开始,就是真正去了解FreeRTOS内部较为复杂的运行模式了。可能会比较乱,请耐心观看。

        首先作者认为一切的源头要从这个xPortSysTickHandler开始说起,那么这个中断在32版本的例程中是存在于滴答定时器中断SysTick_Handler中的,在vivado sdk的freertos源码例程中存在于zynq创建的软件定时器中断中(绑定软件定时器中断即为FreeRTOS的中断)。当然,在不同的版本中,这个中断的名字不一样,32的例程中叫做xPortSysTickHandler,zynq的例程中叫做FreeRTOS_Tick_Handler,不管是什么版本,他都应该在你为FreeRTOS提供时基的那个定时器中断里。源码如下:

//M4
void xPortSysTickHandler( void )
{
	/* The SysTick runs at the lowest interrupt priority, so when this interrupt
	executes all interrupts must be unmasked.  There is therefore no need to
	save and then restore the interrupt mask value as its value is already
	known - therefore the slightly faster vPortRaiseBASEPRI() function is used
	in place of portSET_INTERRUPT_MASK_FROM_ISR(). */
	vPortRaiseBASEPRI();
	{
		/* Increment the RTOS tick. */
		if( xTaskIncrementTick() != pdFALSE )
		{
			/* A context switch is required.  Context switching is performed in
			the PendSV interrupt.  Pend the PendSV interrupt. */
			portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
		}
	}
	vPortClearBASEPRIFromISR();
}

//A9
void FreeRTOS_Tick_Handler( void )
{
	/* Set interrupt mask before altering scheduler structures.   The tick
	handler runs at the lowest priority, so interrupts cannot already be masked,
	so there is no need to save and restore the current mask value.  It is
	necessary to turn off interrupts in the CPU itself while the ICCPMR is being
	updated. */
	portCPU_IRQ_DISABLE();
	portICCPMR_PRIORITY_MASK_REGISTER = ( uint32_t ) ( configMAX_API_CALL_INTERRUPT_PRIORITY << portPRIORITY_SHIFT );
	__asm volatile (	"dsb		\n"
						"isb		\n" ::: "memory" );
	portCPU_IRQ_ENABLE();

	/* Increment the RTOS tick. */
	if( xTaskIncrementTick() != pdFALSE )
	{
		ulPortYieldRequired = pdTRUE;
	}

	/* Ensure all interrupt priorities are active again. */
	portCLEAR_INTERRUPT_MASK();
	configCLEAR_TICK_INTERRUPT();
}

当然,不同系列处理器会对中断控制等方面存在差异,这里不做深入讨论。重点应该是关注xTaskIncrementTick这里面做什么了?

        进入xTaskIncrementTick,FreeRTOS就会判断当前任务调度器是否在运行

如果调度器在运行,那么

        1、系统时间将会被记录,并且增加;

/* Minor optimisation.  The tick count cannot change in this block. */
const TickType_t xConstTickCount = xTickCount + 1;

/* Increment the RTOS tick, switching the delayed and overflowed delayed lists if it wraps to 0. */
xTickCount = xConstTickCount;

        2、如果系统时钟溢出,就需要交换任务列表中的阻塞列表,并且重新设置下一个最近要解除阻塞的任务的阻塞时间,具体是如何设置的,这里先不做具体的描述,等作者了解清楚阻塞列表运作方式,再去做讨论,其实这个和定时器那个是差不多的,到时候一并处理讲解;

#define taskSWITCH_DELAYED_LISTS()																	\
{																									\
	List_t *pxTemp;																					\
																									\
	/* The delayed tasks list should be empty when the lists are switched. */						\
	configASSERT( ( listLIST_IS_EMPTY( pxDelayedTaskList ) ) );										\
																									\
	pxTemp = pxDelayedTaskList;																		\
	pxDelayedTaskList = pxOverflowDelayedTaskList;													\
	pxOverflowDelayedTaskList = pxTemp;																\
	xNumOfOverflows++;																				\
	prvResetNextTaskUnblockTime();																	\
}

        3、当现在系统时钟xConstTickCount比下一个最新要解除阻塞任务的xNextTaskUnblockTime要大,说明我当前肯定是有任务过期了,需要解挂,把所有过期的任务统统放出来,那么这里的用的死循环for就是这个原因,可能我当前不止是一个任务解挂,有很多任务都需要解挂。具体源码如下:

		if( xConstTickCount >= xNextTaskUnblockTime )
		{
			for( ;; )
			{
				if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
				{
					/* The delayed list is empty.  Set xNextTaskUnblockTime
					to the maximum possible value so it is extremely
					unlikely that the
					if( xTickCount >= xNextTaskUnblockTime ) test will pass
					next time through. */
					xNextTaskUnblockTime = portMAX_DELAY; /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
					break;
				}
				else
				{
					/* The 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 must
					be removed from the Blocked state. */
					pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
					xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );

					if( xConstTickCount < xItemValue )
					{
						/* It is not time to unblock this item yet, but the
						item value is the time at which the task at the head
						of the blocked list must be removed from the Blocked
						state -	so record the item value in
						xNextTaskUnblockTime. */
						xNextTaskUnblockTime = xItemValue;
						break;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}

					/* It is time to remove the item from the Blocked state. */
					( void ) uxListRemove( &( pxTCB->xStateListItem ) );

					/* Is the task waiting on an event also?  If so remove
					it from the event list. */
					if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
					{
						( void ) uxListRemove( &( pxTCB->xEventListItem ) );
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}

					/* Place the unblocked task into the appropriate ready
					list. */
					prvAddTaskToReadyList( pxTCB );

					/* A task being unblocked cannot cause an immediate
					context switch if preemption is turned off. */
					#if (  configUSE_PREEMPTION == 1 )
					{
						/* Preemption is on, but a context switch should
						only be performed if the unblocked task has a
						priority that is equal to or higher than the
						currently executing task. */
						if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
						{
							xSwitchRequired = pdTRUE;
						}
						else
						{
							mtCOVERAGE_TEST_MARKER();
						}
					}
					#endif /* configUSE_PREEMPTION */
				}
			}
		}

那么for循环中的首个if,代表的情况就是我阻塞列表有且只有一个任务,解挂后阻塞列表中没有任务了。与之对应的else,就是要判断是否有多个任务需要解挂的过程,如果有他们会一遍遍进入for从阻塞列表中一个个解挂,并把它们加入到就序列表中。

        最后那个条件编译是关于抢占式任务调度是否开启。那么从阻塞列表上下来的任务的优先级如果比现在运行任务优先级高,则需要抢占当前任务运行。

        4、接下来如果你设置了抢占式调度和时间片调度,那么如下源码:

		#if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
		{
			if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
			{
				xSwitchRequired = pdTRUE;
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}

如果当前优先级的就序列表中不止一个任务,那么也需要任务的切换,因为大家需要轮着来。xSiwtchRequired就是标记着需要任务切换的标志变量。

        总的来说,如果调度器在运行,在每次滴答定时器中断都会去检查系统时间是否溢出并观察是否需要交换任务阻塞列表、将阻塞列表中过期任务解挂并放置于就序列表中、将需要任务切换的标志位按照当前情况置位

        如果任务调度器不在运行,uxPendedTicks会用记录任务调度器被挂起时丢失的节拍数。调度器不运行部分FreeRTOS处理源码如下:

else
	{
		++uxPendedTicks;

		/* The tick hook gets called at regular intervals, even if the
		scheduler is locked. */
		#if ( configUSE_TICK_HOOK == 1 )
		{
			vApplicationTickHook();
		}
		#endif
	}

        最后这个函数内还有这样一段:

	#if ( configUSE_PREEMPTION == 1 )
	{
		if( xYieldPending != pdFALSE )
		{
			xSwitchRequired = pdTRUE;
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}

目前还不知道这个 xYieldPending 是哪里来的,所以在这里先打个问号

        以上就可以知道每次在滴答定时器中断里,关于FreeRTOS的部分都在干啥,那么检查完所有这些,如果 return 的 xSwitchRequired 的值是pdTURE,那么就可以请求pendSV任务切换了。

        总结一下,在每次触发滴答定时器中断,那么FreeRTOS都会记录自己的系统时间,检查阻塞列表任务过期情况并按照实际情况放入就绪列表(过后会请求pendSV切换任务),在调度器挂起时记录挂起时间。这部分作者认为是FreeRTOS运行的主干,所有其他的定时器也好、信号量也罢,都是这个树枝主干的附属品。下期文章切入角度还未想好,欢迎大佬指点!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值