🧔 老李:
昨天我们以HR的视角详细介绍了打工人的入职、离职与裁员,对应
xTaskCreate与vTaskDelete。今天我们以一个打工人的视角看看,打工人的日常工作。
提前的周末
周四晚上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个小时),然后不需要唤醒自动回去上班。
关键步骤
- 挂起
Task调度器 - 将当前
Task加入到延时队列 - 恢复
Task调度器 - 如果需要则让出CPU
参数:
const TickType_t xTicksToDelay:调用任务应阻塞的Tick周期数[^1]
C
#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`:被挂起的任务的句柄。 ```C 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`句柄 ```C #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` ```C #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`:需要设置的优先级 ```C #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. 在临界区中返回`TCB`的`uxPriority`值 #### 参数: `TaskHandle_t xTask`:需要查询优先级的`Task`句柄 ```C 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优先级的就绪队列。因此`pxReadyTasksLists`是`List_t`数组。 #### 关键步骤 1. 将`Task`的`xStateListItem`插入到对应优先级的pxReadyTasksLists`[ ( pxTCB )->uxPriority ]`队列的队尾。 #### 参数: `pxTCB`:需要加入就绪队列的`Task`句柄 ```C #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`延迟被取消的`Flag`为`pdFALSE` 2. 移出就绪队列 3. 如果需要永久等待并且打开了`Suspended`功能,则直接添加到挂起队列(`xSuspendedTaskList`)中 4. 计算在哪个时间点`Tick`唤醒。并设置`pxCurrentTCB->xStateListItem`为需要唤醒的值。 5. 如果发生`Tick`发生溢出则插入到`pxOverflowDelayedTaskList`,没有溢出则插入到`pxDelayedTaskList` 6. 计算出最近的下一次`Unblock`的时间,并更新`xNextTaskUnblockTime` #### 参数: `TickType_t xTicksToWait`:需要等待的相对时间 `BaseType_t xCanBlockIndefinitely`: 当前这个Task是不是允许无限期等待 ```C 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。 ```C TickType_t xLastWakeTime; while(1) { // 代码段A--下班 vTaskDelayUntil(&xLastWakeTime, 1000); // 代码段B--上班 } ``` > **🧔 老李:** > > 每次循环: > T代码段A+T代码段B+TvTaskDelayUntil=1000Tick #### 关键步骤 1. 挂起`Task`调度器 2. 计算唤醒的时间点 3. 判断是否需要等待 4. 添加到`prvAddCurrentTaskToDelayedList` 5. 恢复`Task`调度器,并根据返回值判断是否要让出CPU #### 参数: `TickType_t pxPreviousWakeTime`:上一次"唤醒时间"的指针。首次使用需要`TickType_t xLastWakeTime = xTaskGetTickCount();`进行初始化,将当前Tick作为基准Tick。 `TickType_t xTimeIncrement`:真正需要Delay的时间。 ```C #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 */ ```