FreeRTOS內核源碼解讀之-------任務創建

任務創建函數--------xTaskCreate(動態方法)

  • FreeRTOS中任務控制塊詳解
  • FreeRTOS任務創建和刪除的動態和靜態方法區別
  • FreeRTOS動態創建和刪除任務
  • FreeRTOS靜態創建

1、FreeRTOS中任務控制塊詳解

任務控制塊是將每個任務的屬性集中到一個結構體中,這個結構體叫做任務控制塊:TCB_t。它就是一個任務的身份證,以後系統對任務的任何操作,都是通過這個任務控制塊來實現的。具體代碼:

typedef struct tskTaskControlBlock
{
   //任務棧頂指針,必須設置爲任務控制塊的第一個成員變量
   volatile StackType_t	*pxTopOfStack;	
   //	啓用MPU的情況下,設置。MPU內存保護單元
   #if ( portUSING_MPU_WRAPPERS == 1 )
   	//設置任務訪問的內存權限
   	xMPU_SETTINGS	xMPUSettings;		
   #endif
   //狀態鏈表項,任務會處於不同的狀態,該項會被插入到對應的鏈表,供鏈表引用	任務
   ListItem_t			xStateListItem;	 
   //事件鏈表項,
   ListItem_t			xEventListItem;		/*< Used to reference a task from an event list. */
   //任務優先級
   UBaseType_t			uxPriority;			
   //任務棧內存起始地址
   StackType_t			*pxStack;		
   //任務名字,字符串形式,一般用於調試	,configMAX_TASK_NAME_LEN表示任務名字最長值
   char				pcTaskName[ configMAX_TASK_NAME_LEN ];
   //棧生長方向,對於向上生長的棧,用於指明棧的上邊界,用於判斷是否溢出
   #if ( portSTACK_GROWTH > 0 )
   	StackType_t		*pxEndOfStack;		
   #endif
   //保存臨界區嵌套深度
   #if ( portCRITICAL_NESTING_IN_TCB == 1 )
   	UBaseType_t		uxCriticalNesting;	
   #endif

   #if ( configUSE_TRACE_FACILITY == 1 )
   	//用於調試,表示本任務是第幾個創建,每創建一個任務,系統有一個全局變量加1,並將該值賦給新任務
   	UBaseType_t		uxTCBNumber;		
   	//調試用,用戶可以設定特定的數值
   	UBaseType_t		uxTaskNumber;		
   #endif

   #if ( configUSE_MUTEXES == 1 )
   	//
   	UBaseType_t		uxBasePriority;		
   	UBaseType_t		uxMutexesHeld;
   #endif

   #if ( configUSE_APPLICATION_TASK_TAG == 1 )
   	TaskHookFunction_t pxTaskTag;
   #endif

   #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
   	void *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
   #endif

   #if( configGENERATE_RUN_TIME_STATS == 1 )
   	uint32_t		ulRunTimeCounter;	/*< Stores the amount of time the task has spent in the Running state. */
   #endif

   #if ( configUSE_NEWLIB_REENTRANT == 1 )
   	/* Allocate a Newlib reent structure that is specific to this task.
   	Note Newlib support has been included by popular demand, but is not
   	used by the FreeRTOS maintainers themselves.  FreeRTOS is not
   	responsible for resulting newlib operation.  User must be familiar with
   	newlib and must provide system-wide implementations of the necessary
   	stubs. Be warned that (at the time of writing) the current newlib design
   	implements a system-wide malloc() that must be provided with locks. */
   	struct	_reent xNewLib_reent;
   #endif

   #if( configUSE_TASK_NOTIFICATIONS == 1 )
   	volatile uint32_t ulNotifiedValue;
   	volatile uint8_t ucNotifyState;
   #endif

   /* See the comments above the definition of
   tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */
   #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
   	uint8_t	ucStaticallyAllocated; 		/*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */
   #endif

   #if( INCLUDE_xTaskAbortDelay == 1 )
   	uint8_t ucDelayAborted;
   #endif

} tskTCB;
typedef tskTCB TCB_t;

2、FreeRTOS任務創建和刪除的動態和靜態方法區別

在FreeRTOS實時操作系統中,提供了兩種任務創建和刪除函數的方法,一種是動態方式,對應的API函數是xTaskCreate和xTaskDelete;另一種是靜態方式,對應的函數是xTaskCreateStatic和xTaskCreateRestricted函數。
兩種方法的區別如下:
<1>在使用上,使用動態方式創建時,每個任務的任務堆棧需要從FreeRTOS的堆中自動申請,這時,FreeRTOS必須提供內存管理文件,在FreeRTOS提供了5中內存管理方式,後續會進行剖析;如果使用靜態方式創建和刪除任務,需要用戶自己爲每個任務指定任務堆棧。
<2>還有需要提醒的是,如果我們使用動態方式創建的任務,必須使用動態方式刪除任務;使用靜態方式創建任務,必須使用靜態方式刪除任務。
注: 相關代碼
對於選擇靜態還是動態方式創建和刪除任務,在FreeRTOS.h配置文件中可以指定,代碼如下:

#ifndef configSUPPORT_STATIC_ALLOCATION
	/* Defaults to 0 for backward compatibility. */
	#define configSUPPORT_STATIC_ALLOCATION 0
#endif
#ifndef configSUPPORT_DYNAMIC_ALLOCATION
	/* Defaults to 1 for backward compatibility. */
	#define configSUPPORT_DYNAMIC_ALLOCATION 1
#endif

3、FreeRTOS動態創建和刪除任務

  • a、函數原型
    BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
    const char * const pcName,
    const uint16_t usStackDepth,
    void * const pvParameters,
    UBaseType_t uxPriority,
    TaskHandle_t * const pxCreatedTask )
    {
    參數:
    1>pxTaskCode:具體創建任務函數名
    2>pcName:任務名字。在調試跟蹤打印信息時會出現,注意在給任務取名字時,要符合系統名字長度限制,具體可以查看FreeRTOSConfig.h文件中的配置,在這裏,我設置的是:
#define configMAX_TASK_NAME_LEN                 16

3>usStackDepth:任務堆棧大小。申請此空間是用來保存任務切換時的任務信息,任務函數申請的變量。
4>pvParameters:傳遞給任務函數的參數。
5>uxPriority:任務優先級。這裏優先級設置返回是0~configMAX_PRIORITIES-1。其中,configMAX_PRIORITIES設置同樣可以通過FreeRTOSConfig.h文件中查看到,這裏設置爲:

#define configMAX_PRIORITIES                    4

6>pxCreatedTask:任務句柄。當任務創建成功之後會返回一個任務句柄,該參數是用來保存該任務句柄的,這個任務句柄就是任務的堆棧。在其他API函數對任務進行操作時,一般都會用到這個任務句柄。
注: 對上述參數類型具體解釋

typedef void (*TaskFunction_t)( void * );     //定義一種返回值爲空,參數爲空指針的函數類型
typedef unsigned long UBaseType_t;
typedef void * TaskHandle_t;
  • xTaskCreate函數解讀

在這裏需要說明的是:第一步,爲防止堆棧增長到TCB,判斷棧(portSTACK_GROWTH)的生長方向,如果向上增長,則先申請TCB,在申請堆棧;如果向下增長,則先申請堆棧,在申請TCB。對於FreeRTOS內存管理方面的內容,我會在後續寫一篇文章,詳細講述。
動態函數創建流程圖:
在這裏插入圖片描述

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )

	BaseType_t xTaskCreate(	TaskFunction_t pxTaskCode,
							const char * const pcName,
							const uint16_t usStackDepth,
							void * const pvParameters,
							UBaseType_t uxPriority,
							TaskHandle_t * const pxCreatedTask ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
	{
	TCB_t *pxNewTCB;//任務控制塊
	BaseType_t xReturn;

		/* If the stack grows down then allocate the stack then the TCB so the stack
		does not grow into the TCB.  Likewise if the stack grows up then allocate
		the TCB then the stack. */
		//portSTACK_GROWTH  ( -1 ) 表示棧的生長方向,對於ARM CM4是向下增長的
		#if( portSTACK_GROWTH > 0 )     // if stack grows up
		{
			/* Allocate space for the TCB.  Where the memory comes from depends on
			the implementation of the port malloc function and whether or not static
			allocation is being used. */
			pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );

			if( pxNewTCB != NULL )
			{
				/* Allocate space for the stack used by the task being created.
				The base of the stack memory stored in the TCB so the task can
				be deleted later if required. */
				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 )
				{
					/* Could not allocate the stack.  Delete the allocated TCB. */
					vPortFree( pxNewTCB );
					pxNewTCB = NULL;
				}
			}
		}
		#else /* portSTACK_GROWTH */ // Stack grows DOWN on M4F
		{
		StackType_t *pxStack;

			/* 每個任務申請棧空間,這裏注意參數usStackDepth 不代表的是字節數,而是字數,對於32位
			的系統,棧空間的字節數位usStackDepth *4字節
			#define portSTACK_TYPE	uint32_t
			typedef portSTACK_TYPE StackType_t;
			*/
			pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */

			if( pxStack != NULL )//棧空間申請成功
			{
				/* 爲任務申請任務控制塊 */
				pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); 

				if( pxNewTCB != NULL )
				{
					/* 任務控制塊申請成功,初始化任務控制塊中的成員變量任務堆棧指針爲
					前面申請的任務堆棧地址 */
					pxNewTCB->pxStack = pxStack;
				}
				else
				{
					/* 任務控制塊申請失敗,釋放之前申請的任務堆棧 */
					vPortFree( pxStack );
				}
			}
			else
			{
				/* 任務棧空間申請失敗,將任務控制塊句柄設置爲空 */
				pxNewTCB = NULL;
			}     
		}
		#endif /* portSTACK_GROWTH */

		if( pxNewTCB != NULL )
		{
			#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
			{
				/* 表明任務控制塊和任務棧佔用的是heap還是stack,提供給任務會收時判斷 */
				pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
			}
			#endif /* configSUPPORT_STATIC_ALLOCATION */
			/* 初始化任務控制塊 */
			prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
			/* 將任務添加到就緒鏈表中 */
			prvAddNewTaskToReadyList( pxNewTCB );
			xReturn = pdPASS;
		}
		else
		{
			/* 創建任務失敗,返回錯誤碼 */
			xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
		}

		return xReturn;
	}

#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
  • a、函數原型
    void vTaskDelete( TaskHandle_t xTaskToDelete )
    參數:
    1>xTaskToDelete :xTaskCreate任務創建函數創建成功後返回的任務句柄,如果是刪除自己,需要的參數爲NULL。這裏要注意:**如果下個任務的解鎖,剛好是被刪除的那個任務,那麼變量NextTaskUnblockTime就不對了,所以要重新從延時列表中獲取一下。它是從延時列表的頭部來獲取的任務TCB,也可以再次驗證,延時任務列表是按延時時間排序的。如果延時列表是空的,直接給默認值MAX_DELAY賦給NextTaskUnblockTime. **
    在這裏插入圖片描述
  • vTaskDelete函數解讀
#if ( INCLUDE_vTaskDelete == 1 )

	void vTaskDelete( TaskHandle_t xTaskToDelete )
	{
	TCB_t *pxTCB;

		taskENTER_CRITICAL();//關閉中斷,臨界區代碼保護
		{
			/* 傳遞參數爲NULL,表示某個任務要刪除自己 */
			pxTCB = prvGetTCBFromHandle( xTaskToDelete );//通過任務句柄求出任務控制塊,在FreeRTOS中任務句柄就是任務控制塊的地址

			/* 將任務移除就緒列表 */
			if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
			{//狀態列表項爲0
				taskRESET_READY_PRIORITY( pxTCB->uxPriority );//判斷任務同等優先級的任務是否都刪除,如果都刪除,該優先級對應的位設置爲0.這裏主要考慮的是FreeRTOS支持任務優先級相同的時間片輪轉調度的問題
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}

			/* 該任務是否等待某一事件,則從事件列表中移除 */
			if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
			{
				( void ) uxListRemove( &( pxTCB->xEventListItem ) );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
			uxTaskNumber++;//主要用於可視化跟蹤調試,如果configUSE_TRACE_FACILITY沒有開啓,uxTaskNumber不起任何作用
	        
			if( pxTCB == pxCurrentTCB )//當前任務刪除自己
			{
				/* 任務不能夠刪除自己,需要進行上下文切換,進入另一個任務。將任務從相應的工作鏈表中去下,放入xTasksWaitingTermination鏈表中,相應的在空閒任務中會刪除該鏈表中的任務 */
				vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );

				/* 記錄空閒任務需要刪除的 xTasksWaitingTermination列表項*/
				++uxDeletedTasksWaitingCleanUp;

				/* 刪除任務鉤子函數,用戶看可以自定義該鉤子函數的內容 */
				portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
			}
			else//如果刪除的任務不是自己
			{
				--uxCurrentNumberOfTasks;//任務數減1
				prvDeleteTCB( pxTCB );//刪除任務

				/* 重新計算下一個任務解除阻塞的時間,也就是下一個任務需要開始運行的時間 */
				prvResetNextTaskUnblockTime();
			}

			traceTASK_DELETE( pxTCB );//用戶自己定義,用於調試追蹤
		}
		taskEXIT_CRITICAL();//退出臨界代碼保護

		/* 判斷調度器是否正在運行. */
		if( xSchedulerRunning != pdFALSE )//xSchedulerRunning用來指示調度器是否正在運行
		{
			if( pxTCB == pxCurrentTCB )//刪除的是任務自己,由於是刪除自己,因此需要進行一次任務切換
			{
				configASSERT( uxSchedulerSuspended == 0 );
				portYIELD_WITHIN_API();
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
	}

#endif /* INCLUDE_vTaskDelete */

4、FreeRTOS靜態創建

configSUPPORT_STATIC_ALLOCATION設置爲1將會開啓靜態創建任務功能。
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer )
參數:
1>pxTaskCode:具體創建任務執行函數名
2>pcName:任務名字,主要用於調試
3>ulStackDepth:堆棧深度
4>pvParameters:傳給任務執行函數的參數
5>uxPriority:任務優先級
6>puxStackBuffer:任務堆棧空間首地址
7>pxTaskBuffer:任務控制塊首地址
需要說明的是:對於靜態創建任務,用戶需要自己聲明任務堆棧和控制塊,一般是申請一個全局的數組和TCB_t 的變量。

在這裏插入圖片描述

/*-----------------------------------------------------------*/

#if( configSUPPORT_STATIC_ALLOCATION == 1 )//靜態任務創建開關

	TaskHandle_t xTaskCreateStatic(	TaskFunction_t pxTaskCode,
									const char * const pcName,
									const uint32_t ulStackDepth,
									void * const pvParameters,
									UBaseType_t uxPriority,
									StackType_t * const puxStackBuffer,
									StaticTask_t * const pxTaskBuffer ) 
	{
	TCB_t *pxNewTCB;
	TaskHandle_t xReturn;

		configASSERT( puxStackBuffer != NULL );
		configASSERT( pxTaskBuffer != NULL );

		if( ( pxTaskBuffer != NULL ) && ( puxStackBuffer != NULL ) )
		{
			
			pxNewTCB = ( TCB_t * ) pxTaskBuffer; /*將pxTaskBuffer指針傳過來的指針強制轉換成任務控制塊指針.pxTaskBuffer是用戶自己申請的 */
			pxNewTCB->pxStack = ( StackType_t * ) puxStackBuffer;//將puxStackBuffer強制轉換成StackType_t *賦給任務控制塊中指向任務堆棧的成員變量,這也是用戶自己申請的

			#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
			{
				/* 標識這個任務控制塊和棧內存時靜態創建的,刪除任務的時候, 系統不會做內存回收處理*/
				pxNewTCB->ucStaticallyAllocated = tskSTATICALLY_ALLOCATED_STACK_AND_TCB;
			}
			#endif /* configSUPPORT_DYNAMIC_ALLOCATION */

			prvInitialiseNewTask( pxTaskCode, pcName, ulStackDepth, pvParameters, uxPriority, &xReturn, pxNewTCB, NULL );//新任務初始化函數
			prvAddNewTaskToReadyList( pxNewTCB );//將任務加入到就緒列表
		}
		else
		{
			xReturn = NULL;
		}

		return xReturn;
	}

#endif /* SUPPORT_STATIC_ALLOCATION */

靜態創建用的很少。本博客寫完,有什麼錯誤地方請大家指正,謝謝。下一篇將會寫FreeRTOS關於內存管理方面的內容。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章