FreeRTOS源码分析一:task创建(RISCV架构)
文章目录
- 前言
- 启动 Demo
- 主函数
- 创建任务
- prvCreateTask 创建任务
- 栈增长方向
- 具体例子
- 栈向下增长 (portSTACK_GROWTH = -1) - 当前配置
- 栈向上增长 (portSTACK_GROWTH > 0)
- prvInitialiseNewTask 真正初始化任务结构体的函数
- pxPortInitialiseStack 堆栈初始化函数
- prvAddNewTaskToReadyList 将新任务加入系统就绪列表
- 函数作用总结及调用链关系
- 1. `main_blinky`
- 2. `xTaskCreate`
- 3. `prvCreateTask`
- 4. `prvInitialiseNewTask`
- 5. `pxPortInitialiseStack`
- 6. `prvAddNewTaskToReadyList`
- 调用链箭头图
- 附
- 总结
前言
这里有 FreeRTOS 的移植需求,故对 FreeRTOS 做个源码分析,着重结合 RISCV 架构实现做分析。
本篇主要分析任务创建和启动部分。对 RISCV 架构有简单了解即可。
另外,移植工作中我只需考虑单核,所以我们暂不考虑多核情况。
启动 Demo
官方示例中提供了可在 qemu 上运行的 Demo。按以下命令执行:
git clone https://github.com/FreeRTOS/FreeRTOS.git --recurse-submodules
cd FreeRTOS/Demo/RISC-V-Qemu-virt_GCC/ && make
qemu-system-riscv32 -machine virt -bios none -nographic -kernel build/RTOSDemo.axf
正常运行得到结果:
主函数
主函数创建两个任务,然后开始调度。任务优先级分别为 2 和 1。
#define tskIDLE_PRIORITY 0#define mainQUEUE_RECEIVE_TASK_PRIORITY ( tskIDLE_PRIORITY + 2 )
#define mainQUEUE_SEND_TASK_PRIORITY ( tskIDLE_PRIORITY + 1 )int main_blinky( void )
{vSendString( "Hello FreeRTOS!" );/* Create the queue. */xQueue = xQueueCreate( mainQUEUE_LENGTH, sizeof( uint32_t ) );if( xQueue != NULL ){/* Start the two tasks as described in the comments at the top of this* file. */xTaskCreate( prvQueueReceiveTask, "Rx", configMINIMAL_STACK_SIZE * 2U, NULL,mainQUEUE_RECEIVE_TASK_PRIORITY, NULL );xTaskCreate( prvQueueSendTask, "Tx", configMINIMAL_STACK_SIZE * 2U, NULL,mainQUEUE_SEND_TASK_PRIORITY, NULL );}vTaskStartScheduler();return 0;
}
这里我们不关注队列 Queue。只关注 task 相关内容。
创建任务
调用 prvCreateTask 创建任务所需结构体,由 prvCreateTask 填充结构体内容。另外把新的任务加入就绪队列。
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,const char * const pcName,const configSTACK_DEPTH_TYPE uxStackDepth,void * const pvParameters,UBaseType_t uxPriority,TaskHandle_t * const pxCreatedTask ){TCB_t * pxNewTCB;BaseType_t xReturn;pxNewTCB = prvCreateTask( pxTaskCode, pcName, uxStackDepth, pvParameters, uxPriority, pxCreatedTask );if( pxNewTCB != NULL ){prvAddNewTaskToReadyList( pxNewTCB );xReturn = pdPASS;}else{xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;}return xReturn;}
这里我们只需关注 prvCreateTask 和 prvAddNewTaskToReadyList 这两个函数即可。
prvCreateTask 创建任务
根据宏portSTACK_GROWTH 考虑栈增长方向申请堆栈和 TCB空间并调用 prvInitialiseNewTask 初始化任务
static TCB_t * prvCreateTask( TaskFunction_t pxTaskCode,const char * const pcName,const configSTACK_DEPTH_TYPE uxStackDepth,void * const pvParameters,UBaseType_t uxPriority,TaskHandle_t * const pxCreatedTask ){TCB_t * pxNewTCB;/* If the stack grows down then allocate the stack then the TCB so the stack* does not grow into the TCB. Likewise if the stack grows up then allocate* the TCB then the stack. */#if ( portSTACK_GROWTH > 0 ){// 不成立宏,不考虑}#else /* portSTACK_GROWTH */{StackType_t * pxStack;/* Allocate space for the stack used by the task being created. *//* MISRA Ref 11.5.1 [Malloc memory assignment] *//* More details at: https://github.com/FreeRTOS/FreeRTOS-Kernel/blob/main/MISRA.md#rule-115 *//* coverity[misra_c_2012_rule_11_5_violation] */pxStack = pvPortMallocStack( ( ( ( size_t ) uxStackDepth ) * sizeof( StackType_t ) ) );if( pxStack != NULL ){/* Allocate space for the TCB. *//* MISRA Ref 11.5.1 [Malloc memory assignment] *//* More details at: https://github.com/FreeRTOS/FreeRTOS-Kernel/blob/main/MISRA.md#rule-115 *//* coverity[misra_c_2012_rule_11_5_violation] */pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );if( pxNewTCB != NULL ){( void ) memset( ( void * ) pxNewTCB, 0x00, sizeof( TCB_t ) );/* Store the stack location in the TCB. */pxNewTCB->pxStack = pxStack;}else{/* The stack cannot be used as the TCB was not created. Free* it again. */vPortFreeStack( pxStack );}}else{pxNewTCB = NULL;}}#endif /* portSTACK_GROWTH */if( pxNewTCB != NULL ){prvInitialiseNewTask( pxTaskCode, pcName, uxStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );}return pxNewTCB;}
这个函数我们先简单考虑一个问题:
栈增长方向
portSTACK_GROWTH 控制堆栈向上还是向下增长,小于 0 向下增长。以下是增长模型:
“向下增长” = 地址数值变小 = 从高地址向低地址移动。先 alloc 堆栈后 alloc TCB 即可使堆栈位于低地址,TCB 位于高地址。这个后面讨论。
低地址 ┌─────────────┐ ← pxStack (栈内存)│ ││ 栈空间 │ ▲ 栈向这个方向增长│ │ │└─────────────┘┌─────────────┐ ← pxNewTCB (TCB内存)│ TCB结构 │
高地址 └─────────────┘
“向上增长” = 地址数值变大 = 从低地址向高地址移动
具体例子
假设内存地址范围是 0x1000 - 0x2000:
栈向下增长 (portSTACK_GROWTH = -1) - 当前配置
0x2000 ← 栈开始位置(栈基址)│▼ 栈增长方向(地址减小)
0x1900 ← 当前栈顶
0x1800
0x1700...
0x1000
栈向上增长 (portSTACK_GROWTH > 0)
0x2000...
0x1700
0x1800
0x1900 ← 当前栈顶 ▲ 栈增长方向(地址增大)│
0x1000 ← 栈开始位置(栈基址)
prvInitialiseNewTask 真正初始化任务结构体的函数
初始化堆栈内容并设置 TCB 成员,调用 pxPortInitialiseStack 为堆栈栈底填入预设好的堆栈内容之后返回。
static void prvInitialiseNewTask( TaskFunction_t pxTaskCode,const char * const pcName,const configSTACK_DEPTH_TYPE uxStackDepth,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 ( tskSET_NEW_STACKS_TO_KNOWN_VALUE == 1 ){/* 填充的是0xa5 */( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) uxStackDepth * sizeof( StackType_t ) );}#endif /* tskSET_NEW_STACKS_TO_KNOWN_VALUE */// 栈向下增长的处理#if ( portSTACK_GROWTH < 0 ){// 计算栈顶地址,栈顶 = 栈基址 + (栈深度-1)pxTopOfStack = &( pxNewTCB->pxStack[ uxStackDepth - ( configSTACK_DEPTH_TYPE ) 1 ] );pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );/* Check the alignment of the calculated top of stack is correct. */configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0U ) );}/* 设置 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 - 1U ] = '\0';}/* 设置任务优先级 */configASSERT( uxPriority < configMAX_PRIORITIES );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;}#endif /* configUSE_MUTEXES */// 初始化任务成员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 );/* Event lists are always in priority order. */listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );/* Initialize the TCB stack to look as if the task was already running,* but had been interrupted by the scheduler. The return address is set* to the start of the task function. Once the stack has been initialised* the top of stack variable is updated. */#if ( portUSING_MPU_WRAPPERS == 1 ){// 不成立宏,不考虑}#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 ){// 不成立宏,不考虑}#else /* portHAS_STACK_OVERFLOW_CHECKING */{pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );}#endif /* portHAS_STACK_OVERFLOW_CHECKING */}#endif /* portUSING_MPU_WRAPPERS *//* Initialize task state and task attributes. */#if ( configNUMBER_OF_CORES > 1 ){// 不成立宏,不考虑}#endif /* #if ( configNUMBER_OF_CORES > 1 ) */if( pxCreatedTask != NULL ){// 设置用户传入的指针参数,指向 TCB 句柄*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;}
}
这里函数非常简单,设置了 TCB 的堆栈填充了内容,设置了 TCB 的成员,初始化一系列必要内容。最后调用函数 pxPortInitialiseStack 设置了堆栈的内容之后,设置用户句柄参数就好了。
pxPortInitialiseStack 堆栈初始化函数
函数按照约定设置堆栈从栈底到增长方向的内容之后返回
/** StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters );** 按照标准 RISC-V ABI:* - pxTopOfStack 通过 a0 寄存器传入(栈顶指针)* - pxCode 通过 a1 寄存器传入(任务函数指针)* - pvParameters 通过 a2 寄存器传入(任务参数指针)* - 新的栈顶指针通过 a0 寄存器返回** FreeRTOS 任务的 RISC-V 上下文按以下栈帧结构保存:* (全局指针和线程指针假定为常量,因此不保存)** 栈结构(从高地址到低地址):* xCriticalNesting - 临界区嵌套计数* x31 - 通用寄存器 x31* x30 - 通用寄存器 x30* x29 - 通用寄存器 x29* x28 - 通用寄存器 x28* x27 - 通用寄存器 x27* x26 - 通用寄存器 x26* x25 - 通用寄存器 x25* x24 - 通用寄存器 x24* x23 - 通用寄存器 x23* x22 - 通用寄存器 x22* x21 - 通用寄存器 x21* x20 - 通用寄存器 x20* x19 - 通用寄存器 x19* x18 - 通用寄存器 x18* x17 - 通用寄存器 x17* x16 - 通用寄存器 x16* x15 - 通用寄存器 x15* x14 - 通用寄存器 x14* x13 - 通用寄存器 x13* x12 - 通用寄存器 x12* x11 - 通用寄存器 x11* pvParameters - 任务参数(存储在 x10/a0 位置)* x9 - 通用寄存器 x9* x8 - 通用寄存器 x8* x7 - 通用寄存器 x7* x6 - 通用寄存器 x6* x5 - 通用寄存器 x5* portTASK_RETURN_ADDRESS - 任务返回地址(存储在 x1/ra 位置)* [FPU registers] - 浮点寄存器(如果启用/可用)* [VPU registers] - 向量寄存器(如果启用/可用)* [chip specific registers] - 芯片特定寄存器* mstatus - 机器状态寄存器* pxCode - 任务函数入口地址*/pxPortInitialiseStack:/* === 第一部分:为临界区嵌套计数预留空间 === */addi a0, a0, -portWORD_SIZE /* 栈指针下移一个字长,为临界区嵌套计数预留空间 */store_x x0, 0(a0) /* 将临界区嵌套计数初始化为0(每个任务开始时都是0) *//* === 第二部分:为通用寄存器 x10-x31 预留空间 === */
#ifdef __riscv_32eaddi a0, a0, -(6 * portWORD_SIZE) /* RISC-V 32E 变体:为寄存器 x10-x15 预留空间(6个寄存器) */
#elseaddi a0, a0, -(22 * portWORD_SIZE) /* RISC-V 标准变体:为寄存器 x10-x31 预留空间(22个寄存器) */
#endifstore_x a2, 0(a0) /* 将任务参数 pvParameters(在a2中)存储到栈上x10/a0的位置,任务启动时将作为第一个参数 *//* === 第三部分:为寄存器 x5-x9 和返回地址预留空间 === */addi a0, a0, -(6 * portWORD_SIZE) /* 栈指针下移6个字长,为寄存器 x5-x9 + 任务返回地址(x1/ra)预留空间 */load_x t0, xTaskReturnAddress /* 从 xTaskReturnAddress 全局变量加载任务结束时的返回地址到临时寄存器 t0 */store_x t0, 0(a0) /* 将任务返回地址存储到栈上 x1/ra 的位置,任务结束时会跳转到此地址 *//* === 第四部分:处理芯片特定的附加寄存器 === */addi t0, x0, portasmADDITIONAL_CONTEXT_SIZE /* 将芯片特定附加寄存器的数量加载到 t0(用作循环计数器) */
chip_specific_stack_frame: /* 循环标签:为芯片特定寄存器创建栈帧空间 */beq t0, x0, 1f /* 如果计数器为0,跳转到标签1(没有更多芯片特定寄存器需要保存) */addi a0, a0, -portWORD_SIZE /* 栈指针下移一个字长,为芯片特定寄存器预留空间 */store_x x0, 0(a0) /* 将芯片特定寄存器初始化为0值 */addi t0, t0, -1 /* 递减剩余芯片特定寄存器计数器 */j chip_specific_stack_frame /* 跳转回循环开始,继续处理下一个芯片特定寄存器 */
1: /* 循环结束标签:所有芯片特定寄存器处理完毕 *//* === 第五部分:配置机器状态寄存器 mstatus === */csrr t0, mstatus /* 读取当前的机器状态寄存器 mstatus 值到 t0 */andi t0, t0, ~0x8 /* 清除 MIE 位(位3),确保在 ISR 中恢复栈时中断被禁用。当调度器启动后创建任务时需要此操作,否则中断本来就是禁用的 */addi t1, x0, 0x188 /* 生成值 0x188,准备设置 MPIE=1(位7)和 MPP=机器模式(位11-12)*/slli t1, t1, 4 /* 将 0x188 左移4位得到 0x1880,对应 mstatus 寄存器中的正确位位置 */or t0, t0, t1 /* 在 mstatus 值中设置 MPIE 和 MPP 位,配置任务执行时的中断和特权模式状态 *//* === 第六部分:浮点单元 FPU 状态配置(条件编译) === */
#if( configENABLE_FPU == 1 )/* 在 mstatus 值中将 FPU 标记为 clean 状态 */li t1, ~MSTATUS_FS_MASK /* 加载 FS(浮点状态)字段掩码的反值到 t1 */and t0, t0, t1 /* 清除 mstatus 中的 FS 字段位 */li t1, MSTATUS_FS_CLEAN /* 加载 FS_CLEAN 状态值到 t1 */or t0, t0, t1 /* 设置 FS 字段为 CLEAN 状态,表示 FPU 寄存器是干净的(无需保存/恢复) */
#endif/* === 第七部分:向量处理单元 VPU 状态配置(条件编译) === */
#if( configENABLE_VPU == 1 )/* 在 mstatus 值中将 VPU 标记为 clean 状态 */li t1, ~MSTATUS_VS_MASK /* 加载 VS(向量状态)字段掩码的反值到 t1 */and t0, t0, t1 /* 清除 mstatus 中的 VS 字段位 */li t1, MSTATUS_VS_CLEAN /* 加载 VS_CLEAN 状态值到 t1 */or t0, t0, t1 /* 设置 VS 字段为 CLEAN 状态,表示 VPU 寄存器是干净的(无需保存/恢复) */
#endif/* === 第八部分:存储配置好的 mstatus 寄存器 === */addi a0, a0, -portWORD_SIZE /* 栈指针下移一个字长,为 mstatus 寄存器预留空间 */store_x t0, 0(a0) /* 将配置好的 mstatus 值存储到栈上,任务切换时会恢复此状态 *//* === 第九部分:存储任务函数入口地址 === */addi a0, a0, -portWORD_SIZE /* 栈指针下移一个字长,为任务函数地址预留空间 */store_x a1, 0(a0) /* 将任务函数地址 pxCode(在a1中)存储到栈上,作为 mret 指令的跳转目标 *//* === 函数返回 === */ret /* 返回新的栈顶指针(在 a0 中),此时栈已完全初始化好,可用于任务上下文切换 */
prvAddNewTaskToReadyList 将新任务加入系统就绪列表
更新全局变量 pxCurrentTCB 的值并把任务加入优先级对应的队列,方便后续调度任务指行。pxCurrentTCB 指向优先级最高的任务。
static void prvAddNewTaskToReadyList( TCB_t * pxNewTCB )
{/* 确保在更新任务列表时中断不会访问任务列表,禁用中断 */taskENTER_CRITICAL();{/* 增加当前任务数量计数器 */uxCurrentNumberOfTasks = ( UBaseType_t ) ( uxCurrentNumberOfTasks + 1U );/* 检查当前是否有正在运行的任务 */if( pxCurrentTCB == NULL ){/* 没有其他任务,或者所有其他任务都处于挂起状态 - * 将这个新任务设为当前任务 */pxCurrentTCB = pxNewTCB;/* 检查这是否是系统中的第一个任务 */if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ){/* 这是第一个被创建的任务,所以需要进行初步的* 初始化工作。如果此调用失败,我们无法恢复,* 但会报告失败 */prvInitialiseTaskLists();}}else{/* 如果调度器还没有开始运行,在到目前为止创建的任务中,* 如果这个新任务是最高优先级的,就将其设为当前任务 */if( xSchedulerRunning == pdFALSE ){/* 比较优先级,数值越大优先级越高 */if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority ){/* 新任务优先级更高或相等,更新当前任务指针 */pxCurrentTCB = pxNewTCB;}}}/* 增加任务编号计数器,用于给每个任务分配唯一编号 */uxTaskNumber++;#if ( configUSE_TRACE_FACILITY == 1 ){/* 如果启用了跟踪功能,为TCB添加一个计数器,仅用于跟踪 */pxNewTCB->uxTCBNumber = uxTaskNumber;}#endif /* configUSE_TRACE_FACILITY *//* 将新任务添加到相应优先级的就绪列表中 */prvAddTaskToReadyList( pxNewTCB );/* 执行特定于移植层的TCB设置 */portSETUP_TCB( pxNewTCB );}/* 退出临界区 */taskEXIT_CRITICAL();/* 检查调度器是否正在运行 */if( xSchedulerRunning != pdFALSE ){/* 如果创建的任务优先级高于当前任务,* 那么它应该立即运行(抢占式调度) */taskYIELD_ANY_CORE_IF_USING_PREEMPTION( pxNewTCB );}
}
函数作用总结及调用链关系
1. main_blinky
- 主函数,作为程序入口,负责初始化系统(如打印信息、创建队列)。
- 核心动作:调用
xTaskCreate
创建两个不同优先级的任务,最后启动任务调度器(vTaskStartScheduler
)。
2. xTaskCreate
- 任务创建的对外接口函数,负责协调任务创建的全过程。
- 核心动作:
- 调用
prvCreateTask
分配任务栈(StackType_t
)和任务控制块(TCB_t
)并初始化。 - 若
prvCreateTask
成功,调用prvAddNewTaskToReadyList
将新任务加入就绪列表。 - 返回创建结果(成功/内存分配失败)。
- 调用
3. prvCreateTask
- 负责为任务分配内存(栈和TCB),并触发任务初始化。
- 核心动作:
- 根据栈增长方向(此处为向下增长,
portSTACK_GROWTH < 0
),先分配栈空间,再分配TCB空间(避免栈增长覆盖TCB)。 - 若内存分配成功,调用
prvInitialiseNewTask
初始化任务(TCB和栈)。 - 返回初始化后的
TCB_t
指针(或NULL
,表示失败)。
- 根据栈增长方向(此处为向下增长,
4. prvInitialiseNewTask
- 真正初始化任务控制块(
TCB_t
)的核心函数。 - 核心动作:
- 设置TCB成员(如任务名
pcTaskName
、优先级uxPriority
、栈基址pxStack
)。 - 初始化任务状态列表项(
xStateListItem
)和事件列表项(xEventListItem
)。 - 调用
pxPortInitialiseStack
初始化栈内容,获取栈顶指针并存入TCB(pxTopOfStack
)。 - 设置任务句柄(
pxCreatedTask
),指向当前TCB。
- 设置TCB成员(如任务名
5. pxPortInitialiseStack
- 负责按照RISC-V架构的ABI规范初始化任务栈内容,模拟任务“被中断后保存的上下文”,确保任务首次调度时能正确启动。
- 核心动作:
- 预留临界区嵌套计数空间,初始化通用寄存器(
x10-x31
等)的栈帧。 - 设置任务返回地址(
portTASK_RETURN_ADDRESS
)、机器状态寄存器(mstatus
,配置特权模式和中断状态)。 - 存储任务函数入口地址(
pxTaskCode
),作为任务首次执行的起始点。 - 返回初始化后的栈顶指针,供TCB的
pxTopOfStack
使用。
- 预留临界区嵌套计数空间,初始化通用寄存器(
6. prvAddNewTaskToReadyList
- 由
xTaskCreate
在任务创建成功后调用,负责将新任务加入系统就绪列表。 - 核心动作:根据任务优先级,将任务的
xStateListItem
插入对应优先级的就绪列表,使任务成为可被调度的状态。
调用链箭头图
main_blinky ↓(创建任务)
xTaskCreate ├─→ prvCreateTask (分配栈和TCB,触发初始化)│ ↓│ prvInitialiseNewTask (初始化TCB成员)│ ↓│ pxPortInitialiseStack (初始化栈内容)│└─→ prvAddNewTaskToReadyList (将任务加入就绪列表)
附
进入临界区时,会禁用中断并做计数。具体 RISCV 实现如下所示:
// 禁用中断宏,数值8对应二进制1000,即第3 bit 位
// 使用 RISC-V 汇编指令 csrc (Clear bits in CSR) 清除 mstatus 寄存器的第3位 (MIE位)
// MIE (Machine Interrupt Enable) 位控制机器模式下的中断使能
// 当 MIE=0 时,禁用所有机器模式中断
#define portDISABLE_INTERRUPTS() __asm volatile ( "csrc mstatus, 8" )
#define portENABLE_INTERRUPTS() __asm volatile ( "csrs mstatus, 8" )extern size_t xCriticalNesting;
#define portENTER_CRITICAL() \{ \portDISABLE_INTERRUPTS(); \xCriticalNesting++; \}#define portEXIT_CRITICAL() \{ \xCriticalNesting--; \if( xCriticalNesting == 0 ) \{ \portENABLE_INTERRUPTS(); \} \}
总结
完结撒花!!!