关于FreeRTOS的系统时钟节拍

不管是什么系统,运行都需要有个系统时钟节拍。xTickCount就是FreeRTOS的系统时钟节拍器。在每个滴答定时器中断中xTickCount+1,比如stm32中,具体是在delay.c中的void SysTick_Handler(void)函数,该函数是systick中断服务函数,在使用OS时用到,该函数中调用了 xPortSysTickHandler()函数,而 xPortSysTickHandler()函数中调用了xTaskIncrementTick(),而xTickCount的具体操作过程是在xTaskIncrementTick()中进行的,xTaskIncrementTick()函数是在文件tasks.c中定义的。

//systick中断服务函数,使用OS时用到
void SysTick_Handler(void)
{	
    if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
    {
        xPortSysTickHandler();	
    }
}

如上述代码所示,每次滴答定时器中断会调用 xPortSysTickHandler(),那么下来详细看下xPortSysTickHandler()函数的具体实现;

//SysTick以最低的中断优先级运行,所以当这个中断执行时所有中断都必须被隐藏;
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 ) //增加时钟计数器xTickCount的值,并获取xTaskIncrementTick() 的返回值,返回值为pdTRUE时代表需要进行任务切换;
		{
			/* A context switch is required.  Context switching is performed in
			the PendSV interrupt.  Pend the PendSV interrupt. */
			portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; //通过向中断控制和状态寄存器ICSR的bit28写入1挂起PendSV来启动PendSV中断,这样就可以在PendSV中断服务函数中进行任务切换了;
		}
	}
	vPortClearBASEPRIFromISR(); //打开中断
}

再看下 xTaskIncrementTick() 函数代码,看看具体的实现过程;

BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;

	/* Called by the portable layer each time a tick interrupt occurs.
	Increments the tick then checks to see if the new tick value will cause any
	tasks to be unblocked. */
	traceTASK_INCREMENT_TICK( xTickCount ); //跟踪增量宏,其实不产生任何代码;
	if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )  //如果任务调度器没有被挂起;uxSchedulerSuspended标志任务调度器是否被挂起,为pdTRUE时代表任务调度器被挂起,为pdFALSE时代表没有挂起,正常运行;
	{
		/* Minor optimisation.  The tick count cannot change in this
		block. */
		const TickType_t xConstTickCount = xTickCount + 1; //增加系统节拍xTickCount的值,加1后赋值给 xConstTickCount;

		/* Increment the RTOS tick, switching the delayed and overflowed
		delayed lists if it wraps to 0. */
		xTickCount = xConstTickCount; //将 xConstTickCount赋值给xTickCount,相当于给xTickCount加1;
		if( xConstTickCount == ( TickType_t ) 0U ) //当xConstTickCount 为0时,说明发生了溢出;
		{
			taskSWITCH_DELAYED_LISTS(); //交换列表,交换延时列表指针pxDelayedTaskList和溢出列表指针pxDelayedTaskList所指向的列表,并更新了xNextTaskUnblockTime的值;
		}
		else
		{
			mtCOVERAGE_TEST_MARKER(); //否则什么也不做;
		}

		/* See if this tick has made a timeout expire.  Tasks are stored in
		the	queue in the order of their wake time - meaning once one task
		has been found whose block time has not expired there is no need to
		look any further down the list. */
		if( xConstTickCount >= xNextTaskUnblockTime )  //如果系统节拍 xConstTickCount的值大于等于xNextTaskUnblockTime 的值,表示有任务需要解除阻塞,因为xNextTaskUnblockTime 里保存的是下一个解除阻塞的时间点值;
		{
			for( ;; )   //进入死循环
			{
				if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )  //判断延时列表是否为空,pdTRUE时表示延时列表为空,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为最大值
					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来更新xNextTaskUnblockTime;
						xNextTaskUnblockTime = xItemValue;
						break;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();  //什么也不做
					}

					/* It is time to remove the item from the Blocked state. */
					( void ) uxListRemove( &( pxTCB->xStateListItem ) ); //延时时间到了,先将任务从延时列表中移除,此处有点疑问,为什么删除此项任务,或者说不知道这个任务是不是一开始的 if 里面延时时间到了的任务,***如果是	xNextTaskUnblockTime里不应该更新成下次任务而不是这次任务?***

					/* 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;
							xSwitchRequired = pdTRUE;
						}
						else
						{
							mtCOVERAGE_TEST_MARKER(); //优先级低,什么也不做;
						}
					}
					#endif /* configUSE_PREEMPTION */
				}
			}
		}

		/* Tasks of equal priority to the currently running task will share
		processing time (time slice) if preemption is on, and the application
		writer has not explicitly turned time slicing off. */
		//如果使能了时间片调度的话还要处理同优先级下任务的调度
		#if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
		{
			if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
			{
				xSwitchRequired = pdTRUE; 
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		#endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */
		//如果使能了时间片钩子函数的话就执行时间片钩子函数vApplicationTickHook(),函数的具体内容由用户自己编写;
		#if ( configUSE_TICK_HOOK == 1 )
		{
			/* Guard against the tick hook being called when the pended tick
			count is being unwound (when the scheduler is being unlocked). */
			if( uxPendedTicks == ( UBaseType_t ) 0U )
			{
				vApplicationTickHook();
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		#endif /* configUSE_TICK_HOOK */
	}
	else //任务调度器被挂起
	{
		++uxPendedTicks; //它是全局变量,用uxPendedTicks来记录任务调度器挂起过程中的时钟节拍数;这样在用xTaskResumeAll()时就会调用uxPendedTicks次 xTaskIncrementTick(),xTickCount就会恢复,并且那些应该取消阻塞的任务也会取消阻塞;

		/* The tick hook gets called at regular intervals, even if the
		scheduler is locked. */
		#if ( configUSE_TICK_HOOK == 1 )
		{
			vApplicationTickHook();
		}
		#endif
	}
//有时候调用其它的API函数会使用变量 xYieldPending来标记是否是要需要进行上下文切换;
	#if ( configUSE_PREEMPTION == 1 )
	{
		if( xYieldPending != pdFALSE )
		{
			xSwitchRequired = pdTRUE;
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	#endif /* configUSE_PREEMPTION */

	return xSwitchRequired; //返回 xSwitchRequired的值,它里面保存了是否任务切换的信息,若为pdTRUE表示需要任务切换,为pdFALSE表示不需要切换任务;
}

关于traceTASK_INCREMENT_TICK()宏,其实事实上它什么也没做;它在FreeRTOS.h中定义,在FreeRTOS.h中为每个宏提供一个合适的默认值。注意:大多数跟踪宏都是在危急情况下调用的。任务调度程序经常被暂时禁用,中断可能已经被禁用。当从空闲任务中调用跟踪宏时,有许多事情是不应该做的,并且任务堆栈非常小。

所以调用xTaskIncrementTick()时会获取返回值,并根据返回值判断需不需要任务切换。
具体了解点击下方链接:
https://sourceforge.net/p/freertos/discussion/382005/thread/e57fe885/?limit=25#7a2c

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值