@(嵌入式)
FreeRtos
简述
FreeRTOS 的任务调度在 Source/include/task.c 中实现,包含了任务的创建、切换、挂起、延时和删除等所有功能。涉及到的链表组织见文章 <FreeRTOS 任务调度 List 组织> 。任务切换实现代码量比较大,因此关于任务调度这一块会分几个文章来描述,这一篇主要分析任务的创建的调用与实现。
分析的源码版本是 v9.0.0
(为了方便查看,github 上保留了一份源码Source目录下的拷贝)
任务状态
系统运行过程,任务可能处于以下各种状态,各个状态之间切换的关系如上图所示。
- Running
运行状态, 当前正在执行,占有处理器的任务 - Ready
就绪状态,准备被运行的任务,没有被挂起和阻塞,但不是当前正在执行的任务,等待更高优先级任务或者同等级任务时间片结束释放处理器 - Blocked
阻塞状态,任务在等待一个事件而进入阻塞状态,比如延时、获取信号量等 - Suspended
挂起状态,任务由于调用vTaskSuspend()
而被挂起不能被执行, 直到调用xTaskResume()
重新恢复
使用示例
FreeRTOS 中创建任务并开始调度的基本框架如下 :
void vATaskFunction( void *pvParameters )
{
for( ;; )
{
// -- 任务代码 --
}
// 任务不能有任何 返回
// 对自行结束的任务,退出前需要自行清理
vTaskDelete( NULL );
}
void main(void)
{
static unsigned char ucParameterToPass;
xTaskHandle xHandle;
xTaskCreate( vATaskFunction, /*任务实现函数*/
"TASK_NAME", /*任务名,方便调试*/
STACK_SIZE, /*任务堆栈大小 *StackType_t*/
&ucParameterToPass, /*任务运行时的参数*/
tskIDLE_PRIORITY, /*任务优先级*/
&xHandle ); /*回传任务句柄,供其他地方引用任务*/
// 其他任务和拉拉杂杂的初始化
// 启动任务调度器 loop ....
}
任务创建函数中, 设置的栈大小单位由使用平台的 StackType_t
决定,不同平台栈指针对齐有自己的要求。
回传的句柄(指向TCB的指针)一般用于在其他任务中发送消息通知给任务,或者删除任务时引用。
任务成功创建后返回 pdPASS
, 否则失败回传错误码。
另外,删除任务,可以通过其他任务中调用 voidvTaskDelete
进行删除,此时该任务会从各种链表中移除,并且内存会被马上回收; 但是如果是任务自己调用删除,则其内存回收需要由空闲任务来完成(毕竟当前正在使用这些资源)。
使用 voidvTaskDelete
的前提是在 FreeRTOSConfig.h 设置 INCLUDE_vTaskDelete
为1(Tips !! API 在使用前最后需要看看是否需要设置对应的宏定义)。
叙述完上层的调用,后续介绍背后具体是如何实现的。
数据结构
TCB
任务调度离不开任务控制块(TCB), 用于存储任务的状态信息、运行时环境等。源代码见 tskTaskControlBlock, 以下具体介绍下这个数据结构。
typedef struct tskTaskControlBlock
{
// 任务栈顶指针
volatile StackType_t *pxTopOfStack;
// 启用MPU 的情况下设置
#if ( portUSING_MPU_WRAPPERS == 1 )
// 设置任务访问内存的权限
xMPU_SETTINGS xMPUSettings;
#endif
// 状态链表项(Ready, Blocked, Suspended)
// 任务处于不同状态 该项会被插入到对应的链表, 供链表引用任务
ListItem_t xStateListItem;
// 事件链表项
// 比如任务延时挂起等,被插入到延时链表中,到时间或事件发生,链表引用唤醒任务
ListItem_t xEventListItem;
// 任务优先级 0 最低
UBaseType_t uxPriority;
// 任务栈内存起始地址
StackType_t *pxStack;
// 任务名, 字符串, 一般供调试时使用
char pcTaskName[ configMAX_TASK_NAME_LEN ];
// 对于向上生长的栈, 用于指明栈的上边界,用于判断是否溢出
#if ( portSTACK_GROWTH > 0 )
StackType_t *pxEndOfStack;
#endif
// 边界嵌套计数
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
UBaseType_t uxCriticalNesting;
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
// 调试, 标识这个任务是第几个被创建
// 每创建一个任务, 系统有个全局变量就会加一, 并赋值给这个新任务
UBaseType_t uxTCBNumber;
// 调试 供用户设置特定数值
UBaseType_t uxTaskNumber;
#endif
#if ( configUSE_MUTEXES == 1 )
// 涉及互斥锁下的优先级继承(避免优先级反转), queue 那边介绍
// 当优先级被临时提高(继承了拿锁被堵的高优先级任务)时,这个变量保存任务实际的优先级
UBaseType_t uxBasePriority;
UBaseType_t uxMutexesHeld;
#endif
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
TaskHookFunction_t pxTaskTag;
#endif
#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
// 存储一些本地数据的指针
void *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
#endif
#if( configGENERATE_RUN_TIME_STATS == 1 )
// 记录任务运行状态下的总时间
uint32_t ulRunTimeCounter;
#endif
#if ( configUSE_NEWLIB_REENTRANT == 1 )
//为任务分配一个Newlibreent结构体变量
// Newlib是一个C库函数,非FreeRTOS维护
// 个人没用过,不清楚
struct _reent xNewLib_reent;
#endif
#if( configUSE_TASK_NOTIFICATIONS == 1 )
// 任务通知
volatile uint32_t ulNotifiedValue;
volatile uint8_t ucNotifyState;
#endif
#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
// 表明任务栈和TCB占用的是 heap 还是 stack
// 供任务删除回收时判断
uint8_t ucStaticallyAllocated;
#endif
#if( INCLUDE_xTaskAbortDelay == 1 )
uint8_t ucDelayAborted;
#endif
} tskTCB;
typedef tskTCB TCB_t;
任务控制块中有两个链表项 xStateListItem
和 xEventListItem
, 在前面文章提到链表项中有一个指针指向所属的TCB。当任务状态变化或者等待事件的时候,将任务所属的这个链表项插入到对应的链表中,系统调度器就是通过这个方式追踪每个任务, 当符合条件的情况下,系统会通过该链表项引用任务,实现任务切换等操作。
链表
如上所述, 系统中包含的链表定义如下。
// 就绪任务链表 每个优先级对应一个链表
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
// 延时任务链表
PRIVILEGED_DATA static List_t xDelayedTaskList1;
PRIVILEGED_DATA static List_t xDelayedTaskList2;
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;
// 就绪任务链表,当任务调度器被挂起时,状态变换为就绪的任务先保存在此,
// 恢复后移到 pxReadyTasksLists 中
PRIVILEGED_DATA static List_t xPendingReadyList;
// 任务删除后,等待空闲任务释放内存
#if( INCLUDE_vTaskDelete == 1 )
PRIVILEGED_DATA static List_t xTasksWaitingTermination;
PRIVILEGED_DATA static volatile UBaseType_t uxDeletedTasksWaitingCleanUp =
( UBaseType_t ) 0U;
#endif
// 被挂起的任务链表
#if ( INCLUDE_vTaskSuspend == 1 )
PRIVILEGED_DATA static List_t xSuspendedTaskList;
#endif
任务创建
FreeRTOS V9.0.0 版本提供三个函数用于创建任务
- xTaskCreateStatic
通过传递的静态内存创建任务 - xTaskCreate
通过动态申请的内存创建任务 - xTaskCreateRestricted
创建任务参数通过TaskParameters_t
传递给函数,用户自己申请栈的内存,创建函数只负责申请 TCB 所需内存空间
项目中接触版本 V8.0.0, 发现有一些改动, 旧版中实际创建任务的函数实际是 xTaskGenericCreate
, 参数比较多, 可以实现从 heap 动态申请内存或通过静态内存创建任务, 而一般用到的xTaskCreate
实际是一个宏,调用了 xTaskGenericCreate
, 默认采用动态申请内存的方式。
以下主要介绍 xTaskCreateStatic
和 xTaskCreate
这两个函数的实现。
静态创建任务
源代码 xTaskCreateStatic
静态的方式创建任务,需要用户先申请任务控制模块和任务栈需要的内存(一般使用静态内存),然后把内存地址传递给函数,函数负责其他初始化。
函数按顺序完成:
- 根据用户传递内存,初始化任务 TCB
- 初始化任务堆栈
- 将新建任务加入到就绪链表中
- 如果调度器运行,新任务优先级更高,触发系统切换
TaskHandle_t xTaskCreateStatic(
TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer )
{
TCB_t *pxNewTCB;
TaskHandle_t xReturn;
configASSERT( puxStackBuffer != NULL );
configASSERT( pxTaskBuffer != NULL );
if ((pxTaskBuffer != NULL) && (puxStackBuffer != NULL))
{
// 设置用户传递进来的任务控制块和栈的内存地址到对应指针变量
pxNewTCB = (TCB_t *)pxTaskBuffer;
pxNewTCB->pxStack = (StackType_t *)puxStackBuffer;
#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
{
// 标识这个任务控制块和栈内存时静态的
// 删除任务的时候, 系统不会做内存回收处理
pxNewTCB->ucStaticallyAllocated =
tskSTATICALLY_ALLOCATED_STACK_AND_TCB;
}
#endif
// 初始化任务控制块 下文介绍
prvInitialiseNewTask( pxTaskCode, pcName,
ulStackDepth, pvParameters, uxPriority,
&xReturn, pxNewTCB, NULL );
// 把新任务插入就绪链表 下文介绍
prvAddNewTaskToReadyList( pxNewTCB );
}
else
{
xReturn = NULL;
}
return xReturn;
}
动态创建任务
源代码 xTaskCreate
动态创建任务, 调用函数内部向系统申请创建新任务所需的内存,包括任务控制块和栈。 所以调用这个函数,在内存堆空间不足或者碎片话的情况下,可能创建新任务失败,需要判断函数执行后是否成功返回。 其源码解析如下所示。
BaseType_t xTaskCreate(
TaskFunction_t pxTaskCode,
const char * const pcName,
const uint16_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
{
TCB_t *pxNewTCB;
BaseType_t xReturn;
// 如果是向下增长的栈, 先申请栈内存再申请任务控制块内存
// 可以避免栈溢出覆盖了自己任务控制块
// 对应向上增长的则相反
// 在旧版本 V8.0.0 中没有这么处理,统一先 TCB 后 Stack
// 项目上碰到平台栈向下增长, 栈溢出错时候覆盖了自己的 TCB
// 导致调试的时候无法获取出错任务信息(比如任务名)
#if( portSTACK_GROWTH > 0 )
{
// 申请任务控制块内存
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 )
{
// 栈内存申请失败, 释放前面申请的任务控制块内存
vPortFree( pxNewTCB );
pxNewTCB = NULL;
}
}
}
#else /*栈向下增长*/
{
StackType_t *pxStack;
pxStack = (StackType_t *)pvPortMalloc(
(((size_t)usStackDepth) * sizeof(StackType_t)));
if( pxStack != NULL )
{
pxNewTCB = (TCB_t *)pvPortMalloc(sizeof(TCB_t));
if( pxNewTCB != NULL )
{
pxNewTCB->pxStack = pxStack;
}
else
{
vPortFree( pxStack );
}
}
else
{
pxNewTCB = NULL;
}
}
#endif
if( pxNewTCB != NULL )
{
// 成功申请所需内存 执行任务初始化操作
#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
{
// 标志任务控制块和栈是动态申请
// 删除任务系统会自动回收内存
pxNewTCB->ucStaticallyAllocated =
tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
// 初始任务控制块
prvInitialiseNewTask(pxTaskCode, pcName,
(uint32_t)usStackDepth, pvParameters,
uxPriority, pxCreatedTask, pxNewTCB, NULL );
// 将新任务插入到就绪链表
prvAddNewTaskToReadyList( pxNewTCB );
xReturn = pdPASS;
}
else
{
// 创建任务失败,返回错误码
xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
}
return xReturn;
}
初始化任务控制块
在创建任务的函数中, 如果成功获得新任务所需要的内存空间, 则会调用以下函数对任务控制块 TCB 的成员变量进行初始化。
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;
// 如果开启了 MPU, 判断任务是否运行在特权模式
#if( portUSING_MPU_WRAPPERS == 1 )
BaseType_t xRunPrivileged;
if( ( uxPriority & portPRIVILEGE_BIT ) != 0U )
{
// 优先级特权模式掩码置位
// 任务运行在特权模式
xRunPrivileged = pdTRUE;
}
else
{
xRunPrivileged = pdFALSE;
}
uxPriority &= ~portPRIVILEGE_BIT;
#endif /* portUSING_MPU_WRAPPERS == 1 */
#if( ( configCHECK_FOR_STACK_OVERFLOW > 1 )
|| ( configUSE_TRACE_FACILITY == 1 )
|| ( INCLUDE_uxTaskGetStackHighWaterMark == 1 ) )
{
// 调试 栈初始化填充指定数据(默认 0x5a)
(void)memset(pxNewTCB->pxStack,
(int)tskSTACK_FILL_BYTE,
(size_t)ulStackDepth * sizeof(StackType_t));
}
#endif
#if( portSTACK_GROWTH < 0 )
{
// 向下增长栈, 初始化栈顶在内存高位
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));
}
#else
{
// 向上增长栈, 初始化栈顶在内存低位
pxTopOfStack = pxNewTCB->pxStack;
// 字节对齐断言
configASSERT((((portPOINTER_SIZE_TYPE)pxTopOfStack &
(portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK) == 0UL));
// 设置上边界
pxNewTCB->pxEndOfStack =
pxNewTCB->pxStack + (ulStackDepth - (uint32_t)1);
}
#endif /* portSTACK_GROWTH */
// 存储任务名数组 方便调试
for( x = (UBaseType_t)0; x < (UBaseType_t)configMAX_TASK_NAME_LEN; x++ )
{
pxNewTCB->pcTaskName[x] = pcName[x];
// 字符串结束
if( pcName[ x ] == 0x00 )
{
break;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
// 确保任务名有正确字符串结尾
pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
// 限制任务优先级在设置范围内
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 */
// 初始化包含的两个链表项
vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
// 设置状态链表项的 pvOwner 指向所属 TCB
// 如此,系统可以通过该项引用到任务
// 比如任务状态切换到就绪时,则这个链表项会被插入到 就绪链表
// 系统从就绪链表取出这一项进而获得 TCB(ListItem->pvOwner),切换到运行状态
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
// 写入优先级 用于在对应事件链表中排序
// 链表中是按从小到达排序,因此为了实现优先级高的在前
// 两者相反,所以写入优先级的 “补数”
// 保证优先级高的任务,插入时在链表靠前
listSET_LIST_ITEM_VALUE(&(pxNewTCB->xEventListItem),
(TickType_t)configMAX_PRIORITIES - (TickType_t)uxPriority);
// 设置所属 TCB, 同上
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );
// 初始化嵌套 0
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
{
pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
}
#endif /* portCRITICAL_NESTING_IN_TCB */
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
{
pxNewTCB->pxTaskTag = NULL;
}
#endif /* configUSE_APPLICATION_TASK_TAG */
#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
{
// 避免编译报 warning 没有使用变量
( void ) xRegions;
}
#endif
// 初始化任务局部数据指针
#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
{
for( x = 0; x < (UBaseType_t) configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ )
{
pxNewTCB->pvThreadLocalStoragePointers[ x ] = NULL;
}
}
#endif
// 初始化任务消息通知变量
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
{
pxNewTCB->ulNotifiedValue = 0;
pxNewTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
}
#endif
#if ( configUSE_NEWLIB_REENTRANT == 1 )
{
_REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );
}
#endif
#if( INCLUDE_xTaskAbortDelay == 1 )
{
pxNewTCB->ucDelayAborted = pdFALSE;
}
#endif
// 初始化栈 使其像任务已经运行了,但是被调度器中断切换,入栈做了现场保护
// 当任务被调度器取出后, 可以直接执行出栈恢复现场,运行任务
// 而不需要调度器额外特殊处理第一次运行的任务
// 栈初始化涉及系统底层, 由对应平台移植层提供
// 见下举例栈初始化
#if( portUSING_MPU_WRAPPERS == 1 )
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack(pxTopOfStack,
pxTaskCode, pvParameters, xRunPrivileged);
}
#else /* portUSING_MPU_WRAPPERS */
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack(pxTopOfStack,
pxTaskCode, pvParameters);
}
#endif /* portUSING_MPU_WRAPPERS */
if(( void *)pxCreatedTask != NULL )
{
// 返回任务引用, 可用于修改优先级,通知或者删除任务等.
*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
栈初始化举例
新任务初始化任务后,使得当前新建任务像已经运行,但是被调度器中断,栈中保存该任务被中断时的现场,但轮到该任务执行的时候,系统可以直接执行现场恢复,运行任务。
不同平台实现任务切换时的现场保护可能不一样,所以该函数由平台移植层提供
列举 Cotex-M3 没有MPU下的栈初始化函数, 向下增长栈。
StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
// 模拟任务被切换前的现场保护
// 调度切换回来可以统一执行恢复操作
pxTopOfStack--;
*pxTopOfStack = portINITIAL_XPSR; /* xPSR */
pxTopOfStack--;
// 指向任务函数
*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;
pxTopOfStack--;
*pxTopOfStack = ( StackType_t ) prvTaskExitError; /* LR */
pxTopOfStack -= 5; /* R12, R3, R2 and R1. */
// 传递参数
*pxTopOfStack = ( StackType_t ) pvParameters; /* R0 */
pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */
return pxTopOfStack;
}
如上初始化后栈如下所示
-- 低位地址
pxStack-> | .. |
---|---|
.. | .. |
pxTopOfStack-> | R4 |
.. | |
R11 | |
R0 | |
R1 | |
R2 | |
R3 | |
R12 | |
LR : prvTaskExitError | |
PC : pxCode | |
XPSR :portINITIAL_XPSR |
-- 高位地址
初始化后,当任务第一次真正被运行,当前环境设置,使其从对应的函数入口开始执行。
其中LR 寄存器设置的地址是系统的出错处理函数,如果任务错误返回,就会调用该函数。
根据 约定, R0~R3保存调用时传递的参数。
插入就绪链表
任务创建初始化后,需要将任务插入到就绪链表中,通过调度器切换到运行状态。
该函数主要实现将新任务加入就绪链表,第一次调用该函数会进行系统必要的初始化,同时,判断是否需要马上执行任务切换,保证更高优先级的就绪任务可以及时获得CPU 的使用权限。
注意,这里提到的把任务插入到链表,是指将任务所含的链表项插入到合适的链表中,而但需要重新取回任务,则通过该链表项中指向所属任务的指针实现。
static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
{
// 进入边界, 关闭中断(平台相关,移植层实现)
taskENTER_CRITICAL();
{
// 当前任务数加一
uxCurrentNumberOfTasks++;
if( pxCurrentTCB == NULL )
{
// 如果当前没有运行任务,设置新任务为当前运行任务
pxCurrentTCB = pxNewTCB;
if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
{
// 第一个任务,系统执行必要的初始化
// 初始化各个链表
prvInitialiseTaskLists();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
if( xSchedulerRunning == pdFALSE )
{
// 调度器没有运行
// 新任务优先级优先级更高
// 直接设置新任务为当前任务
if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
{
pxCurrentTCB = pxNewTCB;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
// 记录创建任务数
uxTaskNumber++;
#if ( configUSE_TRACE_FACILITY == 1 )
{
// 调试追踪用
pxNewTCB->uxTCBNumber = uxTaskNumber;
}
#endif /* configUSE_TRACE_FACILITY */
traceTASK_CREATE( pxNewTCB );
// 将任务加入到就绪链表
// 不同优先级对应不同就绪链表
// 宏实现,同时更新就绪的最高优先级
prvAddTaskToReadyList( pxNewTCB );
portSETUP_TCB( pxNewTCB );
}
// 退出边界,恢复中断
taskEXIT_CRITICAL();
if( xSchedulerRunning != pdFALSE )
{
// 调度器已经启动
// 新任务优先级比正在运行的任务高
// 触发系统执行任务切换
if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
{
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
之前的文章分析过 FreeRtos 的链表,同样,当第一次调用将新任务插入就绪链表这个函数,会对系统涉及的几个链表进行初始化。
调度器会在每次任务切换中,依据优先级顺序从链表中选出合适的任务,相同优先级任务在同一个就绪链表中,系统按照时间片轮序调度(如果使能),