当前位置: 首页 > news >正文

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。
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(); \}                            \}

总结

完结撒花!!!

http://www.dtcms.com/a/311173.html

相关文章:

  • 【Pytorch✨】LSTM 入门
  • 用 Qt 打造优雅的密码输入框:添加右侧眼睛图标切换显示
  • 云环境K8s集群WebSocket连接失败解决方案
  • 深入解析 <component :is> 在 Vue3 组合式中的使用与局限
  • 关于Web前端安全防御之点击劫持的原理及防御措施
  • Docker容器中文PDF生成解决方案
  • JavaScript特殊集合WeakMap 的使用及场景介绍
  • C++ lambda表达式与线程库
  • String boot 接入 azure云TTS
  • 20250802安装CP2102N的驱动程序(适配飞凌的OK3576-C)
  • 如何在Ubuntu上部署excalidraw
  • Seal Report:一款免费开源的报表工具
  • 使用 BERT 的 NSP 实现语义感知切片 —— 提升 RAG 系统的检索质量
  • 计算机网络:什么是任播
  • 【计算机网络】Socket网络编程
  • 从零开始构建AI Agent评估体系:12种LangSmith评估方法详解
  • QUdpSocket 详解:从协议基础、通信模式、数据传输特点、应用场景、调用方式到实战应用全面解析
  • Linux网络编程【基于UDP网络通信的字典翻译服务】
  • M|银翼杀手
  • Web 开发 10
  • K8s+Nginx-ingress+Websocket基础知识理解
  • 系统思考:超越线性分析
  • python创建一个excel文件
  • MyBatis 批量操作 XML 实现方式
  • 【BTC】挖矿难度调整
  • Vue 详情模块 3
  • Matplotlib - Python图表可视化利器
  • Vue3核心语法进阶(computed与监听)
  • 除数博弈(动态规划)
  • cs336之注意pytorch的tensor在哪里?(assert的使用)