第2节:趣谈FreeRTOS--打工人的日常

56 阅读17分钟

🧔 老李:
昨天我们以HR的视角详细介绍了打工人的入职、离职与裁员,对应xTaskCreatevTaskDelete。今天我们以一个打工人的视角看看,打工人的日常工作。

提前的周末

周四晚上10点Android组小张结束了一天的开发,准备下班回家。iOS组小刘则向老赵请假周六要参加好兄弟婚礼。

🧑 iOS 小刘:
赵哥,周六是我好兄弟婚礼,我去当伴郎,明天想请一天假提前去帮他布置婚礼现场。

🧑🏻‍🦲 移动端 老赵: 去吧,记得飞书上提申请。

🧑 iOS 小刘:
谢谢赵哥!

紧急会议

⏱️ 晚上22:30

PM老吴在项目群里发消息:
22:45 紧急开会,@移动端 老赵 @服务端 老钱 @产品 老孙 @测试 老周

加入XX集团日程: www.feishu.cn/calendar/sh…
日程主题: 端午节XXX功能紧急发版
时间: 2025年XX月XX日(周三) 22:45 - 23:45 (GMT+8)
组织者: 老吴

会议上

🧔🏻‍♀️ VP:
为了保障端午节活动的顺利召开,公司决定新增加一个XXX功能。还有5天时间,为了尽快铺量,功能明晚18点就要发版!老孙,你现在把PRD发给大家。各位,辛苦提前赶赶,这个对咱们公司非常重要!活动结束后给大家加鸡腿!

👩 测试 老周:
留给我们测试的时间太紧了,我们要预留至少8小时测试,决不能在线上出问题。老赵,你们能在明天早上10点提测吗?

老赵摸了摸自己的光头,看了看电脑上的时钟

🧑🏻‍🦲 移动端 老赵: 现在12点了,还有10个小时提测。哎,找小张和小刘吧,他俩就住在隔壁公寓,其他人住的太远,打车过来至少要1个小时。老钱,你那边呢?什么时候能联调?

🧑🏻‍🦲 服务端 老钱: 哎,提测前两小时开始联调吧,小郑还在改Bug,我一会儿让他优先保障端午节活动接口开发。

于是,老赵打电话给 睡觉中的 小张 和 请假中的 小刘

第二天早上9点,小红早早来到公司,看到通宵写代码的小张、小刘正在焦头烂额的自测。小郑趴在桌子上睡着了。

👧 测试 小红:
小红:“二位啥时候提测呀?”

🧑 iOS 小刘:
十一点吧,我们先自测一下,你先忙别的吧。

👧 测试 小红:
小红:好的,提测了叫我

🧑 小刘 & 小张:
OK


⏱️ 上午10:30

小刘 和 小张 在项目群里发消息:

🧑 小刘 & 小张:
提测邮件已发 @测试 小红

vTaskDelay

🧔 老李:
员工(Task)休息一段时间(vTaskDelay),例如:996周期性的每天上班12小时,下班12小时(vTaskDelay12个小时),然后不需要唤醒自动回去上班。

关键步骤

  1. 挂起Task调度器
  2. 将当前Task加入到延时队列
  3. 恢复Task调度器
  4. 如果需要则让出CPU

参数:

const TickType_t xTicksToDelay:调用任务应阻塞的Tick周期数[^1]

#if ( INCLUDE_vTaskDelay == 1 )

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

        /* 如果 xTicksToDelay == 0 则只让出CPU并重新调度 */
        if( xTicksToDelay > ( TickType_t ) 0U )
        {
            /* 调用vTaskDelay之前是不能进行挂起Task调度器的,因为如果调度器被挂起当前Task又调用vTaskDelay,则整个系统将被暂停,实时性无法保障 */
            configASSERT( uxSchedulerSuspended == 0 );
            vTaskSuspendAll(); /* 临时挂起Task调度器,保证操作原子性,参见第五节 */
            {
                traceTASK_DELAY();

                /* 当一个任务如果被从event list中移出时,由于pxReadyTasksLists会在关闭调度器时被修改,因此,如果当前有Task就绪(在ISR函数中变成就绪)不会立即进入就绪队列,而是进入PendingReadyList 当调度器恢复时再进入到就绪队列。 */

                /* 调用vTaskDelay的当前任务由于在执行,所以不会在 event list中,所以可以添加到DelayedList中 */
                prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
            }
            xAlreadyYielded = xTaskResumeAll(); /* 恢复Task调度器 */
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }

        /* 如果恢复Task调度器时没有自动触发任务切换,由于当前任务已经Delay进入“睡眠状态”,则需要手动强制调研一次Yield让出CPU */
        if( xAlreadyYielded == pdFALSE )
        {
            portYIELD_WITHIN_API();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }

#endif /* INCLUDE_vTaskDelay */

[1] 可以使用 portTICK_PERIOD_MS 宏将毫秒转换成Tick。

vTaskSuspend

🧔 老李:
iOS 小刘 主动请假,Task自己调用vTaskSuspend(NULL)。小刘让小红等他的提测通知,vTaskSuspend(xiaohong),挂起Task xiaohong。

关键步骤

  1. 将任务从 ready/delayed list中删除,移到suspend list中
  2. 如果当前Task为等待通知状态,则设置成未等待通知状态
  3. 如果需要重新计算真正的下一个要解除阻塞的任务时间
  4. 让出CPU或switch context,并正确设置pxCurrentTCB

参数:

TaskHandle_t xTaskToSuspend:被挂起的任务的句柄。

	void vTaskSuspend( TaskHandle_t xTaskToSuspend )
	{
	TCB_t *pxTCB;

		taskENTER_CRITICAL(); /* 进入临界区 */
		{
			/* 将TaskHandle_t 转成 TCB_t,xTaskToSuspend == null,则当前任务会被挂起 */
			pxTCB = prvGetTCBFromHandle( xTaskToSuspend );

			traceTASK_SUSPEND( pxTCB );

			/* 将任务从 ready/delayed list中删除,移到suspend list中 */
			if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
			{
				taskRESET_READY_PRIORITY( pxTCB->uxPriority );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}

			/* 如果当前Task 在 xEventList中,则从xEventListItem中移除 */
			if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
			{
				( void ) uxListRemove( &( pxTCB->xEventListItem ) );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
      
                  /* 将当前Task添加到xSuspendedTaskList中 */
			vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xStateListItem ) );

			#if( configUSE_TASK_NOTIFICATIONS == 1 )
			{
				if( pxTCB->ucNotifyState == taskWAITING_NOTIFICATION ) /* 如果当前Task为等待通知状态,则设置成未等待通知状态 */
				{
					/* Task这次没等到通知,要挂起了,等恢复后可以重新等待通知。为防止它在挂起期间错过通知、恢复后状态异常 */
					pxTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
				}
			}
			#endif
		}
		taskEXIT_CRITICAL();/* 退出临界区 */

		if( xSchedulerRunning != pdFALSE )
		{
			/* 调度器正在运行 */
			taskENTER_CRITICAL();/* 进入临界区 */
			{
				prvResetNextTaskUnblockTime(); /* 如果需要suspend的Task正在vTaskDelay,则需要跳过这个Task,重新计算真正的下一个要解除阻塞的任务时间。 */
			}
			taskEXIT_CRITICAL();/* 退出临界区 */
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		if( pxTCB == pxCurrentTCB ) /* 如果当前Task被挂起,则让出CPU */
		{
			if( xSchedulerRunning != pdFALSE ) /* 调度器正在运行,直接调用让出CPU */
			{
				configASSERT( uxSchedulerSuspended == 0 );
				portYIELD_WITHIN_API();
			}
			else
			{
				/* The scheduler is not running, but the task that was pointed
				to by pxCurrentTCB has just been suspended and pxCurrentTCB
				must be adjusted to point to a different task. */
				if( listCURRENT_LIST_LENGTH( &xSuspendedTaskList ) == uxCurrentNumberOfTasks )
				{
					/* 挂起Task列表的大小==当前Task的数量,则pxCurrentTCB置空,Task已经全部被挂起了,没有可运行的。*/
					pxCurrentTCB = NULL;
				}
				else
				{
					vTaskSwitchContext(); /* 强制进行上下文切换,在就绪队列中找到可以运行的Task,更新pxCurrentTCB */
				}
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}

vTaskResume

🧔 老李:
老赵终止了小刘的休假状态vTaskResume(xiaoliu),小刘重新回到岗位开始加班,恢复被vTaskSuspend挂起的任务。

关键步骤

  1. 参数判断,禁止resume自己
  2. 需要被resume的Task确实在挂起队列里
  3. 从挂起队列中移除,并加入到就绪队列中
  4. 如果当前要Resume的Task优先级高,则当前Task直接调用抢占式YIELD让出CPU

参数:

TaskHandle_t xTaskToResume:待恢复的Task句柄

#if ( INCLUDE_vTaskSuspend == 1 )

	void vTaskResume( TaskHandle_t xTaskToResume )
	{
	TCB_t * const pxTCB = xTaskToResume;

		/* 不能传入NULL,因为调用Task不需要resume自己,因为调用Task本来就是resume状态 */
		configASSERT( xTaskToResume );

		/* 同上,如果传入的是调用Task的TaskHandle_t,则do nothing */
		if( ( pxTCB != pxCurrentTCB ) && ( pxTCB != NULL ) )
		{
			taskENTER_CRITICAL(); /* 进入临界区 */
			{
				if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE ) /* 需要被resume的Task确实在挂起队列里 */
				{
					traceTASK_RESUME( pxTCB );

					/* 从挂起队列中移除,并加入到就绪队列中 */
					( void ) uxListRemove(  &( pxTCB->xStateListItem ) );
					prvAddTaskToReadyList( pxTCB );

					/* 如果当前要Resume的Task优先级比 当前运行的Task的优先级要高,则当前Task直接调用抢占式YIELD让出CPU */
					if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ) 
					{
						taskYIELD_IF_USING_PREEMPTION();
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			taskEXIT_CRITICAL(); /* 退出临界区 */
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}

#endif /* INCLUDE_vTaskSuspend */

xTaskAbortDelay

🧔 老李:
老赵叫醒了睡觉中的小张xTaskAbortDelay(xiaozhang),小张重新回到岗位开始加班,恢复嗲用vTaskDelay的任务。强制任务离开阻塞状态,进入就绪状态,可以强制唤醒vTaskDelay 或 阻塞在 Queue/Mutex/Semaphore 上的任务。

关键步骤

  1. 暂停任务调度器
  2. 找出处于eBlocked 状态的任务
  3. Task从就绪队列、延迟、Event list或挂起列表中删除
  4. 添加到就绪队列
  5. 根据优先级判断是否抢占调用xTaskAbortDelay的进程

参数:

TaskHandle_t xTask:需要取消的Task

#if ( INCLUDE_xTaskAbortDelay == 1 )

	BaseType_t xTaskAbortDelay( TaskHandle_t xTask )
	{
	TCB_t *pxTCB = xTask;
	BaseType_t xReturn;
		/* 不能传入NULL,因为调用Task不需要AbortDelay自己,因为调用Task本来就不是Delay状态 */
		configASSERT( pxTCB );

		vTaskSuspendAll(); /* 暂停任务调度器,这样当前函数执行完成之前,所有的Task状态均不会改变 */
		{
			/* 处于eBlocked的任务才能被取消延迟等待 */
			if( eTaskGetState( xTask ) == eBlocked )
			{
				xReturn = pdPASS;

				/* 将Task从就绪队列、延迟或挂起列表中删除,由于调度器已经暂停中断不会修改xStateListItem */
				( void ) uxListRemove( &( pxTCB->xStateListItem ) );

				/* 如果Task还在等待Event,也需要从Event list中移除,这时需要在临界区中操作了,因为中断处理函数中可以修改EventList */
				taskENTER_CRITICAL();
				{
					if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL ) /* 如果Task正在等待事件 */
					{
						( void ) uxListRemove( &( pxTCB->xEventListItem ) ); /* 从事件等待队列中移除 */

						/* 将ucDelayAborted置为pdTRUE,用于通知Task被唤醒原因 */
						pxTCB->ucDelayAborted = pdTRUE;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}
				taskEXIT_CRITICAL();

				/* 处于“未阻塞”状态的Task需要添加到就绪队列中 */
				prvAddTaskToReadyList( pxTCB );

				/* “未阻塞”状态,如果是抢占式内核则进行CPU Yield */
				#if (  configUSE_PREEMPTION == 1 )
				{
					/* 如果被取消Delay的Task优先级大于当前Task的优先级则抢占CPU */
					if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
					{
						/* 由于调度器是暂停状态,所以先记下来,等调度器恢复后再Yield */
						xYieldPending = pdTRUE;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}
				#endif /* configUSE_PREEMPTION */
			}
			else
			{
				xReturn = pdFAIL;/* 返回失败 */
			}
		}
		( void ) xTaskResumeAll(); /* 恢复Task调度器,通过判断xYieldPending的值进行Yield */

		return xReturn; /* 返回是否成功取消 */
	}

#endif /* INCLUDE_xTaskAbortDelay */

vTaskPrioritySet

🧔 老李:
设置员工的“职级”,职级越高的员工任务越优先得到执行,例如:VP职级高于普通员工,所以端午节活动优先得到开发。对应Task就是优先级。

关键步骤

  1. 判断优先级是否有效
  2. 将优先级存储TCB中,如果打开了MUTEXES宏则使用基础优先级,关于优先级继承
  3. 将Task添加到对应优先级的pxReadyTasksList
  4. 如果需要则更新uxReadyPriorities的优先级位图

参数:

TaskHandle_t xTask:需要设置优先级的Task句柄
UBaseType_t uxNewPriority:需要设置的优先级

#if ( INCLUDE_vTaskPrioritySet == 1 )

	void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority )
	{
	TCB_t *pxTCB;
	UBaseType_t uxCurrentBasePriority, uxPriorityUsedOnEntry;
	BaseType_t xYieldRequired = pdFALSE;

		configASSERT( ( uxNewPriority < configMAX_PRIORITIES ) );

		/* 判断优先级是否有效 */
		if( uxNewPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
		{
			uxNewPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		taskENTER_CRITICAL(); /* 进入临界区 */
		{
			/* xTask==NULL,则代表设置调用进程的优先级 */
			pxTCB = prvGetTCBFromHandle( xTask ); /* TaskHandle_t转TCB_t 参见第3节 prvGetTCBFromHandle部分 */

			traceTASK_PRIORITY_SET( pxTCB, uxNewPriority );

			#if ( configUSE_MUTEXES == 1 )
			{
				uxCurrentBasePriority = pxTCB->uxBasePriority; /* 如果打开了MUTEXES宏则使用基础优先级,关于优先级继承,参见第三节  */
			}
			#else
			{
				uxCurrentBasePriority = pxTCB->uxPriority; /* 否则使用普通优先级 */
			}
			#endif

			if( uxCurrentBasePriority != uxNewPriority ) /* 优先级需要变化 */
			{
				/* 要提高优先级 */
				if( uxNewPriority > uxCurrentBasePriority )
				{
					if( pxTCB != pxCurrentTCB )
					{
						/* 要提高优先级的Task并非当前正在运行的Task,并且被提高的优先级高于当前Task的优先级,则当前Task需要让出CPU */
						if( uxNewPriority >= pxCurrentTCB->uxPriority )
						{
							xYieldRequired = pdTRUE;/* 因为在临界区内,所以仅进行标记 */
						}
						else
						{
							mtCOVERAGE_TEST_MARKER();
						}
					}
					else
					{
						/* 不需要提升正在运行Task的优先级,因为如果当前Task正在运行,说明已经是最高优先级了,也就不需要再提升了 */
					}
				}
				else if( pxTCB == pxCurrentTCB )
				{
					/* 要降低当前Task的优先级,则需要让出CPU */
					xYieldRequired = pdTRUE;/* 因为在临界区内,所以仅进行标记 */
				}
				else
				{
					/* 降低其他任务的优先级并不需要让出当前任务的优先级,因为运行中的任务的优先级必须高于被修改任务的新优先级 */
				}

				/* Remember the ready list the task might be referenced from
				before its uxPriority member is changed so the
				taskRESET_READY_PRIORITY() macro can function correctly. */
				uxPriorityUsedOnEntry = pxTCB->uxPriority;

				#if ( configUSE_MUTEXES == 1 )
				{
					/* uxBasePriority == uxPriority 则说明没有处于优先级继承阶段,则将普通优先级也设置成新优先级,如果是优先级继承阶段则普通优先级沿用继承来的优先级 */
					if( pxTCB->uxBasePriority == pxTCB->uxPriority )
					{
						pxTCB->uxPriority = uxNewPriority;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}

					/* 设置基础优先级为新优先级 */
					pxTCB->uxBasePriority = uxNewPriority;
				}
				#else
				{
					pxTCB->uxPriority = uxNewPriority; /* 设置普通优先级为新优先级 */
				}
				#endif

				/* xEventListItem的值没有用到的话,将它用来存储优先级,configMAX_PRIORITIES - ( TickType_t ) uxNewPriority,高优先级的值排在前面 */
				if( ( listGET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ) ) & taskEVENT_LIST_ITEM_VALUE_IN_USE ) == 0UL )
				{
                                   /* 高优先级的任务排在前面 */
					listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxNewPriority ) );
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}

				/* 如果任务在阻塞列表或挂起列表中,只需更改其优先级变量即可。如果
           任务在就绪列表中,则需要将其移除并放入与其新优先级相符的列表中。 */
				if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ uxPriorityUsedOnEntry ] ), &( pxTCB->xStateListItem ) ) != pdFALSE )
				{
					/* 先从原优先级队列中移除,将uxReadyPriorities中对应uxPriority的位清零,
             表示该优先级的任务不再处于就绪状态。由于在临界区内,所以可以直接操作 */
					if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
					{
						portRESET_READY_PRIORITY( uxPriorityUsedOnEntry, uxTopReadyPriority );
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
                                   /* 更换到对应优先级的pxReadyTasksList中 */
					prvAddTaskToReadyList( pxTCB );
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}

				if( xYieldRequired != pdFALSE )
				{
					taskYIELD_IF_USING_PREEMPTION(); // xYieldRequired则抢占Yield
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}

				/* Remove compiler warning about unused variables when the port
				optimised task selection is not being used. */
				( void ) uxPriorityUsedOnEntry;
			}
		}
		taskEXIT_CRITICAL(); /* 退出临界区 */
	}

#endif /* INCLUDE_vTaskPrioritySet */

uxTaskPriorityGet

🧔 老李:
获取某个员工的“职级”,方便通过直接安排开发顺序。对应Task就是获取优先级。

关键步骤

  1. 在临界区中返回TCBuxPriority

参数:

TaskHandle_t xTask:需要查询优先级的Task句柄

	UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask )
	{
	TCB_t const *pxTCB;
	UBaseType_t uxReturn;

		taskENTER_CRITICAL(); /* 进入临界区 */
		{
      /* TaskHandle_t转TCB_t 参见第3节 prvGetTCBFromHandle部分 */
			pxTCB = prvGetTCBFromHandle( xTask );
			uxReturn = pxTCB->uxPriority;
		}
		taskEXIT_CRITICAL(); /* 退出临界区 */

		return uxReturn;
	}

prvAddTaskToReadyList

🧔 老李:
需求需要放在对应职级的需求队列中,相同职级提出的需求,按照先后顺序排队执行。对应FreeRTOS系统有一个就绪队列数组,数组的每个元素代表对应index优先级的就绪队列。因此pxReadyTasksListsList_t数组。

关键步骤

  1. TaskxStateListItem插入到对应优先级的pxReadyTasksLists[ ( pxTCB )->uxPriority ]队列的队尾。

参数:

pxTCB:需要加入就绪队列的Task句柄

#define prvAddTaskToReadyList( pxTCB )																\
	traceMOVED_TASK_TO_READY_STATE( pxTCB );														\
	taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority );												\
	vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \
	tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB )

prvAddCurrentTaskToDelayedList

🧔 老李:
员工午休或下班回家,会被添加到DelayedList中,以便在时间到达时将其唤醒。将pxCurrentTCB当前任务插入到延迟队列(挂起队列)的合适位置 xTicksToWait 是需要等待的相对时间。

关键步骤

  1. 设置Task延迟被取消的FlagpdFALSE
  2. 移出就绪队列
  3. 如果需要永久等待并且打开了Suspended功能,则直接添加到挂起队列(xSuspendedTaskList)中
  4. 计算在哪个时间点Tick唤醒。并设置pxCurrentTCB->xStateListItem为需要唤醒的值。
  5. 如果发生Tick发生溢出则插入到pxOverflowDelayedTaskList,没有溢出则插入到pxDelayedTaskList
  6. 计算出最近的下一次Unblock的时间,并更新xNextTaskUnblockTime

参数:

TickType_t xTicksToWait:需要等待的相对时间
BaseType_t xCanBlockIndefinitely: 当前这个Task是不是允许无限期等待

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

	#if( INCLUDE_xTaskAbortDelay == 1 )
	{
		/* 清除ucDelayAborted标志,xTaskAbortDelay会设置成pdTRUE */
		pxCurrentTCB->ucDelayAborted = pdFALSE;
	}
	#endif

	/* 由于BlockedList和就绪List都使用xStateListItem,则先尝试将pxCurrentTCB移出就绪队列,方便后续添加到BlockedList */
	if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
	{
           /* 删除后,如果该就绪队列size==0,将uxReadyPriorities中对应pxCurrentTCB->uxPriority的位清零,表示该优先级的任务不再处于就绪状态。 */
		portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}

	#if ( INCLUDE_vTaskSuspend == 1 )
	{
		if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) )
		{
			/* 如果永久等待,则放到挂起队尾 */
			vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) );
		}
		else
		{
			/* 计算挂起需要等待的时间,是按照当前xConstTickCount计算的起始时间,因此不准确 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;

			/* 设置xStateListItem的值为xTimeToWake,方便后续按照唤醒顺序插入 The list item will be inserted in wake time order. */
			listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );

			if( xTimeToWake < xConstTickCount ) /* 如果xTimeToWake < xConstTickCount则说明发生了溢出,则插入到溢出延迟队列中,根据xStateListItem的xItemValue进行排序(升序)并插入到合适位置 */
			{
				/* Wake time has overflowed.  Place this item in the overflow
				list. */
				vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
			}
			else
			{
				/* 如果没有溢出,则插入到普通延迟队列中,根据xStateListItem的xItemValue进行排序(升序)并插入到合适位置 */
				vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );

				/* 计算出最近的下一次Unblock的时间,并更新xNextTaskUnblockTime 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();
				}
			}
		}
	}
	#else /* INCLUDE_vTaskSuspend */
	{
		/* 如果没有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 */
}

vTaskDelayUntil

/* finish Delay函数,和vTaskDelay相比较更加的精确,通常用在for循环Delay中 这段代码的好处是:每次循环代码段A和代码段B和vTaskDelayUntil三者的执行时间是1000个Tick,相比vTaskDelay,没有计算代码段A和代码段B的时间,更加精确 */

🧔 老李:
员工下班休息12小时,将上班&下班通勤时间也包含在12小时内。和vTaskDelay相比较更加的精确,通常用在循环中Delay。

    TickType_t xLastWakeTime;
    while(1) {
        // 代码段A--下班
        vTaskDelayUntil(&xLastWakeTime, 1000);
        // 代码段B--上班
    }

🧔 老李:
每次循环:
T代码段A+T代码段B+TvTaskDelayUntil=1000TickT_{代码段A} + T_{代码段B} + T_{vTaskDelayUntil} = 1000Tick

关键步骤

  1. 挂起Task调度器
  2. 计算唤醒的时间点
  3. 判断是否需要等待
  4. 添加到prvAddCurrentTaskToDelayedList
  5. 恢复Task调度器,并根据返回值判断是否要让出CPU

参数:

TickType_t pxPreviousWakeTime:上一次“唤醒时间”的指针。首次使用需要TickType_t xLastWakeTime = xTaskGetTickCount();进行初始化,将当前Tick作为基准Tick。
TickType_t xTimeIncrement:真正需要Delay的时间。

#if ( INCLUDE_vTaskDelayUntil == 1 )

	void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
	{
	TickType_t xTimeToWake; /* 需要等待的绝对时间 */
	BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;

		configASSERT( pxPreviousWakeTime );
		configASSERT( ( xTimeIncrement > 0U ) );
		configASSERT( uxSchedulerSuspended == 0 );

		vTaskSuspendAll(); /* 挂起Task调度器 */
		{
			const TickType_t xConstTickCount = xTickCount;

			/* 上次WakeTime + 需要等待的时间 = xTimeToWake这个时间点Wakeup */
			xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;

			if( xConstTickCount < *pxPreviousWakeTime )
			{
				/* 时钟Tick溢出了,则必须xTimeToWake也溢出,并且依然保证xConstTickCount < xTimeToWake,也就是说xConstTickCount < xTimeToWake均溢出了才需要继续等待 */
				if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) )
				{
					xShouldDelay = pdTRUE;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				/* 这种说明时钟Tick没有溢出,则如果xTimeToWake < *pxPreviousWakeTime说明xTimeToWake溢出了 或者 xTimeToWake > xConstTickCount说明没有溢出,就是普通情况,则两种均需要等待 */
				if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) )
				{
					xShouldDelay = pdTRUE;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}

			/* 更新pxPreviousWakeTime 方便下次无缝使用 */
			*pxPreviousWakeTime = xTimeToWake;

			if( xShouldDelay != pdFALSE )
			{
				traceTASK_DELAY_UNTIL( xTimeToWake );

				/* 添加到等待队列;由于prvAddCurrentTaskToDelayedList()传入的是需要等待的相对时间,因此需要两个绝对时间相减 */
				prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		xAlreadyYielded = xTaskResumeAll();

		/* 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_vTaskDelayUntil */