FreeRTOS学习9——补充 API函数详解(创建、删除任务函数 和 挂起、恢复任务函数,空闲任务函数)

API函数详解(创建任务函数 和 删除任务函数)

创建函数 xTaskCreate()

作用:创建任务

​ 函数 xTaskCreate()在 task.c 文件中有定义,具体的代码如下所示:

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,const char * const pcName,const configSTACK_DEPTH_TYPE usStackDepth,void * const pvParameters,UBaseType_t uxPriority,TaskHandle_t * const pxCreatedTask)
{TCB_t * pxNewTCB;BaseType_t xReturn;/* 宏 portSTACK_GROWTH 用于定义栈的生长方向* STM32 的栈是向下生长的,* 因此宏 portSTACK_GROWTH 定义为-1*/
#if ( portSTACK_GROWTH > 0 )
{/* 为任务控制块申请内存空间 */pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );/* 任务控制块内存申请成功 */if( pxNewTCB != NULL ){/* 为任务栈空间申请内存空间 */pxNewTCB->pxStack =(StackType_t *)pvPortMallocStack((((size_t)usStackDepth)*sizeof(StackType_t)));/* 任务栈空间内存申请失败 */if( pxNewTCB->pxStack == NULL ){/* 释放申请到的任务控制块内存 */vPortFree( pxNewTCB );pxNewTCB = NULL;}}
}
#else
{StackType_t * pxStack;/* 为任务栈空间申请内存空间 */pxStack = pvPortMallocStack((((size_t)usStackDepth) * sizeof(StackType_t)));/* 任务栈空间内存申请成功 */if( pxStack != NULL ){/* 为任务控制块申请内存空间 */pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );/* 任务控制块内存申请成功 */if( pxNewTCB != NULL ){/* 设置任务控制块中的任务栈指针 */pxNewTCB->pxStack = pxStack;}else{/* 释放申请到的任务栈空间内存 */vPortFreeStack( pxStack );}}else{pxNewTCB = NULL;}
}
#endif/* 任务控制块和任务栈空间的内存均申请成功 */if( pxNewTCB != NULL ){/* 宏 tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE 用于* 指示系统是否同时支持静态和动态方式创建任务* 如果系统同时支持多种任务创建方式,* 则需要标记任务具体是静态方式还是动态方式创建的*/#if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ){/* 标记任务是动态方式创建的 */pxNewTCB->ucStaticallyAllocated =tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;}#endif/* 初始化任务控制块中的成员变量 */prvInitialiseNewTask( pxTaskCode,pcName,( uint32_t ) usStackDepth,pvParameters,uxPriority,pxCreatedTask,pxNewTCB,NULL);/* 将任务添加到就绪态任务列表中* 这个函数会同时比较就绪态任务列表中的任务优先级* 并更新 pxCurrentTCB 为就绪态任务列表中优先级最高的任务*/prvAddNewTaskToReadyList( pxNewTCB );/* 返回 pdPASS,说明任务创建成功 */xReturn = pdPASS;}else{/* 内存申请失败,则返回内存申请失败的错误 */xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;}return xReturn;
}
  1. 函数 xTaskCreate()创建任务,首先为任务的任务控制块以及任务栈空间申请内存,如果任务控制块或任务栈空间的内存申请失败,则释放已经申请到的内存,并返回内存申请失败的错误。

  2. 任务创建所需的内存申请成功后,使用函数 prvInitialiseNewTask()初始化任务控制块中的成员变量,包括任务函数指针、任务名、任务栈大小、任务函数参数、任务优先级等。

  3. 最后调用函数 prvAddNewTaskToReadList()将任务添加到就绪态任务列表中,从这里可以看出,任务被创建后,是立马被添加到就绪态任务列表中的。

函数 prvInitialiseNewTask()

作用:函数 prvInitialiseNewTask()用于创建任务时,初始化任务控制块中的成员变量

​ 函数prvInitialiseNewTask()在 task.c 文件中有定义,具体的代码如下所示

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 ) /* MPU 相关 */
{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/* 此宏用于将新建任务的任务栈设置为已知值(由宏 tskSTACK_FILL_BYTE 定义) */
#if ( tskSET_NEW_STACKS_TO_KNOWN_VALUE == 1 )
{/* 将任务栈写满 tskSTACK_FILL_BYTE */( void ) memset(pxNewTCB->pxStack,( int ) tskSTACK_FILL_BYTE,( size_t ) ulStackDepth * sizeof( StackType_t ) );
}
#endif/* 宏 portSTACK_GROWTH 用于定义栈的生长方向* STM32 的栈是向下生长的,* 因此宏 portSTACK_GROWTH 定义为-1*/
#if ( portSTACK_GROWTH < 0 )
{/* 获取任务栈的栈顶地址 */pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );/* 对栈顶地址按宏 portBYTE_ALIGNMENT_MASK 进行字节对齐(8 字节对齐) */pxTopOfStack = (StackType_t*)(((portPOINTER_SIZE_TYPE)pxTopOfStack) &(~((portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK)));/* 检查栈顶地址是否按宏 portBYTE_ALIGNMENT_MASK 进行字节对齐(8 字节对齐) */configASSERT((( (portPOINTER_SIZE_TYPE)pxTopOfStack&(portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK )==0UL));/* 此宏用于开启栈顶地址最大值记录功能(用于调试,不用理会) */
#if ( configRECORD_STACK_HIGH_ADDRESS == 1 )
{/* 保存栈顶地址的最大值* 因为栈是向下生长的,* 因此初始值就是最大值*/pxNewTCB->pxEndOfStack = pxTopOfStack;
}
#endif
}
#else
{/* 获取任务栈的栈顶地址 */pxTopOfStack = pxNewTCB->pxStack;/* 检查栈顶地址是否按宏 portBYTE_ALIGNMENT_MASK 进行字节对齐(8 字节对齐) */configASSERT((( (portPOINTER_SIZE_TYPE)pxNewTCB->pxStack &(portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK ) ==0UL));/* 计算栈顶地址的最大值* 因为栈是向上生长的,* 因此栈顶地址的最大值为栈的初始值+栈的大小*/pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + (ulStackDepth - (uint32_t)1);
}
#endif/* 初始化任务名成员变量 */if( pcName != NULL ){/* 任务名的最大长度由宏 configMAX_TASK_NAME_LEN 定义 */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();}}/* 在任务名成员变量末尾加上'\0' */pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';}else{/* 为赋值任务名,* 创建任务时,可以不给任务名*/pxNewTCB->pcTaskName[ 0 ] = 0x00;}/* 检查任务优先级数值是否合法 */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;/* 用于解决优先级翻转问题 */pxNewTCB->uxMutexesHeld = 0;/* 用于互斥信号量的递归功能 */
}
#endif/* 初始化任务状态列表项和事件列表项成员变量 */vListInitialiseItem( &( pxNewTCB->xStateListItem ) );vListInitialiseItem( &( pxNewTCB->xEventListItem ) );/* 初始化任务状态列表项的拥有者为任务控制块 */listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );/* 初始化事件列表项的值与任务优先级成反比(列表中的列表项按照列表项的值,以升序排序) */listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ),(TickType_t)configMAX_PRIORITIES - (TickType_t)uxPriority);/* 初始化任务事件列表项的拥有者为任务控制块 */listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );/* 此宏用于启用任务单独临界区嵌套计数 */
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
{/* 任务单独临界区嵌套计数器初始化为 0 */pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
}
#endif/* 此宏用于用于自定义任务的钩子函数(用于调试,不用理会) */
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
{/* 钩子函数初始化为空 */pxNewTCB->pxTaskTag = NULL;
}
#endif/* 此宏用于启用任务运行时间统计功能 */
#if ( configGENERATE_RUN_TIME_STATS == 1 )
{/* 任务运行时间计数器初始化为 0 */pxNewTCB->ulRunTimeCounter = ( configRUN_TIME_COUNTER_TYPE ) 0;
}
#endif/* 此宏为 MPU 的相关配置,不用理会 */
#if ( portUSING_MPU_WRAPPERS == 1 )
{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/* 此宏与 Newlib 相关,不用理会 */
#if ( configUSE_NEWLIB_REENTRANT == 1 )
{_REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );
}
#endif/* 此宏用于启用任务延时中断功能 */
#if ( INCLUDE_xTaskAbortDelay == 1 )
{/* 任务被中断延时标志初始化为假 */pxNewTCB->ucDelayAborted = pdFALSE;
}
#endif/* 此宏为 MPU 的相关配置,不用理会 */
#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
{pxNewTCB->pxTopOfStack = pxPortInitialiseStack(pxTopOfStack,pxNewTCB->pxEndOfStack,pxTaskCode,pvParameters,xRunPrivileged);
}
#endif
}
#else
{pxNewTCB->pxTopOfStack = pxPortInitialiseStack(pxTopOfStack,pxTaskCode,pvParameters,xRunPrivileged);
}
#endif
}
#else
{/* 此部分用于初始化任务栈* 分为三种情况* 1. 启用了栈溢出检测功能并且栈的生长方向向下* 2. 启用了栈溢出检测功能并且栈的生长方向向上* 3. 未启用栈溢出检测功能(本教程着重分析这种情况)*/
#if ( portHAS_STACK_OVERFLOW_CHECKING == 1 )
{#if ( portSTACK_GROWTH < 0 ){/* 1. 启用了栈溢出检测功能并且栈的生长方向向下 */pxNewTCB->pxTopOfStack = pxPortInitialiseStack(pxTopOfStack,pxNewTCB->pxStack,pxTaskCode,pvParameters);}
#else
{/* 2. 启用了栈溢出检测功能并且栈的生长方向向上 */pxNewTCB->pxTopOfStack = pxPortInitialiseStack(pxTopOfStack,pxNewTCB->pxEndOfStack,pxTaskCode,pvParameters);
}
#endif
}
#else
{/* 3. 未启用栈溢出检测功能 */pxNewTCB->pxTopOfStack = pxPortInitialiseStack(pxTopOfStack,pxTaskCode,pvParameters);
}
#endif
}
#endif/* 如果需要返回任务句柄 */if( pxCreatedTask != NULL ){/* 返回任务句柄(任务控制块)* 任务句柄可用于更改任务优先级,* 或删除任务等操作*/*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;}else{mtCOVERAGE_TEST_MARKER();}
}

​ 以上就是函数 prvInitialiseNewTask()的具体代码,可以看到函数 prvInitialiseNewTask()就是初始化了任务控制块中的成员变量,其中比较重要的操作就是调用函数 pxPortInitialiseStack()初始化任务栈

函数 pxPortInitialiseStack()

作用 :函数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-M4内核的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()

作用:用于将新建的任务添加到就绪态任务列表中

函数原型在task.c中有定义,如下:

static void prvAddNewTaskToReadyList(TCB_t * pxNewTCB) /* 任务控制块 */
{/* 进入临界区,确保在操作就绪态任务列表时,中断不会访问列表 */taskENTER_CRITICAL();{/* 此全局变量用于记录系统中任务数量 */uxCurrentNumberOfTasks++;/* 此全局变量用于指示当前系统中处于就绪态任务中优先级最高的任务* 如果该全局变量为空(NULL),* 即表示当前创建的任务为系统中的唯一的就绪任务*/if( pxCurrentTCB == NULL ){/* 系统中无其他就绪任务,因此优先级最高的就绪态任务为当前创建的任务 */pxCurrentTCB = pxNewTCB;/* 如果当前系统中任务数量为 1,* 即表示当前创建的任务为系统中第一个任务*/if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ){/* 初始化任务列表(就绪态任务列表,任务阻塞列表) */prvInitialiseTaskLists();}else{mtCOVERAGE_TEST_MARKER();}}else{/* 判断任务调度器是否运行 */if( xSchedulerRunning == pdFALSE ){/* 当任务调度器为运行时* 将 pxCurrentTCB 更新为优先级最高的就绪态任务*/if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority ){pxCurrentTCB = pxNewTCB;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}/* 用于调试,不用理会 */uxTaskNumber++;/* 用于调试,不用理会 */#if ( configUSE_TRACE_FACILITY == 1 ){pxNewTCB->uxTCBNumber = uxTaskNumber;}#endiftraceTASK_CREATE( pxNewTCB );/* 将任务添加到就绪态任务列表中 */prvAddTaskToReadyList( pxNewTCB );/* 为定义,不用理会 */portSETUP_TCB( pxNewTCB );}/* 退出临界区 */taskEXIT_CRITICAL();/* 如果任务调度器正在运行,* 那么就需要判断,当前新建的任务优先级是否最高* 如果是,则需要切换任务*/if( xSchedulerRunning != pdFALSE ){/* 如果当前新建的任务优先级高于 pxCurrentTCB 的优先级 */if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority ){/* 进行任务切换 */taskYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}
}
  1. 可以看到函数 prvAddNewTaskToReadyList()调用了函数 prvAddTaskToReadyList()将新创建的任务添加到就绪态任务队列中。函数 prvAddTaskToReadyList()是一个宏,具体的定义如下所示:
#define prvAddTaskToReadyList( pxTCB ) 								\traceMOVED_TASK_TO_READY_STATE( pxTCB ); 							\taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority ); 				\listINSERT_END( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), 	&( ( pxTCB )->xStateListItem ) ); 									\tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB )

从上面的代码可以看出,宏 prvAddTaskToReadyList()主要就完成两件事:

首先是记录任务有优先级,FreeRTOS 会以位图的方式记录就绪态任务列表中就绪态任务的优先级,这样能够提高切换任务时的效率;

还有就是将任务的任务状态列表项插入到就绪态任务列表的末尾

​ 2.此函数还会根据任务调度器的运行状态,已经新创建的任务优先级是否比 pxCurrentTCB的 优 先 级 高 , 决 定 是 否 进 行 任 务 切 换。

总结(任务创建API函数内部流程

在这里插入图片描述

删除任务函数vTaskDelete()

**作用:**删除已创建的任务

函数 vTaskDelete()在 task.c 文件中有定义,具体的代码如下所示:

void vTaskDelete(TaskHandle_t xTaskToDelete) /* 待删除任务的任务句柄 */
{TCB_t * pxTCB;/* 进入临界区 */taskENTER_CRITICAL();{/* 如果传入的任务句柄为空(NULL)* 此函数会将待删除的任务设置为调用该函数的任务本身* 因此,如果要在任务中删除任务本身,* 那么可以调用函数 vTaskDelete(),并传入任务句柄,* 或传入 NULL*/pxTCB = prvGetTCBFromHandle( xTaskToDelete );/* 将任务从任务所在任务状态列表(就绪态任务列表或阻塞态任务列表)中移除* 如果移除后列表中的列表项数量为 0* 那么就需要更新任务优先级记录* 因为此时系统中可能已经没有和被删除任务相同优先级的任务了*/if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ){/* 更新任务优先级记录 */taskRESET_READY_PRIORITY( pxTCB->uxPriority );}else{mtCOVERAGE_TEST_MARKER();}/* 判断被删除的任务是否还有等待的事件 */if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL ){/* 将被删除任务的事件列表项,从所在事件列表中移除 */( void ) uxListRemove( &( pxTCB->xEventListItem ) );}else{mtCOVERAGE_TEST_MARKER();}/* 由于调试,不用理会 */uxTaskNumber++;/* 判断被删除的任务是否为正在运行的任务(即任务本身) */if( pxTCB == pxCurrentTCB ){/* 任务是无法删除任务本身的,于是需要将任务添加到任务待删除列表中* 空闲任务会处理任务待删除列表中的待删除任务*/vListInsertEnd( &xTasksWaitingTermination,&( pxTCB->xStateListItem ) );/* 这个全局变量用来告诉空闲任务有多少个待删除任务需要被删除 */++uxDeletedTasksWaitingCleanUp;/* 用于调试,不用理会 */traceTASK_DELETE( pxTCB );/* 未定义,不用理会 */portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );}else{/* 任务数量计数器减 1 */--uxCurrentNumberOfTasks;/* 用于调试,不用理会 */traceTASK_DELETE( pxTCB );/* 更新下一个任务的阻塞超时时间,以防被删除的任务就是下一个阻塞超时的任务 */prvResetNextTaskUnblockTime();}}/* 退出临界区 */taskEXIT_CRITICAL();/* 如果待删除任务不是任务本身 */if( pxTCB != pxCurrentTCB ){/* 此函数用于释放待删除任务占用的内存资源 */prvDeleteTCB( pxTCB );}/* 如果任务调度器正在运行,* 那么就需要判断,待删除任务是否为任务本身* 如果是,则需要切换任务*/if( xSchedulerRunning != pdFALSE ){/* 如果待删除任务就是任务本身 */if( pxTCB == pxCurrentTCB ){/* 此时任务调度器不能处于挂起状态 */configASSERT( uxSchedulerSuspended == 0 );/* 进行任务切换 */portYIELD_WITHIN_API();}else{mtCOVERAGE_TEST_MARKER();}}
}
  1. 从上面的代码中可以看出,使用 vTaskDelete()删除任务时,需要考虑两种情况,分别为待删除任务不是当前正在运行的任务(调用该函数的任务)和待删除任务为当前正在运行的任务(调用该函数的任务)。

    第一种情况比较简单,当前正在运行的任务可以直接删除待删除任务;而第二种情况下,待删除任务时无法删除自己的,因此需要将当前任务添加到任务待删除列表中,空闲任务会处理这个任务待删除列表,将待删除的任务统一删除.

  2. 在待删除任务不是当前正在运行的任务这种情况下,当前正在运行的任务可以删除待删除的任务,因此调用了函数 prvDeleteTCB(),将待删除的任务删除。

函数 prvDeleteTCB()

作用:该函数主要用于释放待删除任务所占的内存空间

函数原型在 task.c 文件中有定义,具体的代码如下所示

static void prvDeleteTCB(TCB_t * pxTCB) /* 待删除任务的任务控制块 */
{/* 未定义,不用理会 */portCLEAN_UP_TCB( pxTCB );/* 与 Newlib 相关 */#if ( configUSE_NEWLIB_REENTRANT == 1 ){_reclaim_reent( &( pxTCB->xNewLib_reent ) );}#endif /* configUSE_NEWLIB_REENTRANT *//* 当系统只支持动态内存管理时,
* 任务待删除任务所占用的内存空间是通过动态内存管理分配的,
* 因此只需要将内存空间通过动态内存管理释放掉即可
* 当系统支持静态内存管理和动态内存管理时,
* 则需要分情况讨论
*/#if ( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && \( configSUPPORT_STATIC_ALLOCATION == 0 ) && \( portUSING_MPU_WRAPPERS == 0 ) ){/* 动态内存管理释放待删除任务的任务控制块和任务的栈空间 */vPortFreeStack( pxTCB->pxStack );vPortFree( pxTCB );}#elif ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ){/* 待删除任务的任务控制块和任务栈都是由动态内存管理分配的 */if( pxTCB->ucStaticallyAllocated == tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB ){/* 动态内存管理释放待删除任务的任务控制块和任务的栈空间 */vPortFreeStack( pxTCB->pxStack );vPortFree( pxTCB );}/* 待删除任务的任务控制块是由动态内存管理分配的 */else if( pxTCB->ucStaticallyAllocated ==tskSTATICALLY_ALLOCATED_STACK_ONLY ){/* 动态内存管理释放待删除任务的任务控制块 */vPortFree( pxTCB );}/* 待删除任务的任务控制块和任务栈都是由静态内存管理分配的 */else{/* 不能还存在其他情况 *//* 这种情况下,待删除任务的任务控制块和任务栈空间所占用的内存* 由用户管理*/configASSERT( pxTCB->ucStaticallyAllocated ==tskSTATICALLY_ALLOCATED_STACK_AND_TCB );mtCOVERAGE_TEST_MARKER();}}#endif
}
删除任务API函数流程简述

在这里插入图片描述

务函数** vTaskSuspend()

作用: 挂起(暂停)任务

​ 函数原型,如下

void vTaskSuspend(TaskHandle_t xTaskToSuspend) /* 待挂起任务的任务句柄 */
{TCB_t * pxTCB;/* 进入临界区 */taskENTER_CRITICAL();{/* 如果传入的任务句柄为空(NULL)* 此函数会将待挂起的任务设置为调用该函数的任务本身* 因此,如果要在任务中挂起任务本身,* 那么可以调用函数 vTaskSuspend(),并传入任务句柄,* 或传入 NULL*/pxTCB = prvGetTCBFromHandle( xTaskToSuspend );/* 调试使用,不用理会 */traceTASK_SUSPEND( pxTCB );/* 将任务从任务所在任务状态列表(就绪态任务列表或阻塞态任务列表)中移除* 如果移除后列表中的列表项数量为 0* 那么就需要更新任务优先级记录* 因为此时系统中可能已经没有和被挂起任务相同优先级的任务了*/if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ){/* 更新任务优先级记录 */taskRESET_READY_PRIORITY( pxTCB->uxPriority );}else{mtCOVERAGE_TEST_MARKER();}/* 判断被挂起的任务是否还有等待的事件 */if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL ){/* 将被挂起任务的事件列表项,从所在事件列表中移除 */( void ) uxListRemove( &( pxTCB->xEventListItem ) );}else{mtCOVERAGE_TEST_MARKER();}/* 将待挂起任务的任务状态列表向插入到挂起态任务列表末尾 */vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xStateListItem ) );/* 此宏用于启用任务通知功能 */#if ( configUSE_TASK_NOTIFICATIONS == 1 ){BaseType_t x;/* 遍历待挂起任务的所有任务通知状态 */for( x = 0; x < configTASK_NOTIFICATION_ARRAY_ENTRIES; x++ ){/* 如果有正在等待的任务通知,则取消等待* 因为此时,任务已经被挂起*/if( pxTCB->ucNotifyState[ x ] == taskWAITING_NOTIFICATION ){pxTCB->ucNotifyState[ x ] = taskNOT_WAITING_NOTIFICATION;}}}#endif}/* 退出临界区 */taskEXIT_CRITICAL();/* 判断任务调度器是否正在运行* 如果任务调度器正在运行,* 则需要更新下一个任务的阻塞超时时间,* 以防被挂起的任务就是下一个阻塞超时的任务*/if( xSchedulerRunning != pdFALSE ){taskENTER_CRITICAL();{prvResetNextTaskUnblockTime();}taskEXIT_CRITICAL();}else{mtCOVERAGE_TEST_MARKER();}/* 如果待挂起任务就是任务本身 */if( pxTCB == pxCurrentTCB ){/* 如果任务调度器正在运行,则需要切换任务 */if( xSchedulerRunning != pdFALSE ){/* 此时任务调度器不能处于挂起状态 */configASSERT( uxSchedulerSuspended == 0 );/* 进行任务切换 */portYIELD_WITHIN_API();}else{/* 如果任务调度器没有运行,并且 pxCurrentTCB 又指向了待挂起的任务,* 那么就需要将 pxCurrentTCB 指向其他任务*/if( listCURRENT_LIST_LENGTH( &xSuspendedTaskList ) ==uxCurrentNumberOfTasks ){/* 没有就绪的任务,则将 pxCurrentTCB 指向空(NULL) */pxCurrentTCB = NULL;}else{/* 更新 pxCurrentTCB 为优先级最高的就绪态任务 */vTaskSwitchContext();}}}else{mtCOVERAGE_TEST_MARKER();}
}

​ 该函数也有两种情况 不过是任务调度器来区分

一是挂起任务时任务调度器在运行,正常由调用该函数的任务挂起

二是挂起任务为任务本身且任务调度器不在运行,那么 pxCurrentTCB 需要指向其他优先级最高的就绪态任务,

更新 pxCurrentTCB 的操作,时通过调用函数 vTaskSwitchContext()实现的

函数 vTaskSwitchContext()

**作用:**该函数用于更新 pxCurrentTCB 指向就绪态任务列表中优先级最高的任务

函数原型 ,如下:

void vTaskSwitchContext( void )
{if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE ){/* 任务调度器没有运行,不允许切换上下文,直接退出函数 */xYieldPending = pdTRUE;}else{xYieldPending = pdFALSE;/* 用于调试,不用理会 */traceTASK_SWITCHED_OUT();/* 此宏用于启用任务运行时间统计功能 */#if ( configGENERATE_RUN_TIME_STATS == 1 ){#ifdef portALT_GET_RUN_TIME_COUNTER_VALUEportALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime );#elseulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE();#endifif( ulTotalRunTime > ulTaskSwitchedInTime ){pxCurrentTCB->ulRunTimeCounter +=( ulTotalRunTime - ulTaskSwitchedInTime );}else{mtCOVERAGE_TEST_MARKER();}ulTaskSwitchedInTime = ulTotalRunTime;}#endif/* 未定义,不用理会 */taskCHECK_FOR_STACK_OVERFLOW();/* 与 POSIX 相关配置,不用理会 */#if ( configUSE_POSIX_ERRNO == 1 ){pxCurrentTCB->iTaskErrno = FreeRTOS_errno;}#endif/* 此函数用于将 pxCurrentTCB 更新为指向优先级最高的就绪态任务 */taskSELECT_HIGHEST_PRIORITY_TASK();/* 用于调试,不用理会 */traceTASK_SWITCHED_IN();/* 与 POSIX 相关配置,不用理会 */#if ( configUSE_POSIX_ERRNO == 1 ){FreeRTOS_errno = pxCurrentTCB->iTaskErrno;}#endif/* 与 Newlib 相关配置,不用理会 */#if ( configUSE_NEWLIB_REENTRANT == 1 ){_impure_ptr = &( pxCurrentTCB->xNewLib_reent );}#endif}
}

​ 此 函 数 的 重 点 在 于 调 用 了 函 数 taskSELETE_HIGHEST_PRIORITY_TASK() 更 新pxCurrentTCB 指向优先级最高的就绪态任务,函数 **taskSELETE_HIGHEST_PRIORITY_TASK()**实际上是一个宏定义(),task.c 文件中有定义,具体的代码如下所示:

#define taskSELECT_HIGHEST_PRIORITY_TASK() \
{ \UBaseType_t uxTopPriority; \\/* 查找就绪态任务列表中最高的任务优先级 */ \portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); \/* 此任务优先级不能低于最低的任务优先级 */ \configASSERT( \listCURRENT_LIST_LENGTH(&( pxReadyTasksLists[ uxTopPriority ] ) ) > \0 \); \/* 让 pxCurrentTCB 指向该任务优先级就绪态任务列表中的任务 */ \listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, \&( pxReadyTasksLists[ uxTopPriority ] ) ); \
}
恢复任务函数vTaskResume()

**作用:**恢复被挂起的任务

函数原型,如下:

void vTaskResume(TaskHandle_t xTaskToResume) /* 待恢复的任务句柄 */
{TCB_t * const pxTCB = xTaskToResume;/* 确保有指定的待恢复任务 */configASSERT( xTaskToResume );/* 待恢复的任务不能时当前正在运行的任务,并且也不能为空(NULL) */if( ( pxTCB != pxCurrentTCB ) && ( pxTCB != NULL ) ){/* 进入临界区 */taskENTER_CRITICAL();{/* 判断任务是否被挂起* 只有被挂起的任务才需要恢复*/if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE ){/* 用于调试,不用理会 */traceTASK_RESUME( pxTCB );/* 将待恢复任务的任务状态列表项* 从所在任务状态列表(挂起态任务列表)中移除*/( void ) uxListRemove( &( pxTCB->xStateListItem ) );/* 将待恢复任务的任务状态列表项* 添加到就绪态任务列表中*/prvAddTaskToReadyList( pxTCB );/* 如果待恢复任务的优先级比当前正在运行的任务的任务优先级高* 则需要进行任务切换*/if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ){taskYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}/* 退出临界区 */taskEXIT_CRITICAL();}else{mtCOVERAGE_TEST_MARKER();}
}

​ 以上函数主要就是将待恢复任务的任务状态列表项 从所在任务状态列表(挂起态任务列表)中移除,将待恢复任务的任务状态列表项添加到就绪态任务列表中。

空闲任务函数prvIdleTask()

**作用:**空闲任务主要用于处理待删除任务列表和低功耗

static portTASK_FUNCTION(prvIdleTask, /* 空闲任务函数的函数名 */pvParameters) /* 空闲任务函数的函数参数 */
{/* 未使用的传入参数,防止编译器警告 */( void ) pvParameters;/* 未定义,不用理会 */portALLOCATE_SECURE_CONTEXT( configMINIMAL_SECURE_STACK_SIZE );for( ; ; ){/* 处理待删除任务列表中的待删除任务 */prvCheckTasksWaitingTermination();/* 此宏用于使能抢占式调度,* 当此宏定义为 1 时,使能抢占式调度* 当此宏定义为 0 时,则不使能抢占式调度*/#if ( configUSE_PREEMPTION == 0 ){/* 如果不使用抢占式调度,则强制切换任务,* 以确保其他任务(非空闲任务)可以获得 CPU 使用权,* 如果使能了抢占式调度,则不需要这么做,* 因为优先级高的就绪态任务会自动抢占 CPU 使用权*/taskYIELD();}#endif/* 宏 configIDLE_SHOULD_YIELD 用于使能空闲任务可以被同优先级的任务抢占 */#if ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) ){/* 如果存在与空闲任务相同优先级的任务,则进行任务切换 */if( listCURRENT_LIST_LENGTH(&(pxReadyTasksLists[tskIDLE_PRIORITY]))>( UBaseType_t ) 1 ){taskYIELD();}else{mtCOVERAGE_TEST_MARKER();}}#endif/* 此宏用于使能空闲任务的钩子函数* 空闲任务的钩子函数,需要用户自行定义*/#if ( configUSE_IDLE_HOOK == 1 ){extern void vApplicationIdleHook( void );/* 调用空闲任务的钩子函数 */vApplicationIdleHook();}#endif /* configUSE_IDLE_HOOK *//* 此宏为低功耗的相关配置,不用理会 */#if ( configUSE_TICKLESS_IDLE != 0 ){TickType_t xExpectedIdleTime;xExpectedIdleTime = prvGetExpectedIdleTime();if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP ){vTaskSuspendAll();configASSERT( xNextTaskUnblockTime >= xTickCount );xExpectedIdleTime = prvGetExpectedIdleTime();configPRE_SUPPRESS_TICKS_AND_SLEEP_PROCESSING( xExpectedIdleTime );if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP ){traceLOW_POWER_IDLE_BEGIN();portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );traceLOW_POWER_IDLE_END();}else{mtCOVERAGE_TEST_MARKER();}( void ) xTaskResumeAll();}else{mtCOVERAGE_TEST_MARKER();}}#endif}
}

​ 该函数 处理带删除函数列表里的函数,还有运行钩子函数

对空闲任务的了解不多,后续学到会补充

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

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

相关文章

Vue2.0 通过vue-pdf-signature@4.2.7和pdfjs-dist@2.5.207实现PDF预览

1.安装依赖 npm install pdfjs-dist2.5.207 --savenpm install vue-pdf-signature4.2.7 --save2.在.vue文件中 script 部分引入 <script> import * as PDFJS from pdfjs-dist PDFJS.GlobalWorkerOptions.workerSrc require(pdfjs-dist/build/pdf.worker.js);//解决pdf…

kafka-console-ui的简介及安装使用

kafka-console-ui的简介及安装使用 一、kafka-console-ui的简介二、安装kafka-console-ui2.1 源码安装2.2 docker安装 三、kafka-console-ui功能使用3.1、功能特性3.2、 功能介绍3.2.1 集群3.2.2 topic3.2.3 消费组3.2.4 Acl3.2.5 运维 一、kafka-console-ui的简介 kafka-cons…

MySQL(上)

一、SQL优化 1、如何定位及优化SQL语句的性能问题&#xff1f;创建的索引有没有被使用到?或者说怎么才可以知道这条语句运行很慢的原因&#xff1f; 对于性能比较低的sql语句定位&#xff0c;最重要的也是最有效的方法其实还是看sql的执行计划&#xff0c;而对于mysql来说&a…

贪心算法---java---黑马

贪心算法 1)Greedy algorithm 称之为贪心算法或者贪婪算法&#xff0c;核心思想是 将寻找最优解的问题分为若干个步骤每一步骤都采用贪心原则&#xff0c;选取当前最优解因为未考虑所有可能&#xff0c;局部最优的堆叠不一定得到最终解最优 贪心算法例子 Dijkstra while …

操作系统——计算机系统概述——1.4操作系统结构

目录 操作系统的体系结构 大内核&#xff08;宏内核/单内核&#xff09;&#xff1a; 微内核&#xff1a; 分层法 模块化 操作系统的体系结构 大内核&#xff08;宏内核/单内核&#xff09;&#xff1a; 将操作系统的主要功能模块都作为系统内核&#xff0c;运行在核心态。…

【MyBatis源码】BoundSql分析

基础 BoundSql是对SQL语句及参数信息的封装&#xff0c;它是SqlSource解析后的结果。Executor组件并不是直接通过StaticSqlSource对象完成数据库操作的&#xff0c;而是与BoundSql交互。BoundSql是对Executor组件执行SQL信息的封装&#xff0c;具体实现代码如下&#xff1a; …

本地部署bert-base-chinese模型交互式问答,gradio

首先下载bert-base-chinese&#xff0c;可以在 Huggingface, modelscope, github下载 pip install gradio torch transformers import gradio as gr import torch from transformers import BertTokenizer, BertForQuestionAnswering# 加载bert-base-chinese模型和分词器 mod…

【题】C#-数组:二维数组

1. 将1~10000赋值给一个二维数组(100行100列) int[,] array new int[100,100]; int index 1; for(int i 0;i < array.GetLength(0);i){for(int j 0;j < array.GetLength(1);j){array[i,j] index;index;} }2. 将二维数组的右上半部分置零 int[,] array new int[4,…

你还在用一串数字访问你的系统吗?

大家还记得第一次启动SpringBoot应用并在浏览器访问是如何进行的吗&#xff1f;在SpringBoot启动后&#xff0c;我们会看到如图所示&#xff1a; SpringBoot内置tomcat以端口8080启动&#xff0c;然后根据指引&#xff0c;我们在浏览器输入&#xff1a; http://127.0.0.1:8080…

ffmpeg视频滤镜:膨胀操作-dilation

滤镜介绍 dilation 官网链接 > FFmpeg Filters Documentation 膨胀滤镜会使图片变的更亮&#xff0c;会让细节别的更明显。膨胀也是形态学中的一种操作&#xff0c;在opencv中也有响应的算子。此外膨胀结合此前腐蚀操作&#xff0c;可以构成开闭操作。 开操作是先腐蚀…

Unity中的屏幕坐标系

获得视口宽高 拖动视口会改变屏幕宽高数值 MousePosition 屏幕坐标系的原点在左下角&#xff0c;MousePosition返回Z为0也就是纵深为0的Vector3 但是如果鼠标超出屏幕范围不会做限制&#xff0c;所以可能出现负数或者大于屏幕宽高的情况&#xff0c;做鼠标拖拽物体时需要注…

基于SpringBoot的学生读书笔记共享的设计与实现

一、项目背景 计算机的普及和互联网时代的到来使信息的发布和传播更加方便快捷。用户可以通过计算机上的浏览器访问多个应用系统&#xff0c;从中获取一些可以满足用户需求的管理系统。网站系统有时更像是一个大型“展示平台”&#xff0c;用户可以选择所需的信息进入系统查看…

【数据结构】二叉树——层序遍历

层序遍历 一、层序遍历二、层序遍历&#xff08;递归&#xff09;三、层序遍历&#xff08;非递归&#xff09;四、总结 一、层序遍历 层序遍历是一种广度优先遍历 以图上二叉树为例&#xff0c;层序遍历就是按照二叉树的深度一层一层进行遍历 遍历顺序&#xff1a; A B C D …

react使用Fullcalendar

前言&#xff1a; 最近在做项目时&#xff0c;遇到了需要用日历的项目。一开始考虑使用antd的日历组件。后来 调研技术库&#xff0c;发现了fullcalendar 库。经过对比 fullcalendar 更强大&#xff0c;更灵活。 其实 antd的日历组件 也不错&#xff0c;简单的需求用他也行。…

Golang--函数、包、defer、系统函数、内置函数

1、何为函数 函数作用&#xff1a;提高代码的复用型&#xff0c;减少代码的冗余&#xff0c;提高代码的维护性 函数定义&#xff1a;为完成某一功能的程序指令(语句)的集合,称为函数。 语法&#xff1a; func 函数名(形参列表)(返回值类型列表){ //执行语句 //…… return …

Chrome和Firefox如何保护用户的浏览数据

在当今数字化时代&#xff0c;保护用户的浏览数据变得尤为重要。浏览器作为我们日常上网的主要工具&#xff0c;其安全性直接关系到个人信息的保密性。本文将详细介绍Chrome和Firefox这两款主流浏览器如何通过一系列功能来保护用户的浏览数据。&#xff08;本文由https://chrom…

如何在Linux系统中使用SSH进行安全连接

如何在Linux系统中使用SSH进行安全连接 SSH简介 安装SSH 在Debian/Ubuntu系统中安装 在CentOS/RHEL系统中安装 启动SSH服务 验证SSH是否安装成功 SSH配置 配置监听端口 配置登录方式 SSH客户端 安装SSH客户端 使用SSH客户端 SSH密钥认证 生成SSH密钥对 复制公钥到远程服务器…

ElasticSearch - Bucket Selector使用指南

文章目录 官方文档Bucket Selector1. 定义2. 工作原理3. 使用场景与示例使用场景官方案例示例2 4. 注意事项5. 总结 官方文档 https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html Bucket Selector https://www.elastic.co/guide/en/…

RHCE——笔记

Web服务器 1&#xff0c;web服务器简介 &#xff08;1&#xff09;什么是www 是全球信息广播的意思。通常说的上网就是使用 www 来查询用户 所需要的信息。 www 可以结合文字、图形、影像以及声音等多媒体&#xff0c;并通过可以让鼠标单击超链接的方式将信息以Internet 传递…

Unity XR Interaction Toolkit 开发教程(1):OpenXR 与 XRI 概述【3.0 以上版本】

文章目录 &#x1f4d5;Unity XR 开发架构&#x1f50d;底层插件&#xff08;对接硬件&#xff09;&#x1f50d;高层 SDK&#xff08;面向应用交互层&#xff09; &#x1f4d5;OpenXR&#x1f4d5;XR Interaction Toolkit&#x1f50d;特点&#x1f50d;XRI 能够实现的交互类…