API函数详解(创建任务函数 和 删除任务函数)
创建函数 xTaskCreate()
作用:创建任务
函数 xTaskCreate()在 task.c 文件中有定义,具体的代码如下所示:
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;
}
-
函数 xTaskCreate()创建任务,首先为任务的任务控制块以及任务栈空间申请内存,如果任务控制块或任务栈空间的内存申请失败,则释放已经申请到的内存,并返回内存申请失败的错误。
-
任务创建所需的内存申请成功后,使用函数 prvInitialiseNewTask()初始化任务控制块中的成员变量,包括任务函数指针、任务名、任务栈大小、任务函数参数、任务优先级等。
-
最后调用函数 prvAddNewTaskToReadList()将任务添加到就绪态任务列表中,从这里可以看出,任务被创建后,是立马被添加到就绪态任务列表中的。
函数 prvInitialiseNewTask()
作用:函数 prvInitialiseNewTask()用于创建任务时,初始化任务控制块中的成员变量
函数prvInitialiseNewTask()在 task.c 文件中有定义,具体的代码如下所示
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()函数原型:
c
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中有定义,如下:
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;
}
#endif
traceTASK_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();
}
}
- 可以看到函数 prvAddNewTaskToReadyList()调用了函数 prvAddTaskToReadyList()将新创建的任务添加到就绪态任务队列中。函数 prvAddTaskToReadyList()是一个宏,具体的定义如下所示:
c
#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 文件中有定义,具体的代码如下所示:
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();
}
}
}
-
从上面的代码中可以看出,使用 vTaskDelete()删除任务时,需要考虑两种情况,分别为待删除任务不是当前正在运行的任务(调用该函数的任务)和待删除任务为当前正在运行的任务(调用该函数的任务)。
第一种情况比较简单,当前正在运行的任务可以直接删除待删除任务;而第二种情况下,待删除任务时无法删除自己的,因此需要将当前任务添加到任务待删除列表中,空闲任务会处理这个任务待删除列表,将待删除的任务统一删除.
-
在待删除任务不是当前正在运行的任务这种情况下,当前正在运行的任务可以删除待删除的任务,因此调用了函数 prvDeleteTCB(),将待删除的任务删除。
函数 prvDeleteTCB()
作用:该函数主要用于释放待删除任务所占的内存空间
函数原型在 task.c 文件中有定义,具体的代码如下所示
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()
作用: 挂起(暂停)任务
函数原型,如下
c
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 指向就绪态任务列表中优先级最高的任务
函数原型 ,如下:
c
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_VALUE
portALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime );
#else
ulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE();
#endif
if( 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 文件中有定义,具体的代码如下所示:
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()
**作用:**恢复被挂起的任务
函数原型,如下:
c
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()
**作用:**空闲任务主要用于处理待删除任务列表和低功耗
c
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
}
}
该函数 处理带删除函数列表里的函数,还有运行钩子函数
对空闲任务的了解不多,后续学到会补充