FreeRTOS笔记(四):任务创建/删除,挂起/解挂详解
在第二篇笔记中介绍了任务创建的API,并且简单使用了相关API,本文将详细介绍任务创建的过程。
一、任务创建
任务创建步骤为:
1.创建任务;
2.初始化任务;
3.任务堆栈初始化;
4.任务添加到就绪列表。
下面将详细介绍这四个部分。
1.1任务创建函数分析
任务创建函数为xTaskCreate(),函数的具体实现如下:
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint16_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
{
TCB_t *pxNewTCB;
BaseType_t xReturn;
StackType_t *pxStack;
pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) \
* sizeof( StackType_t ) ) ); //---------------(1)
if( pxStack != NULL )
{
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); //---------------(2)
if( pxNewTCB != NULL )
{
pxNewTCB->pxStack = pxStack; //---------------(3)
}
else
{
vPortFree( pxStack ); //---------------(4)
}
}
else
{
pxNewTCB = NULL;
}
if( pxNewTCB != NULL )
{
#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
{
pxNewTCB->ucStaticallyAllocated = \
tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB; //---------------(5)
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
prvInitialiseNewTask( pxTaskCode, pcName, \
( uint32_t ) usStackDepth, pvParameters, uxPriority,\
pxCreatedTask, pxNewTCB, NULL ); //---------------(6)
prvAddNewTaskToReadyList( pxNewTCB ); //---------------(7)
xReturn = pdPASS;
}
else
{
xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
}
return xReturn;
}
(1)申请任务堆栈内存;
(2)如果堆栈申请成功,则申请任务控制块内存;
(3)初始化任务控制块中的堆栈指针;
(4)任务控制块申请失败,就释放申请到的任务堆栈内存;
(5)标记任务控制块内存、任务堆栈内存由动态申请得到;
(6)初始化任务控制块,后面详述prvInitialiseNewTask();
(7)新建的任务添加到就绪列表中,后面详述prvAddNewTaskToReadyList()。
1.2任务初始化函数
任务创建函数中,需要对任务进行初始化,函数详解如下:
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 )
{
StackType_t *pxTopOfStack;
UBaseType_t x;
#if( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) || ( configUSE_TRACE_FACILITY == 1 )\
|| ( INCLUDE_uxTaskGetStackHighWaterMark == 1 ) )
{
( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE,\
( size_t ) ulStackDepth * sizeof( StackType_t ) ); //-------------------(1)
}
#endif
pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 ); //-------------------(2)
pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) &\
( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
{
pxNewTCB->pcTaskName[ x ] = pcName[ x ]; //-------------------(3)
if( pcName[ x ] == 0x00 )
{
break;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0'; //-------------------(4)
if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES ) //-------------------(5)
{
uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
pxNewTCB->uxPriority = uxPriority; //-------------------(6)
#if ( configUSE_MUTEXES == 1 ) //-------------------(7)
{
pxNewTCB->uxBasePriority = uxPriority;
pxNewTCB->uxMutexesHeld = 0;
}
#endif /* configUSE_MUTEXES */
vListInitialiseItem( &( pxNewTCB->xStateListItem ) ); //-------------------(8)
vListInitialiseItem( &( pxNewTCB->xEventListItem ) ); //-------------------(9)
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );//-------------------(10)
listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), \
( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority ); //-------------------(11)
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB ); //-------------------(12)
#if ( portCRITICAL_NESTING_IN_TCB == 1 ) //使能临界区
{
pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
}
#endif /* portCRITICAL_NESTING_IN_TCB */
#if ( configUSE_APPLICATION_TASK_TAG == 1 ) //使能任务标签
{
pxNewTCB->pxTaskTag = NULL;
}
#endif /* configUSE_APPLICATION_TASK_TAG */
#if ( configGENERATE_RUN_TIME_STATS == 1 ) //使能实践统计
{
pxNewTCB->ulRunTimeCounter = 0UL;
}
#endif /* configGENERATE_RUN_TIME_STATS */
#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
{
for( x = 0; x < ( UBaseType_t ) configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ )
{
pxNewTCB->pvThreadLocalStoragePointers[ x ] = NULL; //-------------------(13)
}
}
#endif
#if ( configUSE_TASK_NOTIFICATIONS == 1 ) //使能任务通知
{
pxNewTCB->ulNotifiedValue = 0;
pxNewTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
}
#endif
#if ( configUSE_NEWLIB_REENTRANT == 1 ) //使能NEWLIB
{
_REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );
}
#endif
#if( INCLUDE_xTaskAbortDelay == 1 ) //使能xTaskAbortDelay
{
pxNewTCB->ucDelayAborted = pdFALSE;
}
#endif
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, \
pxTaskCode, pvParameters, xRunPrivileged );//-------------------(14)
if( ( void * ) pxCreatedTask != NULL )
{
*pxCreatedTask = ( TaskHandle_t ) pxNewTCB; //-------------------(15)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
(1)初始化堆栈元素为0xa5U,用于检测堆栈溢出;
(2)获取栈顶,保存到栈顶指针pxTopOfStack;
(3)保存任务名字到 pxNewTCB->pcTaskName;
(4)保存任务优先级 pxNewTCB->uxPriority 范围0-31;
(5)判断优先级是否合法;
(6)初始化优先级;
(7)如果使用了互斥量,初始化相应字段,主要实现优先级继承;
(8)、(9)初始化状态和事件列表项;
(10)、(12)设置xEventListItem、xStateListItem属于当前任务控制块;
(11)设置xEventListItem中的xItemValue为 configMAX_PRIORITIES - uxPriority,实现高优先级任务列表前面,低优先级在后面;
(13)初始化线程本地存储指针;
(14)初始化任务堆栈pxPortInitialiseStack,后面详述;
(15)生成任务句柄,任务句柄就是任务控制块。
1.3任务堆栈初始化
在任务初始函数要对任务堆栈进行初始化。堆栈是用来在任务进行切换时保存现场的,一般在新创建好一个堆栈以后对其初始化,对寄存器赋初值。这些初值保存在任务堆栈中,保存顺序按照如下:
xPSR、R15(PC)、R14(LR)、R12、R3-R0、R11-R4
函数详解如下:
StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, \
TaskFunction_t pxCode, void *pvParameters )
{
pxTopOfStack--;
*pxTopOfStack = portINITIAL_XPSR; //--------------------(1)
pxTopOfStack--;
*pxTopOfStack = ( ( StackType_t ) pxCode ) &\
portSTART_ADDRESS_MASK; //--------------------(2)
pxTopOfStack--;
*pxTopOfStack = ( StackType_t ) prvTaskExitError; //--------------------(3)
pxTopOfStack -= 5; //--------------------(4)
*pxTopOfStack = ( StackType_t ) pvParameters; //--------------------(5)
pxTopOfStack -= 8; //--------------------(6)
return pxTopOfStack;
}
(1)设置xPSR(程序状态寄存器),这个寄存器的bit24为1,表示处于Thumb状态;
(2)寄存器PC初始化为pxCode;
(3)寄存器LR初始化为prvTaskExitError;
(4)跳过4个寄存器R12,R3,R2,R1不初始化;
(5)寄存器R0初始化为pvParameters;
(6)跳过8个寄存器,R11,R10,R8,R7,R6,R5,R4。
初始化完后,堆栈情况如下图
1.4添加任务到就绪列表函数
在创建任务函数中,最后要把任务添加到就绪列表中,进行详解函数。
static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
{
taskENTER_CRITICAL();
{
uxCurrentNumberOfTasks++; //---------------------(1)
if( pxCurrentTCB == NULL ) //没有任务执行
{
pxCurrentTCB = pxNewTCB; //新任务控制块赋值给当前任务控制块
if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ) //新任务是第一个任务
{
prvInitialiseTaskLists(); //---------------------(2)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else //当前有任务执行
{
if( xSchedulerRunning == pdFALSE ) //任务调度器没开启
{
if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )//新任务优先级高
{
pxCurrentTCB = pxNewTCB; //---------------------(3)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
uxTaskNumber++; //任务控制块编号
#if ( configUSE_TRACE_FACILITY == 1 )
{
pxNewTCB->uxTCBNumber = uxTaskNumber;
}
#endif /* configUSE_TRACE_FACILITY */
prvAddTaskToReadyList( pxNewTCB ); //---------------------(4)
}
taskEXIT_CRITICAL();
if( xSchedulerRunning != pdFALSE )
{
if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority ) //新任务优先级高
{
taskYIELD_IF_USING_PREEMPTION(); //---------------------(5)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
二、任务删除
在笔记的第二篇中简单使用了任务删除,本节将详细介绍删除函数工作原理。
void vTaskDelete( TaskHandle_t xTaskToDelete )
{
TCB_t *pxTCB;
taskENTER_CRITICAL();
{
pxTCB = prvGetTCBFromHandle( xTaskToDelete ); //----------------------(1)
if( uxListRemove( &( pxTCB->xStateListItem ) ) \
== ( UBaseType_t ) 0 ) //----------------------(2)
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
if( listLIST_ITEM_CONTAINER( & \
( pxTCB->xEventListItem ) ) != NULL )//----------------------(3)
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
uxTaskNumber++;
if( pxTCB == pxCurrentTCB ) //----------------------(4)
{
vListInsertEnd( &xTasksWaitingTermination, \
&( pxTCB->xStateListItem ) ); //----------------------(5)
++uxDeletedTasksWaitingCleanUp; //----------------------(6)
portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending ); //----------------------(7)
}
else
{
--uxCurrentNumberOfTasks; //----------------------(8)
prvDeleteTCB( pxTCB ); //----------------------(9)
prvResetNextTaskUnblockTime(); //----------------------(10)
}
traceTASK_DELETE( pxTCB );
}
taskEXIT_CRITICAL();
if( xSchedulerRunning != pdFALSE ) //删除的是正在运行的任务就进行任务切换
{
if( pxTCB == pxCurrentTCB )
{
configASSERT( uxSchedulerSuspended == 0 );
portYIELD_WITHIN_API(); //----------------------(11)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
(1)获取删除的任务控制块,如果删除自己,则返回NULL;
(2)将任务从就绪列表删除;
(3)查看任务是否在等待某个事件,如果是,就从相应的等待列表中删除;
(4)、(5)要删除的是正在运行的任务,由于要等到任务执行完才能释放资源,
所以先添加到xTasksWaitingTermination列表中,由空闲任务来释放资源;
(6)记录要释放任务的个数;
(7)调用任务删除钩子函数;
(8)、(9)删除的不是正在运行的任务,任务数量减一,删除任务控制块;
(10)调整下一个任务要解锁的时间;
(11) 删除的是正在运行的任务就进行任务切换。
三、任务挂起
挂起任务的源码如下:
void vTaskSuspend( TaskHandle_t xTaskToSuspend )
{
TCB_t *pxTCB;
taskENTER_CRITICAL();
{
pxTCB = prvGetTCBFromHandle( xTaskToSuspend ); //---------------------(1)
traceTASK_SUSPEND( pxTCB );
if( uxListRemove( &( pxTCB->xStateListItem ) ) \
== ( UBaseType_t ) 0 ) //---------------------(2)
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) \
!= NULL ) //---------------------(3)
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
vListInsertEnd( &xSuspendedTaskList, \
&( pxTCB->xStateListItem ) ); //---------------------(4)
}
taskEXIT_CRITICAL();
if( xSchedulerRunning != pdFALSE )
{
taskENTER_CRITICAL();
{
prvResetNextTaskUnblockTime(); //---------------------(5)
}
taskEXIT_CRITICAL();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
if( pxTCB == pxCurrentTCB )
{
if( xSchedulerRunning != pdFALSE )
{
configASSERT( uxSchedulerSuspended == 0 );
portYIELD_WITHIN_API(); //---------------------(6)
}
else
{
if( listCURRENT_LIST_LENGTH( &xSuspendedTaskList ) \
== uxCurrentNumberOfTasks ) //---------------------(7)
{
pxCurrentTCB = NULL; //---------------------(8)
}
else
{
vTaskSwitchContext(); //---------------------(9)
}
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
(1)获取挂起的任务控制块,如果删除自己,则返回NULL;
(2)将任务从就绪列表删除;
(3)查看任务是否在等待某个事件,如果是,就从相应的等待列表中删除;
(4)将任务添加到挂起列表尾部;
(5)调整下一个任务要解锁的时间;
(6)挂起的是正在运行的任务,且调度器开启,就进行任务切换;
(7)挂起的是正在运行的任务,由于调度器没开启,不能进行任务切换,listCURRENT_LIST_LENGTH()函数检查是否所有任务都挂起。
(8)所有任务挂起,pxCurrentTCB == NULL;
(9)有任务没有被挂起 ,获取下一个任务。
四、任务解挂
任务恢复函数有普通版和中断版,本节将介绍普通版,具体内容如下:
void vTaskResume( TaskHandle_t xTaskToResume )
{
TCB_t * const pxTCB = ( TCB_t * ) xTaskToResume; //-------------------(1)
configASSERT( xTaskToResume );
if( ( pxTCB != NULL ) && ( pxTCB != pxCurrentTCB ) ) //-------------------(2)
{
taskENTER_CRITICAL(); //-------------------(3)
{
if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE ) //-------------------(4)
{
traceTASK_RESUME( pxTCB );
( void ) uxListRemove( &( pxTCB->xStateListItem ) ); //-------------------(5)
prvAddTaskToReadyList( pxTCB ); //-------------------(6)
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ) //-------------------(7)
{
taskYIELD_IF_USING_PREEMPTION(); //-------------------(8)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL(); //-------------------(9)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
(1)获取解挂的任务控制块;
(2)任务控制块不能为空,也不能是正在运行的任务;
(3)进入临界区;
(4)判断要恢复的任务是否已经挂起;
(5)将要恢复的任务从挂起列表移除;
(6)恢复的任务添加到就绪列表;
(7)、(8)恢复任务的优先级高于正在运行任务的优先级,进行任务切换;
(9)退出临界区。