FreeRTOS时间管理

void vTaskDelay( const TickType_t xTicksToDelay )函数是FreeRTOS中非常重要的函数,用来延时xTicksToDelay个时钟节拍。
vTaskDelay函数源码

#if ( INCLUDE_vTaskDelay == 1 )

	void vTaskDelay( const TickType_t xTicksToDelay )
	{
	BaseType_t xAlreadyYielded = pdFALSE;

		/* A delay time of zero just forces a reschedule. */
		if( xTicksToDelay > ( TickType_t ) 0U )//延时的时间是否大于0
		{
			configASSERT( uxSchedulerSuspended == 0 );//断言  如果任务调度器是挂起的调用本函数会报错。
			vTaskSuspendAll();//挂起任务调度器
			{
				traceTASK_DELAY();

				/* A task that is removed from the event list while the
				scheduler is suspended will not get placed in the ready
				list or removed from the blocked list until the scheduler
				is resumed.

				This task cannot be in an event list as it is the currently
				executing task. */
				prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );//将任务添加到延时列表
			}
			xAlreadyYielded = xTaskResumeAll();//恢复任务调度器
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		/* Force a reschedule if xTaskResumeAll has not already done so, we may
		have put ourselves to sleep. */
		if( xAlreadyYielded == pdFALSE )//如果没有做任务切换就做一次任务切换
		{
			portYIELD_WITHIN_API();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}

#endif /* INCLUDE_vTaskDelay */

函数的逻辑很简单,注释写的很清楚了。
1、如果延时时间大于0,那么将当前任务添加到延时列表。
2、根据需要做一次任务切换
我们重点看一下prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );的内部实现

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

	#if( INCLUDE_xTaskAbortDelay == 1 )
	{
		/* About to enter a delayed list, so ensure the ucDelayAborted flag is
		reset to pdFALSE so it can be detected as having been set to pdTRUE
		when the task leaves the Blocked state. */
		pxCurrentTCB->ucDelayAborted = pdFALSE;
	}
	#endif

	/* Remove the task from the ready list before adding it to the blocked list
	as the same list item is used for both lists. */
	//将当前任务的状态列表项从就绪列表中移除,
	//为什么这么肯定就是就绪列表呢? 因为调用vTaskDelay函数一定是当前正在运行的任务(也就是任务自身),
	//不可能说A任务调用vTaskDelay让B任务延时,这是不存在的。
	//既然是当前正在运行的任务调用vTaskDelay,那么说明他一定在就绪列表中。
	if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
	{
		/* The current task must be in a ready list, so there is no need to
		check, and the port reset macro can be called directly. */
		//当前优先级下没有就绪任务就复位优先级
		portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}

	#if ( INCLUDE_vTaskSuspend == 1 )
	{
		if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) )
		{
			/* Add the task to the suspended task list instead of a delayed task
			list to ensure it is not woken by a timing event.  It will block
			indefinitely. */
			//如果延时portMAX_DELAY个时钟节拍就添加到挂起列表中,也就是挂起任务,
			//但是调用vTaskDelay函数即使传递参数portMAX_DELAY也不能挂起任务 因为xCanBlockIndefinitely固定传递的是pdFALSE
			vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) );
		}
		else
		{
			/* Calculate the time at which the task should be woken if the event
			does not occur.  This may overflow but this doesn't matter, the
			kernel will manage it correctly. */
			xTimeToWake = xConstTickCount + xTicksToWait;//计算出唤醒时间点

			/* The list item will be inserted in wake time order. */
			//将唤醒时间点的值写入到当前任务控制块的状态列表项中,后续会把状态列表项挂载到延时列表
			listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );

			if( xTimeToWake < xConstTickCount )//如果下一个时间点小于当前时间点
			{
				/* Wake time has overflowed.  Place this item in the overflow
				list. */
				//这种说明"唤醒点"会在xTickCount溢出之后到来  溢出就挂载到pxOverflowDelayedTaskList指针指向的延时列表上
				vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
			}
			else
			{
				/* The wake time has not overflowed, so the current block list
				is used. */
				//这种情况说名"唤醒点"会在xTickCount溢出之前到来  
				//"唤醒点"在xTickCount溢出之前到来就挂载到pxDelayedTaskList指针指向的延时列表上 
				vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );

				/* If the task entering the blocked state was placed at the
				head of the list of blocked tasks then xNextTaskUnblockTime
				needs to be updated too. */
				
				//xNextTaskUnblockTime保存了最近一个任务的解锁时间
				//如果新添加到阻塞列表中的"唤醒点"小于最近一个任务解锁时刻
				//那么就更新xNextTaskUnblockTime  
				if( xTimeToWake < xNextTaskUnblockTime )
				{
					//更新xNextTaskUnblockTime
					xNextTaskUnblockTime = xTimeToWake;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
	}
	#else /* INCLUDE_vTaskSuspend */
	{
		/* Calculate the time at which the task should be woken if the event
		does not occur.  This may overflow but this doesn't matter, the kernel
		will manage it correctly. */
		xTimeToWake = xConstTickCount + xTicksToWait;

		/* The list item will be inserted in wake time order. */
		listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );

		if( xTimeToWake < xConstTickCount )
		{
			/* Wake time has overflowed.  Place this item in the overflow list. */
			vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
		}
		else
		{
			/* The wake time has not overflowed, so the current block list is used. */
			vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );

			/* If the task entering the blocked state was placed at the head of the
			list of blocked tasks then xNextTaskUnblockTime needs to be updated
			too. */
			if( xTimeToWake < xNextTaskUnblockTime )
			{
				xNextTaskUnblockTime = xTimeToWake;
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}

		/* Avoid compiler warning when INCLUDE_vTaskSuspend is not 1. */
		( void ) xCanBlockIndefinitely;
	}
	#endif /* INCLUDE_vTaskSuspend */
}

函数的核心思想就是将当前任务从就绪列表中移除,添加到延时列表中,这其中有一个头疼的问题就是系统时钟节拍溢出。解决方案就是溢出就挂载到另一条延时列表上,两条列表交替使用,完美解决了此问题。

总结:
阻塞的任务在xTickCount溢出之前唤醒就挂载到pxDelayedTaskList延时列表上。
阻塞的任务在xTickCount溢出之后唤醒就挂载到pxOverflowDelayedTaskList延时列表上。

那么问题来了,pxDelayedTaskListpxOverflowDelayedTaskList又是怎么维护的呢?
pxDelayedTaskListpxOverflowDelayedTaskList是在xTaskIncrementTick()函数中维护的,xTaskIncrementTick()函数会在滴答定时中断服务函数中调用

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();
}

接下来在看一下时钟节拍函数xTaskIncrementTick

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

	traceTASK_INCREMENT_TICK( xTickCount );
	if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )//任务调度器没有挂起
	{
		const TickType_t xConstTickCount = xTickCount + 1;
		xTickCount = xConstTickCount;	//实现了时钟节拍xTickCount自增 	每次加1,

		//xTickCount 每次加1,只加1,不是跳跃式的加,所以xTickCount 加到最大值以后再加就是0,也就是溢出了。
		if( xConstTickCount == ( TickType_t ) 0U )//xConstTickCount为0说明时钟节拍溢出
		{
			taskSWITCH_DELAYED_LISTS();//交换延时列表的指针指向
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		//当前时钟节拍大于等于下一个任务解锁时间点 说明有任务需要解锁了
		if( xConstTickCount >= xNextTaskUnblockTime )
		{
			for( ;; )//用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赋值后则直接跳出
					//这种情况也是存在的 比如说系统中只有一个空闲任务的时候 
					//或者说用户目前没有创建任务直接调用开启任务调度器函数
					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 ) );//将TCB的状态列表项从阻塞列表中移除

					/* 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;	//标记需要做任务切换  因为RTOS  时刻都在运行就绪态中优先级最高的任务
						}
						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 ) ) */

		#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变量++ 记录时钟节拍个数

		/* 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();
		}
	}
	#endif /* configUSE_PREEMPTION */

	return xSwitchRequired;//返回是否进行任务切换的标志
}

这里重点介绍一下时钟节拍溢出以后交换两条延时列表指针的原理,
taskSWITCH_DELAYED_LISTS是一个宏,如下:

#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();																	\
}
PRIVILEGED_DATA static List_t xDelayedTaskList1;						//延时列表1
PRIVILEGED_DATA static List_t xDelayedTaskList2;						//延时列表2
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;				//指向延时列表的指针
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;		//指向延时溢出列表的指针

系统创建任务时判断,如果创建第一个任务会初始化系统中所有的列表。其中两条延时列表和延时列表指针初始化如下:

	pxDelayedTaskList = &xDelayedTaskList1;
	pxOverflowDelayedTaskList = &xDelayedTaskList2;

两条列表在和列表指针在系统中运行关系如下图,
在这里插入图片描述
两个指针交替指向两条延时列表。
pxDelayedTaskList 指向当前延时的列表,pxOverflowDelayedTaskList 指向延时溢出列表。如果一个任务延时的时间会在xTickCount溢出之前到来,那么该任务会挂载到pxDelayedTaskList指向的延时列表,否则挂载到pxOverflowDelayedTaskList指向的列表。
xTickCount溢出以后指针指向会交换,交换以后原来的溢出列表变成当前延时列表,当前延时列表变成了溢出列表。下次溢出再交换指向,就这样周而复始的运行。
不知道我这里我是否表达清楚。这是FreeRTOS中写的很巧妙的一段代码。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
FreeRTOS提供了五种内存分配方法,分别是heap_1.c、heap_2.c、heap_3.c、heap_4.c和heap_5.c。这些文件位于FreeRTOS源码的路径:FreeRTOS->Source->portable->MemMang。不同的嵌入式系统对于内存分配和时间要求不同,因此FreeRTOS将内存分配作为移植层的一部分,使用户可以选择适合自己系统的内存分配方法\[1\]\[2\]。 在FreeRTOS中,有两种内存使用方法:静态内存管理和动态内存管理。静态内存管理是在编程时直接定义或申请一个数组或结构体内存空间,而动态内存管理是在代码运行时才申请数组或结构体内存空间。静态内存管理具有安全、简单性和确定性的优点,不需要关心内存分配问题。而动态内存管理可以实现操作系统统一管理内存,但也存在一些问题,如安全问题、碎片问题和需要管理的复杂性\[3\]。 总的来说,FreeRTOS提供了多种内存分配方案,用户可以根据自己的需求选择合适的方法进行内存管理。静态内存管理和动态内存管理各有优缺点,对于大多数普通用户来说,两者没有太多区别。 #### 引用[.reference_title] - *1* *2* [FreeRTOS 内存管理](https://blog.csdn.net/Dustinthewine/article/details/130434246)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [FreeRTOS:内存管理](https://blog.csdn.net/liuwuyi1987/article/details/125812384)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值