【嵌入式操作系统-6】延时队列

任务状态

任务状态

typedef enum
{
	eRunning = 0,	/* A task is querying the state of itself, so must be running. */
	eReady,			/* The task being queried is in a read or pending ready list. */
	eBlocked,		/* The task being queried is in the Blocked state. */
	eSuspended,		/* The task being queried is in the Suspended state, or is in the Blocked state with an infinite time out. */
	eDeleted,		/* The task being queried has been deleted, but its TCB has not yet been freed. */
	eInvalid		/* Used as an 'invalid state' value. */
} eTaskState;
  • freertos中存在以上几种状态,其中就绪,阻塞,挂起,和删除都会放到对应的队列中,就绪任务被CPU执行时为运行态。

内核链表

操作系统中的状态转换是通过将tcb从一个双向循环链表转移到另一个双向循环链表中实现的,在freertos中存在如下队列:

/*< Prioritised ready tasks. */
static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
/*< Delayed tasks. */
static List_t xDelayedTaskList1;
/*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
static List_t xDelayedTaskList2;
/*< Points to the delayed task list currently being used. */
static List_t * volatile pxDelayedTaskList;
/*< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */
static List_t * volatile pxOverflowDelayedTaskList;
/*< Tasks that have been readied while the scheduler was suspended.  They will be moved to the ready list when the scheduler is resumed. */
static List_t xPendingReadyList;

#if( INCLUDE_vTaskDelete == 1 )
	/*< Tasks that have been deleted - but their memory not yet freed. */
	PRIVILEGED_DATA static List_t xTasksWaitingTermination;				
	PRIVILEGED_DATA static volatile UBaseType_t uxDeletedTasksWaitingCleanUp = ( UBaseType_t ) 0U;

#endif

#if ( INCLUDE_vTaskSuspend == 1 )
	/*< Tasks that are currently suspended. */
	PRIVILEGED_DATA static List_t xSuspendedTaskList;
#endif

  • pxReadyTasksLists 是具有优先级的就绪队列,freertos的最高优先级为多少,就会有多少个就绪队列,调度器会从其中查找出当前优先级最高的就绪任务,然后由CPU执行,当CPU运行该任务时,任务由就绪态转为运行态。

  • xDelayedTaskList1 和 xDelayedTaskList2 用于延时任务的唤醒,延时时间不能是0xFFFFFFFF,该值代表无限等待,操作系统会将无限等待的任务放入到 xSuspendedTaskList 链表中,将非无限等待的任务放入到 xDelayedTaskList1 或 xDelayedTaskList2 中。

  • pxDelayedTaskList 指向 xDelayedTaskList1 或者 xDelayedTaskList2, 它当前正在使用的延时队列,如果当前CPU时钟节拍值加上延迟时间之后没有溢出,那么该任务会被添加到 pxDelayedTaskList 中(插入后会根据唤醒的时间进行排序),如果计数值溢出,那么该任务会被添加到 pxOverflowDelayedTaskList,每个定时器中断触发时,如果该队列中存在节点,会从里面取出第一个节点,判断该任务是否需要唤醒,如果需要唤醒则将其从延时队列移出到就绪队列中。

  • pxOverflowDelayedTaskList指向 xDelayedTaskList1 或者 xDelayedTaskList2,当计算出的任务唤醒时间已经溢出的时候会被添加到该队列中,直到CPU时钟节拍溢出为0时,在定时器中断中会将pxDelayedTaskList 和 pxOverflowDelayedTaskList 进行交换,这样原本添加到 pxOverflowDelayedTaskList 的任务在计时溢出后仍然可以继续正常运行。

  • xPendingReadyList 当调度器挂起时,所有就绪状态的任务会移出到该队列中,调度器恢复时再将任务从该队列移出到就绪队列中。

  • xTasksWaitingTermination 队列用于保存了被 vTaskDelete 函数删除的任务,vTaskDelete 运行后,该任务就会移出其他队列并添加到 xTasksWaitingTermination 队列中,idle任务运行时会将其使用的TCB和 stack 资源进行回收。

  • xSuspendedTaskList 用于保存被挂起和无限等待的任务。

任务等待

延时队列的作用

  • 将需要延时的任务添加到延时队列并记录他们的唤醒时间,并在延时结束后重新将他们恢复运行,在被延时的任务停止运行之后,其他优先级较低的任务得以运行,在宏观上看起来就像是多个任务同时执行。

双延时队列

操作系统使用变量记录时钟节拍,硬件定时器每产生一次中断便加一,无论是32位还是64位,该变量总有溢出的时候,判断任务是否需要唤醒的依据就是当前时钟节拍计数值大于延时队列中任务的下一次唤醒时间,如果下一次唤醒时间是在溢出之后,这种情况下无法进行判断,如果将溢出前后的任务分别放入两个队列则可以避免该问题。

  • freertos 中提供了两个任务延时函数:
void vTaskDelay( const TickType_t xTicksToDelay );
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement );
  • vTaskDelay 下一次唤醒时刻为:vTaskDelay 被调用时的当前时钟计数值加上延迟节拍数目,如果xTicksToDelay 等于0xFFFFFFFF,那么该任务将被放入 xSuspendedTaskList,此时相当于将该任务挂起,只有使用 vTaskResume 才能使该任务恢复运行。

  • vTaskDelayUntil 下一次唤醒时刻为:pxPreviousWakeTime + xTimeIncrement,在未发生计数溢出的情况下只有当该值大于当前时钟节拍时才会进入blocked状态,如果在此期间发生计数溢出,下一次唤醒时间必须大于当前时钟节拍计数值且小于pxPreviousWakeTime,否则不会被阻塞。

  • 任务在调用需要等待的函数时,如果其等待时间不是无限等待,那么将会通过如下方式计算出下一次的唤醒时间,此处不考虑使用 vTaskDelayUntil 的情况,以 vTaskDelay 为例:

void vTaskDelay( const TickType_t xTicksToDelay )
{
	/* ... */
	prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
	/* ... */
}

static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely )
{
	TickType_t xTimeToWake;
	const TickType_t xConstTickCount = xTickCount;

	/* 将任务从就绪队列移出 */
	if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
	{
		/* 当前正在运行的任务一定在就绪队列中,不需要检查其是否在就绪队列中,然后重置优先级 */
		portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority ); 
	}

	#if ( INCLUDE_vTaskSuspend == 1 )
	{
		if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) )
		{
			/* 等待时间为 portMAX_DELAY 的任务被放入到挂起队列中 */
			vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) );
		}
		else
		{
			/* 计算下一次唤醒时间 */
			xTimeToWake = xConstTickCount + xTicksToWait;

			/* 将下次唤醒时间作为队列排序的值,也就是说队列插入时会自动根据从小到大进行排序,头节点值最小 */
			listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );

			if( xTimeToWake < xConstTickCount )
			{
				/* 计算出的唤醒时间在计数值溢出之后,则将其添加到溢出延时队列中 */
				vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
			}
			else
			{
				/* 如果唤醒时间不在溢出之后则直接将该任务放入当前正在使用的延时队列中 */
				vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );

				if( xTimeToWake < xNextTaskUnblockTime )
				{
					xNextTaskUnblockTime = xTimeToWake;
				}
			}
		}
	}
	#endif /* INCLUDE_vTaskSuspend */
}
  • 当延时时间不为0时,将会把任务从就绪队列移出,然后计算下一次唤醒时间,将该时间值作为队列排序值,将溢出前的延时任务放入到 pxDelayedTaskList 指向的队列中,将溢出后的延时任务放入到 pxOverflowDelayedTaskList 指向的队列中,加入队列时会根据唤醒时间的大小进行排序,这样确保了在未溢出和溢出后需要唤醒的任务都按照唤醒时间进行了有序排列。
void xPortSysTickHandler( void )
{
	portDISABLE_INTERRUPTS();
	{
		if( xTaskIncrementTick() != pdFALSE )
		{
			portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
		}
	}
	portENABLE_INTERRUPTS();
}

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

	if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
	{
		/* Minor optimisation.  The tick count cannot change in this
		block. */
		const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1;

		/* 时钟节拍计数值加1 */
		xTickCount = xConstTickCount;

		if( xConstTickCount == ( TickType_t ) 0U ) /* 判断计数值是否发生溢出 */
		{
			taskSWITCH_DELAYED_LISTS();
		}
		
		/* ... */
	}

	/* ... */
	
	return xSwitchRequired;
}

#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();																	\
}
  • 在定时器中断检测到计数值溢出时会将两个指针进行交换,完成延时队列的切换,从而使任务延时不被影响。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咕咚.萌西

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值