【原创声明】:本文系原创作品,谢绝任何形式的未经授权的转载。如需转载,请先私信联系我获取许可,谢谢合作!
引言
今天是星期一,为了和老李学习FreeRTOS,小鹤早早来到了公司。不一会儿,老李背着双肩包来到了工位。
👨 小鹤:
早啊!李哥
🧔 老李:
早!今天怎么来这么早啊?小鹤
👨 小鹤:
上周李哥说要用“互联网企业日常工作”情景剧的方式讲FreeRTOS,我早早就醒了,我们现在就开始吧!
🧔 老李:
哈哈!那我就先用比喻来介绍几个基础概念吧:
| 比喻 | FreeRTOS名词 |
|---|---|
| 员工 | Task |
| 保洁阿姨 | IdleTask |
| 项目经理 | 调度器 |
| 员工的公司档案 | TCB |
| 员工的TODO-List | 调用栈(Stack) |
| 员工的任务 | TaskFunction |
🧔 老李:
有了这几个基础概念,接下来我们就能在此基础上逐步拓展,最终勾勒出FreeRTOS的整个架构。
🧔 老李:
今天的故事从HR的一天开始😊
早上9点,HR小美准时进入公司,在茶水间打上一杯拿铁,回到工位,打开飞书日历:09:30 Android和服务端分别入职新员工小张和小刘14:00 和老王谈裁员补偿18:00 在海底捞参加Android组给小赵举行的送行宴
🧔 老李:
我们可以认为HR是一个特殊的Task,她负责招聘(xTaskCreate) 和 员工离职(vTaskDelete),这里离职分两种如果vTaskDelete(NULL),代表调用这个函数的员工(Task)主动离职;如果vTaskDelete传入员工(Task)名字,则代表裁员(删除)这个员工(Task),例如:vTaskDelete(laowang)
👨 小鹤:
HR这个特殊的Task是什么时候创建的呢?
🧔 老李:
当然是公司(FreeRTOS)成立的时候,对应是程序初始化的时候,可以是静态初始化xTaskCreateStatic
xTaskCreate
🧔 老李:
HR招聘员工,发工牌(TCB pxCreatedTask)、分配任务(TaskFunction_t pxTaskCode)、设置职级(UBaseType_t uxPriority)
关键步骤
- 分配TCB
- 分配Task栈
- 调用
prvInitialiseNewTask初始化TCB, - 添加到就绪队列
参数:
TaskFunction_t pxTaskCode: Task的执行函数
char * const pcName: 任务的名字
configSTACK_DEPTH_TYPE usStackDepth: 栈的深度 uxStackDepth * sizeof(StackType_t)是栈的真正大小
void * pvParameters: 传入执行函数的参数列表
UBaseType_t uxPriority: 任务优先级
TaskHandle_t * pxCreatedTask: 创建的TCB,TaskHandle_t是tskTCB的别名
BaseType_t xTaskCreate(TaskFunction_t pxTaskCode,
const char * const pcName,
const configSTACK_DEPTH_TYPE usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
{
TCB_t *pxNewTCB;
BaseType_t xReturn;
#if( portSTACK_GROWTH > 0 ) /* 低地址向高地址增长 */
{
/* 分配TCB内存 */
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
if( pxNewTCB != NULL )
{
/* 分配栈内存 */
pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
if( pxNewTCB->pxStack == NULL )
{
/* 栈内存分配失败,回收TCB内存 */
vPortFree( pxNewTCB );
pxNewTCB = NULL;
}
}
}
#else /* Cortex-M3 系列是这个分支 portSTACK_GROWTH */
{
StackType_t *pxStack;
/* 分配栈内存 */
pxStack = pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
if( pxStack != NULL )
{
/* 分配TCB内存 */
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
if( pxNewTCB != NULL )
{
pxNewTCB->pxStack = pxStack;
}
else
{
/* TCB内存分配失败,回收栈内存 */
vPortFree( pxStack );
}
}
else
{
pxNewTCB = NULL;
}
}
#endif /* portSTACK_GROWTH */
if( pxNewTCB != NULL )
{
#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e9029 !e731 Macro has been consolidated for readability reasons. */
{
/* 既 允许动态分配内存 又 允许静态分配内存,则在TCB中标明是xTaskCreate的Task而不是xTaskCreateStatic,用来在vTaskDelete(内部是prvDeleteTCB)中决定是否要释放内存 */
pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
}
#endif /* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE */
prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
prvAddNewTaskToReadyList( pxNewTCB );
xReturn = pdPASS;
}
else
{
xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
}
return xReturn;
}
prvInitialiseNewTask
🧔 老李:
真正负责发工牌(TCB pxCreatedTask)、分配任务(TaskFunction_t pxTaskCode)、设置职级(UBaseType_t uxPriority)的函数
关键步骤
- 初始化 WaterMask [^1]
- 计算
pxTopOfStack和pxEndOfStack - 复制Task name
- 设置
TCB的优先级和基础优先级(优先级继承机制使用) - 初始化
xStateListItem和xEventListItem、pxTaskTag[^2]、pvThreadLocalStoragePointers[^3]等 - 调用
pxPortInitialiseStack将Task的函数地址放在PC的位置、pvParameters放在R0位置,当执行ldmia sp!, {r3, r14}时,Task的函数地址会自动赋值给PC从开开始从Task开始执行 - 将TCB赋值给
pxCreatedTask从而在应用层可以获取到当前TaskHandler_t(TCB)
参数:
TaskFunction_t pxTaskCode: Task的执行函数
char * pcName: 栈的名字
uint32_tulStackDepth: 栈的深度 uxStackDepth * sizeof(StackType_t)是栈的真正大小
void * pvParameters: 传入执行函数的参数列表
UBaseType_t uxPriority: 任务优先级
TaskHandle_t * pxCreatedTask: 创建的TCB,TaskHandle_t是tskTCB的别名
TCB_t * pxNewTCB: TCB
MemoryRegion_t * xRegions: MPU描述,为该任务定义受保护的内存区域
static void prvInitialiseNewTask(TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask,
TCB_t *pxNewTCB,
const MemoryRegion_t * const xRegions )
{
StackType_t *pxTopOfStack;
UBaseType_t x;
#if( portUSING_MPU_WRAPPERS == 1 ) /* 暂不考虑MPU分支 */
/* Should the task be created in privileged mode? */
BaseType_t xRunPrivileged;
if( ( uxPriority & portPRIVILEGE_BIT ) != 0U )
{
xRunPrivileged = pdTRUE;
}
else
{
xRunPrivileged = pdFALSE;
}
uxPriority &= ~portPRIVILEGE_BIT;
#endif /* portUSING_MPU_WRAPPERS == 1 */
/* Avoid dependency on memset() if it is not required. */
#if( tskSET_NEW_STACKS_TO_KNOWN_VALUE == 1 )
{
/* 用0xA5填满整个任务栈,当任务运行时0xA5会被修改成其他数值,这时从栈顶向栈底数出剩余0xA5的字节个数X,用Stack_size - X 即可算出运行中使用栈的上限 */
( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );
}
#endif /* tskSET_NEW_STACKS_TO_KNOWN_VALUE */
#if( portSTACK_GROWTH < 0 ) /* Cortex-M3 系列是这个分支 高地址向低地址增长 */
{
pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );
/* 将栈指针按字节对齐,以确保系统的稳定性和性能 */
pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
#if( configRECORD_STACK_HIGH_ADDRESS == 1 )
{
pxNewTCB->pxEndOfStack = pxTopOfStack;
}
#endif /* configRECORD_STACK_HIGH_ADDRESS */
}
#else /* portSTACK_GROWTH */
{
pxTopOfStack = pxNewTCB->pxStack;
/* Check the alignment of the stack buffer is correct. */
configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxNewTCB->pxStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
/* The other extreme of the stack space is required if stack checking is
performed. */
pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
}
#endif /* portSTACK_GROWTH */
/* 将task name 存入TCB */
if( pcName != NULL )
{
for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
{
pxNewTCB->pcTaskName[ x ] = pcName[ x ];
/* Don't copy all configMAX_TASK_NAME_LEN if the string is shorter than
configMAX_TASK_NAME_LEN characters just in case the memory after the
string is not accessible (extremely unlikely). */
if( pcName[ x ] == ( char ) 0x00 )
{
break;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* Ensure the name string is terminated in the case that the string length
was greater or equal to configMAX_TASK_NAME_LEN. */
pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
}
else
{
pxNewTCB->pcTaskName[ 0 ] = 0x00;
}
/* 优先级必须小于configMAX_PRIORITIES,否则将会被强制设置成configMAX_PRIORITIES - 1. */
if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
{
uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
pxNewTCB->uxPriority = uxPriority;
#if ( configUSE_MUTEXES == 1 )
{
pxNewTCB->uxBasePriority = uxPriority;
pxNewTCB->uxMutexesHeld = 0;
}
#endif /* configUSE_MUTEXES */
/* 初始化xStateList “状态链表项” 和xEventListItem list “事件链表项” */
vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
/* Set the pxNewTCB as a link back from the ListItem_t. This is so we can get
back to the containing TCB from a generic item in a list. */
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
/* 事件等待相关的列表列表是按照xItemValue递增排序的,确保高优先级任务排在前面。*/
listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
{
pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
}
#endif /* portCRITICAL_NESTING_IN_TCB */
/* 初始化pxTaskTag,参见 注解2 */
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
{
pxNewTCB->pxTaskTag = NULL;
}
#endif /* configUSE_APPLICATION_TASK_TAG */
/* 运行花费的CPU时间(节拍)清零 */
#if ( configGENERATE_RUN_TIME_STATS == 1 )
{
pxNewTCB->ulRunTimeCounter = 0UL;
}
#endif /* configGENERATE_RUN_TIME_STATS */
#if ( portUSING_MPU_WRAPPERS == 1 ) /* 暂不考虑MPU分支 */
{
vPortStoreTaskMPUSettings( &( pxNewTCB->xMPUSettings ), xRegions, pxNewTCB->pxStack, ulStackDepth );
}
#else
{
/* Avoid compiler warning about unreferenced parameter. */
( void ) xRegions;
}
#endif
#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
{
/* 初始化Thread Local数组 */
for( x = 0; x < ( UBaseType_t ) configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ )
{
pxNewTCB->pvThreadLocalStoragePointers[ x ] = NULL;
}
}
#endif
/* Notify 相关初始化 */
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
{
pxNewTCB->ulNotifiedValue = 0;
pxNewTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
}
#endif
#if ( configUSE_NEWLIB_REENTRANT == 1 )
{
/* Initialise this task's Newlib reent structure.
See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
for additional information. */
_REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );
}
#endif
#if( INCLUDE_xTaskAbortDelay == 1 )
{
pxNewTCB->ucDelayAborted = pdFALSE;
}
#endif
/* 初始化任务的Stack */
#if( portUSING_MPU_WRAPPERS == 1 ) /* 暂不考虑MPU分支 */
{
/* If the port has capability to detect stack overflow,
pass the stack end address to the stack initialization
function as well. */
#if( portHAS_STACK_OVERFLOW_CHECKING == 1 )
{
#if( portSTACK_GROWTH < 0 ) /* Cortex-M3 系列是这个分支 高地址向低地址增长 */
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters, xRunPrivileged );
}
#else /* portSTACK_GROWTH */
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters, xRunPrivileged );
}
#endif /* portSTACK_GROWTH */
}
#else /* portHAS_STACK_OVERFLOW_CHECKING */
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged );
}
#endif /* portHAS_STACK_OVERFLOW_CHECKING */
}
#else /* portUSING_MPU_WRAPPERS */
{
/* If the port has capability to detect stack overflow,
pass the stack end address to the stack initialization
function as well. */
#if( portHAS_STACK_OVERFLOW_CHECKING == 1 )
{
#if( portSTACK_GROWTH < 0 )
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters );
}
#else /* portSTACK_GROWTH */
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters );
}
#endif /* portSTACK_GROWTH */
}
#else /* portHAS_STACK_OVERFLOW_CHECKING */
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
}
#endif /* portHAS_STACK_OVERFLOW_CHECKING */
}
#endif /* portUSING_MPU_WRAPPERS */
if( pxCreatedTask != NULL )
{
/* 返回TCB Handler */
*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
[1] WaterMask 是一给非常巧妙的机制,用于判断当前Task使用了多少栈,防止栈size过小导致溢出。其原理是在Task执行前,将栈的内容全部初始化为一个特定的值(此处是 0xA5 ),当Task执行完成后,栈的内容会被修改,就像栈顶就像“水位线”一样,随着栈的使用会将0xA5修改为其他值,最后:
栈size - pxEndOfStack开始剩余0xA5的个数 = 栈的最大使用size
[2] pxTaskTag一个用户自定义的指针函数,类似Android View的tag,用户可以用Tag携带任意函数在Task的任意生命周期,只要有 TaskHandle_t 就可以调用该函数指针做 相关操作,这样增加了灵活性,同时不破坏TaskHandle_t核心结构 ,并降低耦合。
[3] pvThreadLocalStoragePointers “线程封闭”,当访问共享的可变数据时,通常需要使用同步。一种避免使用同步的方式就是不共享数据。类似Java的ThreadLocal。
vTaskDelete
🧔 老李:
员工离职(vTaskDelete(NULL)) & HR裁员(vTaskDelete(laowang)) 😛
关键步骤
- 如果在 就绪
list或Delay list中,则从 相关list中移除 - 删除资源
- 让出CPU(调度器未运行)
参数:
TaskHandle_t xTaskToDelete: 需要删除的Task的TCB,如果xTaskToDelete == NULL,则说明是Task“自杀”。
#if ( INCLUDE_vTaskDelete == 1 )
void vTaskDelete( TaskHandle_t xTaskToDelete )
{
TCB_t *pxTCB;
taskENTER_CRITICAL();/* 进入临界区,暂停禁止任务切换与中断服务,确保关键代码执行的原子性 */
{
/* 如果xTaskToDelete == NULL则返回当前Task的TCB,也就是Task主动离职,否则返回xTaskToDelete */
pxTCB = prvGetTCBFromHandle( xTaskToDelete );
/* 从就绪list或Delay list中移除 */
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
/* 当前优先队列为空,则将当前优先级从就绪优先级位图(uxTopReadyPriority)中清除 */
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 如果当前Task在事件等待队列中,则从相关队列中移除 */
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 类似PID */
uxTaskNumber++;
if( pxTCB == pxCurrentTCB )
{
/* 当任务“主动离职”的时候,不能在自己的执行上下文中完成删除自身资源,它会被加入到xTasksWaitingTermination僵尸Task列表中,等待清洁工阿姨IdleTask进行回收 */
vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );
/* 待清理任务数量+1,IdleTask会逐一清理直至uxDeletedTasksWaitingCleanUp为0 */
++uxDeletedTasksWaitingCleanUp;
traceTASK_DELETE( pxTCB );
/* The pre-delete hook is primarily for the Windows simulator,
in which Windows specific clean up operations are performed,
after which it is not possible to yield away from this task -
hence xYieldPending is used to latch that a context switch is
required. */
portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
}
else
{
/* “裁员模式”HR会帮助员工(Task)做好“善后”工作 */
--uxCurrentNumberOfTasks;
traceTASK_DELETE( pxTCB );
/* 删除TCB */
prvDeleteTCB( pxTCB );
/* 计算下一个Delay任务解除阻塞的时间 */
prvResetNextTaskUnblockTime();
}
}
taskEXIT_CRITICAL(); /* 退出临界区 */
/* 调度器没有在运行 */
if( xSchedulerRunning != pdFALSE )
{
if( pxTCB == pxCurrentTCB )
{
configASSERT( uxSchedulerSuspended == 0 );
portYIELD_WITHIN_API(); /* 调度器没有运行,而且当前Task是“主动离职”则需要让出CPU给其他员工(Task)使用 */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
#endif /* INCLUDE_vTaskDelete */
prvDeleteTCB
🧔 老李:
回收 员工(Task)工牌(TCB) 等资源。
关键步骤
- 调用平台相关的TCB清理函数
- 释放
TCB_t和 栈内存
参数:
TCB_t *pxTCB: 需要删除的Task的TCB
#if ( INCLUDE_vTaskDelete == 1 )
static void prvDeleteTCB( TCB_t *pxTCB )
{
/* 清理相关的宏或函数,其具体实现依赖于目标硬件平台port层 */
portCLEAN_UP_TCB( pxTCB );
#if ( configUSE_NEWLIB_REENTRANT == 1 ) /* NewLib相关,暂不考虑 */
{
_reclaim_reent( &( pxTCB->xNewLib_reent ) );
}
#endif /* configUSE_NEWLIB_REENTRANT */
/* 允许动态分配内存 并且 不允许静态分配内存 并且 没有启动MPU。则说明当前创建的Task是动分配的内存 */
#if( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 0 ) && ( portUSING_MPU_WRAPPERS == 0 ) )
{
/* 释放栈内存,必须先释放堆内存,如果先释放pxTCB,则pxTCB->pxStack为非法地址 */
vPortFree( pxTCB->pxStack );
/* 释放TCB内存 */
vPortFree( pxTCB );
}
#elif( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /* 既 允许动态分配内存 又 允许静态分配内存,则需要判断调用prvDeleteTCB的是xTaskCreate 还是 xTaskCreateStatic */
{
if( pxTCB->ucStaticallyAllocated == tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB )
{
/* 当前Task是在xTaskCreate中创建的,可以先free pxStack 然后 free pxTCB,此处要注意free顺序防止发生访问非法地址 */
/* Both the stack and TCB were allocated dynamically, so both
must be freed. */
vPortFree( pxTCB->pxStack );
vPortFree( pxTCB );
}
else if( pxTCB->ucStaticallyAllocated == tskSTATICALLY_ALLOCATED_STACK_ONLY )
{
/* 当栈内存是静态分配的时候,只释放TCB_t */
vPortFree( pxTCB );
}
else
{
/* 栈内存和TCB都是静态创建的,这时候configASSERT一下ucStaticallyAllocated */
configASSERT( pxTCB->ucStaticallyAllocated == tskSTATICALLY_ALLOCATED_STACK_AND_TCB );
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
}
prvResetNextTaskUnblockTime
🧔 老李:
计算最近的一个员工休假回来的时间
关键步骤
- 计算下一个Delay任务解除阻塞的时间
参数:
void: 无参数
static void prvResetNextTaskUnblockTime( void )
{
TCB_t *pxTCB;
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
{
/* 当DelayTaskList为空时,意味着没有排队等待执行的员工(Task),则这个时间是无限大,也就是portMAX_DELAY */
xNextTaskUnblockTime = portMAX_DELAY;
}
else
{
/* 如果不为空,则获取DelayTaskList头部的Task,由于TCB->xStateListItem在插入pxDelayedTaskList时是按照xStateListItem(xTimeToWake需要Delay)的升序有序插入的,所以头部的Task就是最短Block时间 */
( pxTCB ) = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
/* 获取xStateListItem也就是xTimeToWake */
xNextTaskUnblockTime = listGET_LIST_ITEM_VALUE( &( ( pxTCB )->xStateListItem ) );
}
}