🧔 老李:
昨天我们以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]
#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。
关键步骤
- 将任务从 ready/delayed list中删除,移到suspend list中
- 如果当前Task为等待通知状态,则设置成未等待通知状态
- 如果需要重新计算真正的下一个要解除阻塞的任务时间
- 让出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挂起的任务。
关键步骤
- 参数判断,禁止resume自己
- 需要被resume的Task确实在挂起队列里
- 从挂起队列中移除,并加入到就绪队列中
- 如果当前要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上的任务。
关键步骤
- 暂停任务调度器
- 找出处于
eBlocked状态的任务 - 将
Task从就绪队列、延迟、Event list或挂起列表中删除 - 添加到就绪队列
- 根据优先级判断是否抢占调用
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就是优先级。
关键步骤
- 判断优先级是否有效
- 将优先级存储TCB中,如果打开了
MUTEXES宏则使用基础优先级,关于优先级继承 - 将Task添加到对应优先级的
pxReadyTasksList中 - 如果需要则更新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就是获取优先级。
关键步骤
- 在临界区中返回
TCB的uxPriority值
参数:
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优先级的就绪队列。因此pxReadyTasksLists是List_t数组。
关键步骤
- 将
Task的xStateListItem插入到对应优先级的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是需要等待的相对时间。
关键步骤
- 设置
Task延迟被取消的Flag为pdFALSE - 移出就绪队列
- 如果需要永久等待并且打开了
Suspended功能,则直接添加到挂起队列(xSuspendedTaskList)中 - 计算在哪个时间点
Tick唤醒。并设置pxCurrentTCB->xStateListItem为需要唤醒的值。 - 如果发生
Tick发生溢出则插入到pxOverflowDelayedTaskList,没有溢出则插入到pxDelayedTaskList - 计算出最近的下一次
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--上班
}
🧔 老李:
每次循环:
关键步骤
- 挂起
Task调度器 - 计算唤醒的时间点
- 判断是否需要等待
- 添加到
prvAddCurrentTaskToDelayedList - 恢复
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 */