FreeRTOS 6:任务创建函数xTaskCreate分析

动态创建任务xTaskCreate

xTaskCreate

1、申请堆栈内存(返回首地址)

2、申请任务控制块内存(返回首地址)

3、把前面申请的堆栈地址,赋值给控制块的堆栈成员

4、调用prvlnitialiseNewTask初始化任务控制块中的成员

5、调用prvAddNewTaskToReadyList添加新创建任务到就绪列表中

/*** 创建一个任务并将其添加到就绪列表中。* * @param pxTaskCode 任务的入口函数。* @param pcName 任务的名称,主要用于调试。* @param usStackDepth 任务堆栈的大小,单位是堆栈项的数量。* @param pvParameters 传递给任务入口函数的参数。* @param uxPriority 任务的优先级,优先级越高,任务的优先级越高。* @param pxCreatedTask 用于存储新创建任务的句柄的变量的地址,如果不需要句柄,可以设置为NULL。* * @return 返回任务创建的结果,pdPASS表示成功,errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY表示由于缺乏内存而失败。* * 此函数根据指定的参数创建一个新的任务,并将其添加到就绪列表中。如果内存不足,将返回错误代码。* 它首先根据堆栈增长的方向分配TCB(任务控制块)和堆栈空间,然后初始化新任务并将其添加到就绪列表中。*/
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */const configSTACK_DEPTH_TYPE usStackDepth,void * const pvParameters,UBaseType_t uxPriority,TaskHandle_t * const pxCreatedTask )
{TCB_t * pxNewTCB;BaseType_t xReturn;/* 如果堆栈向下增长,则先分配堆栈,然后分配TCB,以防止堆栈增长到TCB中。* 同样,如果堆栈向上增长,则先分配TCB,然后分配堆栈。 */#if ( portSTACK_GROWTH > 0 ){/* 分配TCB的空间。内存的来源取决于port malloc函数的实现以及是否使用静态分配。 */pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );if( pxNewTCB != NULL ){/* 为新创建的任务分配堆栈空间。* 堆栈的基地址存储在TCB中,以便在需要时删除任务。 */pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */if( pxNewTCB->pxStack == NULL ){/* 无法分配堆栈。删除已分配的TCB。 */vPortFree( pxNewTCB );pxNewTCB = NULL;}}}#else /* portSTACK_GROWTH */{StackType_t * pxStack;/* 为新创建的任务分配堆栈空间。 */pxStack = pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation is the stack. */if( pxStack != NULL ){/* 分配TCB的空间。 */pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e9087 !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack, and the first member of TCB_t is always a pointer to the task's stack. */if( pxNewTCB != NULL ){/* 将堆栈位置存储在TCB中。 */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. */{/* 可以静态或动态创建任务,因此记录此任务是动态创建的,以防它稍后被删除。 */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;
}

prvlnitialiseNewTask

xTaskCreate

prvlnitialiseNewTask

1、初始化堆栈为Oxa5(可选)

2、记录栈顶,保存在pxTopOfStack

3、保存任务名字到pxNewTCP>PCTasKname[ x ]中

4、保存任务优先级到p×NewTCB->uxPriority

5、设置状态列表项的所属控制块,设置事件列表项的优先级

6、列表项的擦汗如入是从小到大插入,所以这里将越高优先级的任务的事件列表项值设置越小,这样就可以排前面

7、调用pxPortlnitialiseStack初始化任务堆栈,用于保存当前任务上下文器信息,已备后续任务切换使用

8.将任务句柄等于控制块

static void prvInitialiseNewTask( TaskFunction_t pxTaskCode,const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */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 )/* 是否需要以特权模式创建任务? */BaseType_t xRunPrivileged;if( ( uxPriority & portPRIVILEGE_BIT ) != 0U ){xRunPrivileged = pdTRUE;}else{xRunPrivileged = pdFALSE;}/* 清除优先级中的特权位 */uxPriority &= ~portPRIVILEGE_BIT;#endif /* portUSING_MPU_WRAPPERS == 1 *//* 如果不需要依赖 memset(),则避免使用 */#if ( tskSET_NEW_STACKS_TO_KNOWN_VALUE == 1 ){/* 使用已知值填充堆栈,以便于调试 */( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );}#endif /* tskSET_NEW_STACKS_TO_KNOWN_VALUE *//* 计算栈顶地址。这取决于栈是从高内存向低内存增长(如80x86架构)还是相反。* portSTACK_GROWTH 用于根据端口需求使结果为正或负。 */#if ( portSTACK_GROWTH < 0 ){/* 计算从高到低增长的栈的栈顶地址。 */pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );/* 将栈顶地址对齐到所需的字节边界。 */pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); /*lint !e923 !e9033 !e9078 MISRA 异常。避免指针和整数之间的转换在实际中不可行。使用 portPOINTER_SIZE_TYPE 类型来处理大小差异。通过 assert() 检查。 *//* 检查计算出的栈顶地址对齐是否正确。 */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;/* 检查栈缓冲区的对齐是否正确。 */configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxNewTCB->pxStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );/* 如果进行栈检查,则需要栈空间的另一端。 */pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );}#endif /* portSTACK_GROWTH *//* 将任务名称存储在TCB中。 */if( pcName != NULL ){for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ ){pxNewTCB->pcTaskName[ x ] = pcName[ x ];/* 如果字符串长度小于 configMAX_TASK_NAME_LEN,确保不会复制超过实际字符串长度的部分,* 以防字符串后面的内存不可访问(极不可能发生)。 */if( pcName[ x ] == ( char ) 0x00 ){break;}else{mtCOVERAGE_TEST_MARKER();}}/* 确保在字符串长度大于或等于 configMAX_TASK_NAME_LEN 时,字符串以空字符终止。 */pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';}else{/* 如果任务没有被赋予名称,确保读取时有一个空终止符。 */pxNewTCB->pcTaskName[ 0 ] = 0x00;}/* 用作数组索引,因此必须确保它不会太大。首先移除特权位(如果存在的话)。 */if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES ){/* 如果优先级超出最大值,将其设置为最大优先级减一。 */uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;}else{mtCOVERAGE_TEST_MARKER();}/* 将优先级存储在新创建的TCB中。 */pxNewTCB->uxPriority = uxPriority;#if ( configUSE_MUTEXES == 1 ){/* 如果配置支持互斥锁,初始化基优先级和持有的互斥锁数量。 */pxNewTCB->uxBasePriority = uxPriority;pxNewTCB->uxMutexesHeld = 0;}#endif /* configUSE_MUTEXES *//* 初始化新创建的TCB中的状态列表项。 */vListInitialiseItem( &( pxNewTCB->xStateListItem ) );/* 初始化新创建的TCB中的事件列表项。 */vListInitialiseItem( &( pxNewTCB->xEventListItem ) );/* 将 pxNewTCB 设置为状态列表项的拥有者。这样可以从列表中的任意项返回到包含它的 TCB。 */listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );/* 事件列表总是按优先级顺序排列。 */listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority ); /*lint !e961 MISRA 异常,因为对于某些端口,这些类型转换是多余的。*//* 将 pxNewTCB 设置为事件列表项的拥有者。 */listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );#if ( portCRITICAL_NESTING_IN_TCB == 1 ){/* 如果配置了在 TCB 中管理临界区嵌套计数,则初始化为 0。 */pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U;}#endif /* portCRITICAL_NESTING_IN_TCB */#if ( configUSE_APPLICATION_TASK_TAG == 1 ){/* 如果配置了应用任务标签,则初始化为 NULL。 */pxNewTCB->pxTaskTag = NULL;}#endif /* configUSE_APPLICATION_TASK_TAG */#if ( configGENERATE_RUN_TIME_STATS == 1 ){/* 如果配置了生成运行时统计信息,则初始化运行时间计数器为 0。 */pxNewTCB->ulRunTimeCounter = 0UL;}#endif /* configGENERATE_RUN_TIME_STATS */#if ( portUSING_MPU_WRAPPERS == 1 ){/* 如果使用 MPU 包装器,则存储任务的 MPU 设置。 */vPortStoreTaskMPUSettings( &( pxNewTCB->xMPUSettings ), xRegions, pxNewTCB->pxStack, ulStackDepth );}#else{/* 避免编译器关于未引用参数的警告。 */( void ) xRegions;}#endif#if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 ){/* 如果配置了线程本地存储指针,则初始化为 0。 */memset( ( void * ) &( pxNewTCB->pvThreadLocalStoragePointers[ 0 ] ), 0x00, sizeof( pxNewTCB->pvThreadLocalStoragePointers ) );}#endif#if ( configUSE_TASK_NOTIFICATIONS == 1 ){/* 如果配置了任务通知,则初始化通知值和状态为 0。 */memset( ( void * ) &( pxNewTCB->ulNotifiedValue[ 0 ] ), 0x00, sizeof( pxNewTCB->ulNotifiedValue ) );memset( ( void * ) &( pxNewTCB->ucNotifyState[ 0 ] ), 0x00, sizeof( pxNewTCB->ucNotifyState ) );}#endif#if ( configUSE_NEWLIB_REENTRANT == 1 ){/* 初始化此任务的 Newlib 重入结构。* 有关更多信息,请参阅第三方链接 http://www.nadler.com/embedded/newlibAndFreeRTOS.html。 */_REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );}#endif#if ( INCLUDE_xTaskAbortDelay == 1 ){/* 如果配置了任务延迟中止功能,则初始化延迟中止标志为 false。 */pxNewTCB->ucDelayAborted = pdFALSE;}#endif/* 初始化 TCB 栈,使其看起来像是任务已经在运行,但被调度器中断了一样。* 返回地址设置为任务函数的起始地址。一旦栈初始化完成,更新栈顶变量。 */#if ( portUSING_MPU_WRAPPERS == 1 ){/* 如果端口具有检测栈溢出的能力,则将栈结束地址传递给栈初始化函数。 */#if ( portHAS_STACK_OVERFLOW_CHECKING == 1 ){#if ( portSTACK_GROWTH < 0 ){/* 栈从高地址向低地址增长。 */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 ( 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 ){/* 以匿名方式传递任务句柄。任务句柄可以用于更改创建的任务的优先级、删除任务等。 */*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;}else{/* 覆盖测试标记,用于确保所有代码路径都被测试到。 */mtCOVERAGE_TEST_MARKER();}
}
pxPortlnitialiseStack

xTaskCreate

prvlnitialiseNewTask

pxPortlnitialiseS

        函数 pxPortInitialiseStack()用于初始化任务栈,就是往任务的栈中写入一些重要的信息,这些信息会在任务切换的时候被弹出到 CPU 寄存器中,以恢复任务的上下文信息,这些信息就包括 xPSR 寄存器的初始值、 任务的函数地址(PC 寄存器)、任务错误退出函数地址(LR 寄存器)、任务函数的传入参数(R0 寄存器)以及为 R1~R12 寄存器预留空间,若使用了浮点单元,那么还会有 EXC_RETURN 的值。同时该函数会返回更新后的栈顶指针。
        针 对 ARM Cortex-M3 和 针 对 ARM Cortex-M4 和 ARM Cortex-M7 内 核 的 函 数pxPortInitialiseStack()稍有不同, 原因在于 ARM Cortex-M4 和 ARM Cortex-M7 内核具有浮点单元, 因此在任务栈中还需保存浮点寄存器的值。
        针对 ARM Cortex-M3 内核的函数 pxPortInitialiseStack(), 具体的代码如下所示:

StackType_t * pxPortInitialiseStack(StackType_t * pxTopOfStack, /* 任务栈顶指针 */TaskFunction_t pxCode, /* 任务函数地址 */void * pvParameters) /* 任务函数传入参数 */
{/* 模拟栈的格式将信息保存到任务栈中,用于上下文切换 */pxTopOfStack--;/* xPSR 寄存器初始值为 0x01000000 */*pxTopOfStack = portINITIAL_XPSR;pxTopOfStack--;/* 任务函数的地址(PC 寄存器) */*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;pxTopOfStack--;/* 任务错误退出函数地址(LR 寄存器) */*pxTopOfStack = ( StackType_t ) prvTaskExitError;/* 为 R12、 R3、 R2、 R1 寄存器预留空间 */pxTopOfStack -= 5;/* 任务函数的传入参数(R0 寄存器) */*pxTopOfStack = ( StackType_t ) pvParameters;/* 为 R11、 R10、 R9、 R8、 R7、 R6、 R5、 R4 寄存器预留空间 */pxTopOfStack -= 8;/* 返回更新后的任务栈指针* 后续任务运行时需要用到栈的地方,* 将从这个地址开始保存信息*/return pxTopOfStack;
}

StackType_t * pxPortInitialiseStack( StackType_t * pxTopOfStack,TaskFunction_t pxCode,void * pvParameters )
{/* 模拟上下文切换中断创建的堆栈帧。 */pxTopOfStack--;                                                      /* 增加偏移量以适应 MCU 在中断进入/退出时对堆栈的使用方式。 */*pxTopOfStack = portINITIAL_XPSR;                                    /* xPSR 寄存器的初始值 */pxTopOfStack--;*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; /* PC 寄存器的初始值,即任务函数的入口地址 */pxTopOfStack--;*pxTopOfStack = ( StackType_t ) prvTaskExitError;                    /* LR 寄存器的初始值,用于错误处理 */pxTopOfStack -= 5;                                                   /* 为 R12, R3, R2 和 R1 预留空间 */*pxTopOfStack = ( StackType_t ) pvParameters;                        /* R0 寄存器的初始值,即任务参数 */pxTopOfStack -= 8;                                                   /* 为 R11, R10, R9, R8, R7, R6, R5 和 R4 预留空间 */return pxTopOfStack;                                                 /* 返回调整后的堆栈顶部指针 */
}

针对 ARM Cortex-M4 和 ARM Cortex-M7 内核的函数 pxPortInitialiseStack(),具体的代码如下所示:

StackType_t * pxPortInitialiseStack(StackType_t * pxTopOfStack, /* 任务栈顶指针 */TaskFunction_t pxCode, /* 任务函数地址 */void * pvParameters) /* 任务函数传入参数 */
{/* 模拟栈的格式将信息保存到任务栈中,用于上下文切换 */pxTopOfStack--;/* xPSR 寄存器初始值为 0x01000000 */*pxTopOfStack = portINITIAL_XPSR;pxTopOfStack--;/* 任务函数的地址(PC 寄存器) */*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;pxTopOfStack--;/* 任务错误退出函数地址(LR 寄存器) */*pxTopOfStack = ( StackType_t ) prvTaskExitError;/* 为 R12、 R3、 R2、 R1 寄存器预留空间 */pxTopOfStack -= 5;/* 任务函数的传入参数(R0 寄存器) */*pxTopOfStack = ( StackType_t ) pvParameters;pxTopOfStack--;/* EXC_RETURN* 初始化为 0xFFFFFFFD,* 即表示不使用浮点单元,且中断返回后进入线程模式,使用 PSP*/*pxTopOfStack = portINITIAL_EXC_RETURN;/* 为 R11、 R10、 R9、 R8、 R7、 R6、 R5、 R4 寄存器预留空间 */pxTopOfStack -= 8;/* 返回更新后的任务栈指针* 后续任务运行时需要用到栈的地方,* 将从这个地址开始保存信息*/return pxTopOfStack;
}

函数 pxPortInitialiseStack()初始化后的任务栈如下图所示:

prvAddNewTaskToReadyList

xTaskCreate

prvAddNewTaskToReadyList

1、记录任务数量uxCurrentNumberOfTasks++

2、判断新创建的任务是否为第一个任务

如果创建的是第一个任务,初始化任务列表prvInitialiseTaskLists;如果创建的不是第一个任务,并且调度器还未开始启动,比较新任务与正在执行的任务优先级大小,新任务优先级大的话,将当前控制块重新指向新的控制块

3、将新的任务控制块添加到就绪列表中,使用函数prvAddTaskToReadyList

4、如果调度器已经开始运行,并且新任务的优先级更大的话,进行一次任务切换

将uxTopReadyPrioritytä应bit置一,表示相应优先级有就绪任务,比如任务优先级为5,就该变量的位5置一,方便后续任务切换判断,对应的就绪列表是否有任务存在将新创建的任务插入对应的就绪列表末尾

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 ){/* 仅用于跟踪,向 TCB 添加一个计数器。 */pxNewTCB->uxTCBNumber = uxTaskNumber;}#endif /* configUSE_TRACE_FACILITY *//* 记录任务创建。 */traceTASK_CREATE( pxNewTCB );/* 将新任务添加到就绪列表。 */prvAddTaskToReadyList( pxNewTCB );/* 设置 TCB。 */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();}
}
prvAddTaskToReadyList( pxTCB )

/** Place the task represented by pxTCB into the appropriate ready list for* the task.  It is inserted at the end of the list.*/
#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 )

/*** 将新列表项插入到列表末尾。* * 此函数用于将新项添加到指定列表的末尾。它不会对列表进行排序,而是将新添加的项设置为最后一个被检索的项。* 该函数适用于特定的使用场景和列表管理需求。* * @param pxList 指向要添加新项的列表的指针。* @param pxNewListItem 指向要添加的新项的指针。*/
void vListInsertEnd( List_t * const pxList,ListItem_t * const pxNewListItem )
{// 获取列表的索引项,用于遍历和操作列表。ListItem_t * const pxIndex = pxList->pxIndex;// 仅在定义了 configASSERT() 时有效,这些测试可以捕获内存中被覆盖的列表数据结构问题。// 它们不会捕获因 FreeRTOS 配置或使用不当导致的数据错误。listTEST_LIST_INTEGRITY( pxList );listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );// 将新列表项插入到 pxList 中,但不对其进行排序,// 而是将新列表项设置为通过调用 listGET_OWNER_OF_NEXT_ENTRY() 最后移除的项。pxNewListItem->pxNext = pxIndex;pxNewListItem->pxPrevious = pxIndex->pxPrevious;// 仅在决策覆盖率测试中使用。mtCOVERAGE_TEST_DELAY();// 更新列表项之间的链接,以正确插入新项。pxIndex->pxPrevious->pxNext = pxNewListItem;pxIndex->pxPrevious = pxNewListItem;// 记录该项所属的列表。pxNewListItem->pxContainer = pxList;// 增加列表中的项数。( pxList->uxNumberOfItems )++;
}

此函数就是将待插入的列表项插入到列表 pxIndex 指向列表项的前面,要注意的时, pxIndex 不一定指向 xListEnd,而是有可能指向列表中任意一个列表项。函数 vListInsertEnd()插入列表项后的列表结构示意图,如下图所示:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/58006.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux系统安全:开源的反病毒工具ClamAV的安装配置使用和维护介绍

目录 一、概述 1、开发者 2、功能 3、特性 二、安装ClamAV 1、更新系统包列表 2、安装ClamAV 三、更新病毒库 四、配置ClamAV 1、编辑ClamAV配置文件 2、主要配置选项 &#xff08;1&#xff09;/etc/clamd.conf &#xff08;2&#xff09; /etc/freshclam.conf …

windows下安装python库wordCloud报错

换电脑安装wordcloud半天安装失败&#xff0c;记录一下遇到的坑&#xff0c;也给大家节省点时间。 方法1&#xff1a; 错误呢就是下面这个&#xff0c;说没c编译器&#xff0c;要不就去他给的地址上安装一下&#xff0c;我安装了一下好像没什么选&#xff0c;也没太敢勾选吗&a…

2025 - AI人工智能药物设计 - 中药网络药理学和毒理学的研究

中药网络药理学和毒理学的研究 TCMSP&#xff1a;https://old.tcmsp-e.com/tcmsp.php 然后去pubchem选择&#xff1a;输入Molecule Name 然后进行匹配&#xff1a;得到了smiles 再次通过smiles&#xff1a;COC1C(CC(C2C1OC(CC2O)C3CCCCC3)O)O 然后再次输入&#xff1a;http…

C语言基础(五)【控制语句与循环(上)学习篇】

文章目录 前言一、if - else 语句二、switch-case 语句三、while 循环四、for 循环五、do - while 循环六、break 语句七、continue 语句八、go to 语句总结 前言 C语言是结构化的程序设计语言&#xff0c;这里的结构指的是顺序结构、选择结构、循环结构&#xff0c;因此提供了…

中国人寿财险青岛市分公司:科技赋能,车险服务再升级

中国人寿财险青岛市分公司积极响应国家号召&#xff0c;大力推进车险综合改革&#xff0c;以科技赋能车险服务&#xff0c;实现服务再升级。公司利用AI技术&#xff0c;实现车险报案的自动化处理&#xff0c;大幅缩短了理赔周期。同时&#xff0c;公司还通过大数据分析&#xf…

【力扣】[Java版] 刷题笔记-101. 对称二叉树

题目&#xff1a;101. 对称二叉树 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 解题思路 可以理解为遍历对比&#xff0c;最简单的方法就是递归。 解题过程 递归&#xff1a;左右子树分开遍历&#xff0c;左子树遵循根、左、右的顺序&#xff0c;右子树循…

重学SpringBoot3-Spring WebFlux之SSE服务器发送事件

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ Spring WebFlux之SSE服务器发送事件 1. 什么是 SSE&#xff1f;2. Spring Boot 3 响应式编程与 SSE为什么选择响应式编程实现 SSE&#xff1f; 3. 实现 SSE 的基本步骤3.…

(三)第一个Qt程序“Qt版本的HelloWorld”

一、随记 我们在学习编程语言的时候&#xff0c;各种讲解编程语言的书籍中通常都会以一个非常经典的“HelloWorld”程序展开详细讲解。程序虽然简短&#xff0c;但是“麻雀虽小&#xff0c;五脏俱全”&#xff0c;但是却非常适合用来熟悉程序结构、规范&#xff0c;快速形成对编…

技术杂谈与进阶01--------战时操作系统与国产化数据库 |截止到目前修改时间,本文已上全站综合热榜

写文不易 给我点点关注 和点点赞 点点收藏吧 目录 为什么国产化&#xff1f; 操作系统 suse 统信uos 的阉割版 deepin &#xff08;deepin又名深度操作系统&#xff09; 麒麟 的阉割版 -欧拉 debian 的修改残版 ubuntu 国产数据库 开源数据库 部分开源数据库 …

uniapp使用uni-push模拟推送

uniapp使用uni-push模拟推送 第一步先去uniapp开发者中心添加开通uni-push功能 这里的Android 应用签名可以先用测试的官网有,可以先用这个测试 官方测试链接文档地址 在项目中的配置文件勾选 组件中使用 如果要实时可以去做全局ws //消息推送模版uni.createPushMessage(…

人工智能进程;算子加速的具体计算部分;大模型GPT5:参数18万亿;大模型面临问题

目录 人工智能进程 算子加速的简单理解,举例说明 一、简单理解 二、举例说明 一、算子加速的具体计算部分 二、举例说明 三、算子加速是否仅针对GPU 大模型GPT5:参数18万亿 大模型面临问题 算力集群设计框架 人工智能进程

算法-二叉树的最大路径和

为了找到二叉树的最大路径和&#xff0c;我们需要考虑所有可能的路径&#xff0c;包括不经过根节点的路径&#xff0c;所以其实如果你从整体上来一条路径一条路径的遍历&#xff0c;太复杂&#xff0c;我们可以换个思路&#xff0c;从每个节点出发&#xff0c;就把那个节点当成…

C++ | Leetcode C++题解之第504题七进制数

题目&#xff1a; 题解&#xff1a; class Solution { public:string convertToBase7(int num) {if (num 0) {return "0";}bool negative num < 0;num abs(num);string digits;while (num > 0) {digits.push_back(num % 7 0);num / 7;}if (negative) {dig…

Github优质项目推荐(第八期)

文章目录 Github优质项目推荐 - 第八期一、【manim】&#xff0c;66.5k stars - 创建数学动画的 Python 框架二、【siyuan】&#xff0c;19.5k stars - 个人知识管理软件三、 【GetQzonehistory】&#xff0c;1.3k stars - 获取QQ空间发布的历史说说四、【SecLists】&#xff0…

Redis 过期策略 总结

前言 相关系列 《Redis & 目录》&#xff08;持续更新&#xff09;《Redis & 过期策略 & 源码》&#xff08;学习过程/多有漏误/仅作参考/不再更新&#xff09;《Redis & 过期策略 & 总结》&#xff08;学习总结/最新最准/持续更新&#xff09;《Redis &a…

python 制作 发货单 (生成 html, pdf)

起因&#xff0c; 目的: 某个小店&#xff0c;想做个发货单。 过程: 先写一个 html 模板。准备数据&#xff0c; 一般是从数据库读取&#xff0c;也可以是 json 格式&#xff0c;或是 python 字典。总之&#xff0c;是数据内容。使用 jinja2 来渲染模板。最终的结果可以是 h…

【Jenkins】解决在Jenkins Agent节点容器内无法访问物理机的docker和docker compose的问题

解决在Jenkins Agent节点容器内无法访问物理机的docker和docker compose的问题 1. 确定物理机docker和docker compose已经安装2. 编写Jenkins Agent结点docker-compose.yaml配置文件3. 修改docker运行时文件权限4. 启动容器并验证 最近接触到一个发布产物是一个 docker镜像的项…

AAPL: Adding Attributes to Prompt Learning for Vision-Language Models

文章汇总 当前的问题 1.元标记未能捕获分类的关键语义特征 如下图(a)所示&#xff0c; π \pi π在类聚类方面没有显示出很大的差异&#xff0c;这表明元标记 π \pi π未能捕获分类的关键语义特征。我们进行简单的数据增强后&#xff0c;如图(b)所示&#xff0c;效果也是如…

Android使用协程实现自定义Toast弹框

Android使用协程实现自定义Toast弹框 ​ 最近有个消息提示需要显示10s,刚开始使用协程写了一个shoowToast方法&#xff0c;传入消息内容、显示时间和toast显示类型即可&#xff0c;以为能满足需求&#xff0c;结果测试说只有5s&#xff0c;查看日志和源码发现Android系统中Toa…

【AI大模型】深入解析 存储和展示地理数据(.kmz)文件格式:结构、应用与项目实战

文章目录 1. 引言2. 什么是 .kmz 文件&#xff1f;2.1 .kmz 文件的定义与用途2.2 .kmz 与 .kml 的关系2.3 常见的 .kmz 文件使用场景 3. .kmz 文件的内部结构3.1 .kmz 文件的压缩格式3.2 解压缩 .kmz 文件的方法3.3 .kmz 文件的典型内容3.4 .kml 文件的结构与主要元素介绍 4. 深…