FreeRTOS源码解析(任务创建)

1.1任务的核心TCB控制块

TCB 是任务控制块,它是操作系统用来管理和调度任务的核心数据结构。每个任务都会有一个对应的 TCB,里面保存了任务的所有关键信息,包括:

  1. 栈信息pxStack 指向栈底,pxTopOfStack 指向栈顶栈保存了任务上下文(寄存器、返回地址等),上下文切换时会用到
  2. 任务状态信息xStateListItem 和 xEventListItem 保存任务在就绪列表或事件等待列表中的位置uxPriority 保存任务优先级,用于调度算法
  3. 调试与统计信息任务名称、任务编号、运行时间统计等支持 Newlib 重入、任务通知、互斥锁管理等
  4. 扩展功能支持静态/动态分配标记临界区嵌套计数、线程本地存储指针、应用钩子等

总结

  • TCB 是 任务的“大脑”,FreeRTOS 通过它管理任务的状态、栈和调度信息。
  • 无论是静态任务还是动态任务,每个任务都需要一个 TCB。
  • TCB 的设计高度可配置,可以根据内存大小和功能需求裁剪不同成员。
typedef struct tskTaskControlBlock
{
	volatile StackType_t *pxTopOfStack;   /* 指向任务栈顶的指针(最后一个压入栈的值)。
	                                       * 注意:这是 TCB 的第一个成员,FreeRTOS 依赖它进行上下文切换。 */

	#if ( portUSING_MPU_WRAPPERS == 1 )
	xMPU_SETTINGS xMPUSettings;           /* MPU(内存保护单元)设置,仅在启用 MPU 时使用。
	                                       * 必须是 TCB 的第二个成员。 */
	#endif

	ListItem_t xStateListItem;            /* 任务状态列表节点,用于表示任务的状态(就绪、阻塞、挂起等)。 */
	ListItem_t xEventListItem;            /* 事件列表节点,用于任务在事件等待队列中。 */
	UBaseType_t uxPriority;               /* 任务优先级,数值越大优先级越高,0 为最低优先级。 */
	StackType_t *pxStack;                 /* 指向任务栈的起始位置(栈底)。 */
	char pcTaskName[configMAX_TASK_NAME_LEN]; /* 任务名称,用于调试,不影响调度。 */

	#if ((portSTACK_GROWTH > 0) || (configRECORD_STACK_HIGH_ADDRESS == 1))
	StackType_t *pxEndOfStack;            /* 指向栈的最高有效地址(栈顶),便于调试或栈溢出检查。 */
	#endif

	#if (portCRITICAL_NESTING_IN_TCB == 1)
	UBaseType_t uxCriticalNesting;        /* 保存临界区嵌套计数,适用于没有在端口层维护计数的 MCU。 */
	#endif

	#if (configUSE_TRACE_FACILITY == 1)
	UBaseType_t uxTCBNumber;              /* 每次创建 TCB 的编号,用于调试追踪任务生命周期。 */
	UBaseType_t uxTaskNumber;             /* 供第三方跟踪代码使用的任务编号。 */
	#endif

	#if (configUSE_MUTEXES == 1)
	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)
	struct _reent xNewLib_reent;          /* Newlib 重入结构体,确保每个任务独立的库函数运行环境。 */
	#endif

	#if(configUSE_TASK_NOTIFICATIONS == 1)
	volatile uint32_t ulNotifiedValue;    /* 任务通知值,用于事件通知机制。 */
	volatile uint8_t ucNotifyState;       /* 任务通知状态。 */
	#endif

	#if(tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0)
	uint8_t ucStaticallyAllocated;        /* 标记任务是否为静态分配,静态分配的任务不允许被释放。 */
	#endif

	#if(INCLUDE_xTaskAbortDelay == 1)
	uint8_t ucDelayAborted;               /* 标记延迟被中断。 */
	#endif

} tskTCB;

typedef tskTCB TCB_t; /* typedef 为 TCB_t,方便老调试器兼容 */


1.2创建任务的两种形式

静态创建任务:

// 确认用户提供的内存不为空
configASSERT(puxStackBuffer != NULL);
configASSERT(pxTaskBuffer != NULL);

// 检查 StaticTask_t 与 TCB_t 大小是否一致
volatile size_t xSize = sizeof(StaticTask_t);
configASSERT(xSize == sizeof(TCB_t));

if (pxTaskBuffer != NULL && puxStackBuffer != NULL) {
    // 使用用户提供的 TCB 和栈内存
    pxNewTCB = (TCB_t *)pxTaskBuffer;  
    pxNewTCB->pxStack = (StackType_t *)puxStackBuffer;

    #if tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE
    // 标记任务为静态分配
    pxNewTCB->ucStaticallyAllocated = tskSTATICALLY_ALLOCATED_STACK_AND_TCB;
    #endif

    // 初始化任务 TCB(如设置任务函数、优先级、参数等)
    prvInitialiseNewTask(pxTaskCode, pcName, ulStackDepth, pvParameters, uxPriority, &xReturn, pxNewTCB, NULL);

    // 将任务加入就绪列表
    prvAddNewTaskToReadyList(pxNewTCB);
} else {
    xReturn = NULL;
}

return xReturn; // 返回任务句柄


prvInitialiseNewTask(初始化任务函数)

栈对齐与栈初始化:

#if( tskSET_NEW_STACKS_TO_KNOWN_VALUE == 1 )
{
    // 将整个栈填充为已知的字节值(如 0xA5),便于调试栈溢出
    ( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, 
                     ( size_t ) ulStackDepth * sizeof( StackType_t ) );
}
#endif

  • 作用:用于调试,快速发现栈溢出或栈使用量。
  • tskSTACK_FILL_BYTE 默认是 0xA5,可在 FreeRTOSConfig.h 配置。

计算栈顶地址:

#if( portSTACK_GROWTH < 0 ) // 栈向低地址增长
    pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - 1 );
    // 对齐栈顶地址,满足 CPU 对齐要求
    pxTopOfStack = (StackType_t *)(((portPOINTER_SIZE_TYPE)pxTopOfStack) & ~(portBYTE_ALIGNMENT_MASK));
    configASSERT( (((portPOINTER_SIZE_TYPE)pxTopOfStack & portBYTE_ALIGNMENT_MASK) == 0UL) );
    pxNewTCB->pxEndOfStack = pxTopOfStack; // 保存栈顶信息
#else // 栈向高地址增长
    pxTopOfStack = pxNewTCB->pxStack;
    configASSERT( (((portPOINTER_SIZE_TYPE)pxNewTCB->pxStack & portBYTE_ALIGNMENT_MASK) == 0UL) );
    pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( ulStackDepth - 1 );
#endif

  • 作用:根据 CPU 架构计算栈顶位置,并对齐,保证上下文切换正确。
  • 栈增长方向portSTACK_GROWTH 决定。

设置任务名称:

for( x = 0; x < configMAX_TASK_NAME_LEN; x++ )
{
    pxNewTCB->pcTaskName[x] = pcName[x];
    if( pcName[x] == 0x00 ) break; // 遇到字符串结束符停止
}
pxNewTCB->pcTaskName[configMAX_TASK_NAME_LEN - 1] = '\0'; // 确保结束符

  • 作用:把任务名称存入 TCB,方便调试。
  • 如果名称过长,会截断并确保以 \0 结束。

设置任务优先级:

if( uxPriority >= configMAX_PRIORITIES )
{
    uxPriority = configMAX_PRIORITIES - 1; // 限制在最大优先级范围内
}
pxNewTCB->uxPriority = uxPriority;

#if ( configUSE_MUTEXES == 1 )
pxNewTCB->uxBasePriority = uxPriority; // 原始优先级
pxNewTCB->uxMutexesHeld = 0;           // 初始持有互斥量为 0
#endif

  • 作用:初始化任务的优先级信息,支持优先级继承机制。

初始化任务的状态列表节点:

vListInitialiseItem(&(pxNewTCB->xStateListItem));
vListInitialiseItem(&(pxNewTCB->xEventListItem));

listSET_LIST_ITEM_OWNER(&(pxNewTCB->xStateListItem), pxNewTCB);
listSET_LIST_ITEM_VALUE(&(pxNewTCB->xEventListItem), configMAX_PRIORITIES - uxPriority);
listSET_LIST_ITEM_OWNER(&(pxNewTCB->xEventListItem), pxNewTCB);

  • 作用:初始化两个链表节点,用于任务调度: xStateListItem:任务状态列表(就绪、阻塞、挂起)xEventListItem:事件等待列表
  • 事件列表按优先级排序,保证高优先级任务先执行。

其他 TCB 成员初始化:

#if ( portCRITICAL_NESTING_IN_TCB == 1 )
pxNewTCB->uxCriticalNesting = 0; // 临界区嵌套计数
#endif

#if ( configUSE_TASK_NOTIFICATIONS == 1 )
pxNewTCB->ulNotifiedValue = 0;
pxNewTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
#endif

#if ( configGENERATE_RUN_TIME_STATS == 1 )
pxNewTCB->ulRunTimeCounter = 0; // 初始化运行时间统计
#endif

#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
for(x = 0; x < configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++)
    pxNewTCB->pvThreadLocalStoragePointers[x] = NULL; // 线程本地存储初始化
#endif

  • 作用:初始化任务的各种扩展功能字段,确保任务启动时干净。

初始化任务栈,模拟任务已被中断:

#if( portUSING_MPU_WRAPPERS == 1 )
pxNewTCB->pxTopOfStack = pxPortInitialiseStack(pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged);
#else
pxNewTCB->pxTopOfStack = pxPortInitialiseStack(pxTopOfStack, pxTaskCode, pvParameters);
#endif

  • 核心作用:设置任务栈,使其看起来像“任务刚被调度器中断”,即上下文已经保存。
  • 包括: CPU 寄存器初始值返回地址指向任务函数 pxTaskCode任务参数 pvParameters

这样做是为了下一次任务切换时可以直接 pop 寄存器,任务从入口函数开始执行。

设置任务句柄:

if( pxCreatedTask != NULL )
{
    *pxCreatedTask = (TaskHandle_t) pxNewTCB; // 返回任务句柄
}

  • 作用:外部可以通过这个句柄控制任务(删除、修改优先级等)。

总结核心流程:

  1. 栈初始化:清空栈或填充已知值,计算栈顶地址并对齐
  2. 任务信息初始化:名称、优先级、互斥量、链表节点、扩展字段
  3. 上下文初始化:让栈看起来像任务已经被调度器中断,准备好第一次执行
  4. 返回任务句柄:外部可以通过句柄管理任务

prvAddNewTaskToReadyList(添加任务到就绪链表)

作用:把新任务 pxNewTCB 添加到就绪任务列表中,并根据当前调度状态决定是否立即切换任务。

函数作用概览:

static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )

  • 作用:把新任务 pxNewTCB 添加到就绪任务列表中,并根据当前调度状态决定是否立即切换任务。
  • 调用时机:在 xTaskCreatexTaskCreateStatic 创建任务后调用。

进入临界区:

taskENTER_CRITICAL();
{
    uxCurrentNumberOfTasks++;

  • 作用:禁止中断访问任务链表,保证修改任务列表的原子性。
  • uxCurrentNumberOfTasks++:增加系统中任务总数。

设置当前任务指针 pxCurrentTCB

if( pxCurrentTCB == NULL )
{
    pxCurrentTCB = pxNewTCB; // 当前系统中没有运行任务,直接设置为当前任务

    if( uxCurrentNumberOfTasks == 1 )
    {
        prvInitialiseTaskLists(); // 初始化任务就绪列表等内部数据结构
    }
}
else
{
    // 调度器未运行时,选优先级最高的任务作为当前任务
    if( xSchedulerRunning == pdFALSE )
    {
        if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
        {
            pxCurrentTCB = pxNewTCB;
        }
    }
}

  • 核心点: 如果系统中没有任务,直接设置 pxCurrentTCB。如果是系统中第一个任务,初始化任务链表。如果调度器还没启动,保证 pxCurrentTCB 指向优先级最高的任务。

任务编号与跟踪:

uxTaskNumber++;

#if ( configUSE_TRACE_FACILITY == 1 )
pxNewTCB->uxTCBNumber = uxTaskNumber; // 任务编号,用于调试追踪
#endif

traceTASK_CREATE( pxNewTCB ); // 用户可选的任务创建事件跟踪钩子

  • 作用: 每创建一个任务增加全局编号。用于调试或性能分析(如任务追踪)。

将任务加入就绪列表:

prvAddTaskToReadyList( pxNewTCB );

  • 作用: 真正把任务放入系统就绪列表。就绪列表按任务优先级排序,调度器会根据就绪列表选择下一个执行任务。

调用端口宏 portSETUP_TCB

portSETUP_TCB( pxNewTCB );

  • 作用: 一些端口可能需要做额外处理(例如 MPU 设置、特定寄存器初始化等)。

退出临界区:

taskEXIT_CRITICAL();

  • 恢复中断,使系统可以继续响应调度器和中断。

调度器已经运行时的任务切换判断:

if( xSchedulerRunning != pdFALSE )
{
    if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
    {
        taskYIELD_IF_USING_PREEMPTION(); // 当前任务优先级低,触发任务切换
    }
}

  • 核心点: 如果新任务优先级比当前任务高,立即触发调度(前置抢占)。taskYIELD_IF_USING_PREEMPTION() 宏会调用 portYIELD(),由底层切换到优先级更高的任务执行。

总结核心逻辑:

  1. 进入临界区,防止中断破坏任务列表。
  2. 更新系统任务总数
  3. 设置当前任务(首次创建或调度器未运行)。
  4. 任务编号与调试跟踪
  5. 加入就绪列表,按优先级排序。
  6. 端口特定初始化
  7. 退出临界区
  8. 抢占调度(如果调度器运行且新任务优先级高)。

这个函数是 FreeRTOS 任务创建后立即可调度的关键步骤,它保证了任务优先级调度、系统稳定性和调试信息完整。

添加到就绪链表的细节:

prvInitialiseTaskLists(初始化任务链表)

static void prvInitialiseTaskLists( void )
{
    UBaseType_t uxPriority;

    /* 初始化每个优先级的就绪任务列表 */
    for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ )
    {
        vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
        // pxReadyTasksLists 是一个数组,每个优先级对应一个就绪链表
        // 初始化链表为空,后续任务会加入对应优先级的链表
    }

    /* 初始化延时任务列表 */
    vListInitialise( &xDelayedTaskList1 );   // 延时任务链表 1
    vListInitialise( &xDelayedTaskList2 );   // 延时任务链表 2
    vListInitialise( &xPendingReadyList );   // 挂起后待就绪任务链表

    /* 如果配置了任务删除功能,则初始化等待删除的任务链表 */
    #if ( INCLUDE_vTaskDelete == 1 )
    {
        vListInitialise( &xTasksWaitingTermination );
        // 用于存放等待被删除的任务
    }
    #endif /* INCLUDE_vTaskDelete */

    /* 如果配置了任务挂起功能,则初始化挂起任务列表 */
    #if ( INCLUDE_vTaskSuspend == 1 )
    {
        vListInitialise( &xSuspendedTaskList );
        // 用于存放挂起的任务
    }
    #endif /* INCLUDE_vTaskSuspend */

    /* 设置延时任务链表指针,开始时使用 list1,溢出使用 list2 */
    pxDelayedTaskList = &xDelayedTaskList1;         // 当前延时任务列表
    pxOverflowDelayedTaskList = &xDelayedTaskList2; // 溢出延时任务列表
}

就绪任务列表(Ready List)

  • pxReadyTasksLists 是按优先级分组的数组,每个优先级有一个链表。
  • 用于调度器快速选择最高优先级可运行任务。

延时任务列表(Delayed Task List)

  • xDelayedTaskList1xDelayedTaskList2 用于管理处于 阻塞延时状态 的任务。
  • 当任务调用 vTaskDelay() 等延时函数时,会加入延时列表。
  • 使用双链表的原因是方便处理“时间溢出”的情况。

挂起任务列表(Suspended Task List)

  • 用于存放通过 vTaskSuspend() 挂起的任务。

等待删除任务列表(Tasks Waiting Termination)

  • 用于存放调用 vTaskDelete() 删除但尚未释放的任务。

初始化顺序

  • 所有链表初始化为空。
  • 延时任务链表的指针设置为 list1(主链表)和 list2(溢出链表)。

添加到就绪链表(prvAddTaskToReadyList)

#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 )

动态创建任务:

// 根据栈生长方向,先分配 TCB 或栈
#if portSTACK_GROWTH > 0
    pxNewTCB = (TCB_t *)pvPortMalloc(sizeof(TCB_t));
    if (pxNewTCB != NULL) {
        // 分配栈空间
        pxNewTCB->pxStack = (StackType_t *)pvPortMalloc(usStackDepth * sizeof(StackType_t));
        if (pxNewTCB->pxStack == NULL) {
            // 栈分配失败,释放已分配的 TCB
            vPortFree(pxNewTCB);
            pxNewTCB = NULL;
        }
    }
#else
    StackType_t *pxStack = (StackType_t *)pvPortMalloc(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
    pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
    #endif

    // 初始化任务 TCB
    prvInitialiseNewTask(pxTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL);

    // 将任务加入就绪列表
    prvAddNewTaskToReadyList(pxNewTCB);

    xReturn = pdPASS; // 创建成功
} else {
    xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY; // 内存分配失败
}

return xReturn;


区别对比:

内存分配

用户提供 TCB 和栈(静态)

FreeRTOS 内部动态分配

返回值

任务句柄

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

嵌入式面试八股文全集 文章被收录于专栏

这是一个全面的嵌入式面试专栏。主要内容将包括:操作系统(进程管理、内存管理、文件系统等)、嵌入式系统(启动流程、驱动开发、中断管理等)、网络通信(TCP/IP协议栈、Socket编程等)、开发工具(交叉编译、调试工具等)以及实际项目经验分享。专栏将采用理论结合实践的方式,每个知识点都会附带相关的面试真题和答案解析。

全部评论

相关推荐

评论
1
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务