FreeRTOS笔记(四):任务创建/删除,挂起/解挂详解

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。

初始化完后,堆栈情况如下图

FreeRTOS笔记(四):任务创建/删除,挂起/解挂详解_第1张图片

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)退出临界区。

你可能感兴趣的:(FreeRTOS笔记(四):任务创建/删除,挂起/解挂详解)