1. 程式人生 > >freertos之task淺析

freertos之task淺析

前言

rtos排程的基本單位是task(任務),其重要性不言而喻,一般都會包括任務的建立,刪除,阻塞,掛起,回覆等等操作。當然,freertos也不例外。
一般一個task包含三個基礎部分TCB結構、stack結構、任務程式碼。
下面就從這幾方面來講一講



task有關的資料結構


TCB結構體

typedef struct tskTaskControlBlock
{
	volatile StackType_t	*pxTopOfStack;	//任務棧頂指標PSP,在任務切換的時候至關重要,通過TCB結構的棧頂指標來找到具體任務程式碼
	ListItem_t			xStateListItem;	//任務狀態列表項
	ListItem_t			xEventListItem;	//任務事件列表項
	UBaseType_t			uxPriority;		//任務優先順序
	StackType_t			*pxStack;		//任務棧底
	char				pcTaskName[ configMAX_TASK_NAME_LEN ];	//任務名稱
	#if ( configUSE_TRACE_FACILITY == 1 )	//啟用任務跟蹤功能
		UBaseType_t		uxTCBNumber;		
		UBaseType_t		uxTaskNumber;
	#endif

	#if ( configUSE_MUTEXES == 1 )			//使能mutex功能
		UBaseType_t		uxBasePriority;	
		UBaseType_t		uxMutexesHeld;
	#endif

	#if( configUSE_TASK_NOTIFICATIONS == 1 )	//使能任務通知功能
		volatile uint32_t ulNotifiedValue;
		volatile uint8_t ucNotifyState;
	#endif

	#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )	//判斷TCb結構的記憶體能使用方式
		uint8_t	ucStaticallyAllocated; 		
	#endif

	#if( INCLUDE_xTaskAbortDelay == 1 )	//使用任務放棄延時功能
		uint8_t ucDelayAborted;
	#endif
} tskTCB;
typedef tskTCB TCB_t;

一些跟任務相關的全域性變數

PRIVILEGED_DATA TCB_t * volatile pxCurrentTCB = NULL;	//指向當前任務TCB結構體的指標
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];	//列表陣列表示已經就緒的任務,因為每一個優先順序都有一個就緒列表,所以是個列表陣列;
PRIVILEGED_DATA static List_t xDelayedTaskList1;	//延時任務列表1				
PRIVILEGED_DATA static List_t xDelayedTaskList2;	//延時任務列表2	,使用兩個延時任務列表的原因主要是解決tick溢位問題的,下面程式碼具體分析				
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;	//因為兩個延時任務列表,這個指標是指向當前被使用的那個列表的	
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;	//這個指標是指向溢位的那個延時列表的		
/*< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t xPendingReadyList;	//當排程器上鎖之後,就緒的task就會加入到此列表中來,在排程器恢復之後,就會轉到就緒態中去			
#if( INCLUDE_vTaskDelete == 1 )
	PRIVILEGED_DATA static List_t xTasksWaitingTermination;	//在任務刪除自己的時候由於不能立刻釋放自己TCB 和stack所佔用的記憶體(前提是動態分配,而且是任務自己刪除自己),就會將自己新增到這個列表中去,等待空閒任務來遍歷這個列表來釋放動態ram
	PRIVILEGED_DATA static volatile UBaseType_t uxDeletedTasksWaitingCleanUp = ( UBaseType_t ) 0U;	//這個如果為01的話,空閒任務就會忽略去遍歷上面這個列表,只有部位0的情況下才會去釋放動態記憶體。
#endif

#if ( INCLUDE_vTaskSuspend == 1 )
	PRIVILEGED_DATA static List_t xSuspendedTaskList;			//被阻塞的任務列表
#endif

PRIVILEGED_DATA static volatile UBaseType_t uxCurrentNumberOfTasks 	= ( UBaseType_t ) 0U;	//當前任務總數
PRIVILEGED_DATA static volatile TickType_t xTickCount 				= ( TickType_t ) 0U;	//當前tick時間
PRIVILEGED_DATA static volatile UBaseType_t uxTopReadyPriority 		= tskIDLE_PRIORITY;	//空閒任務優先順序
PRIVILEGED_DATA static volatile BaseType_t xSchedulerRunning 		= pdFALSE;	//排程器執行狀態
PRIVILEGED_DATA static volatile UBaseType_t uxPendedTicks 			= ( UBaseType_t ) 0U;	//suspend期間發生的tick數
PRIVILEGED_DATA static volatile BaseType_t xYieldPending 			= pdFALSE;	//
PRIVILEGED_DATA static volatile BaseType_t xNumOfOverflows 			= ( BaseType_t ) 0;	
PRIVILEGED_DATA static UBaseType_t uxTaskNumber 					= ( UBaseType_t ) 0U;	
PRIVILEGED_DATA static volatile TickType_t xNextTaskUnblockTime		= ( TickType_t ) 0U; 	
/* Initialised to portMAX_DELAY before the scheduler starts. */
PRIVILEGED_DATA static TaskHandle_t xIdleTaskHandle					= NULL;			
/*< Holds the handle of the idle task.  The idle task is created


task相關程式碼分析


任務建立

rtos中資源使用排程的最小單位一般就是task了,先看任務建立程式碼。分為兩種,一種是靜態static建立task(任務stack,tcb都是提前靜態申請好),另一種是動態建立(任務stack,tcb都是建立的時候申請),其中tcb結構中的ucStaticallyAllocated元素就是記錄此tcb是用那種方式建立的。看程式碼

/*這是靜態任務建立方式,在建立任務之前需要將tcb、stack建立好,地址作為引數傳入,裡面主要是
對pxNewTCB、pxStack 賦值,然後呼叫prvInitialiseNewTask初始化TCB結構,
最後呼叫prvAddNewTaskToReadyList將task加入到readylist中去*/
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;
	if( ( pxTaskBuffer != NULL ) && ( puxStackBuffer != NULL ) ){
		pxNewTCB = ( TCB_t * ) pxTaskBuffer; /*lint !e740 Unusual cast is ok as the structures are designed to have the same alignment, and the size is checked by an assert. */
		pxNewTCB->pxStack = ( StackType_t * ) puxStackBuffer;
		#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ){
			pxNewTCB->ucStaticallyAllocated = tskSTATICALLY_ALLOCATED_STACK_AND_TCB;
		}
		#endif 

		prvInitialiseNewTask( pxTaskCode, pcName, ulStackDepth, pvParameters, uxPriority, &xReturn, pxNewTCB, NULL );
		prvAddNewTaskToReadyList( pxNewTCB );
	}else{
		xReturn = NULL;
	}
	return xReturn;
}

/*這是動態任務建立方式,裡面先動態分配tcb、stack結構的ram,
然後呼叫prvInitialiseNewTask初始化TCB結構,最後呼叫prvAddNewTaskToReadyList將task加入到readylist中去*/
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;

	/* 如果棧是向下生長的,那先分配stack,然後分配TCB,
	這樣就算stack溢位,也不會將TCb結構體覆蓋掉 。。。這個地方程式碼實現我認為是有問題的*/
	/* 給TCb分配記憶體,記憶體來源取決於malloc function  的實現*/
	pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
	if( pxNewTCB != NULL ){
		pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); 
		if( pxNewTCB->pxStack == NULL ){
			vPortFree( pxNewTCB );
			pxNewTCB = NULL;
		}
	}
	if( pxNewTCB != NULL ){
		prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, 
							pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
		prvAddNewTaskToReadyList( pxNewTCB );
		xReturn = pdPASS;
	}else{
		xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
	}

	return xReturn;
}

其中都呼叫了prvInitialiseNewTaskprvAddNewTaskToReadyList,下面來看這兩個函式原始碼

/*這個函式主要是儲存task名稱,設定task的優先順序,在設定task的tcb的xStateListItem、xEventListItem兩個列表項
然後初始化任務棧(涉及到任務切換,硬體架構),最後將建立的task得到控制代碼傳遞出來*/
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( portSTACK_GROWTH < 0 ){
		pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
		pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); 
		/* 檢查位元組對齊操作,一般情況下都是要做4位元組或者8位元組對齊的*/
		configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
	}
	#endif /* portSTACK_GROWTH */

	/* 儲存task名字 */
	for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ ){
		pxNewTCB->pcTaskName[ x ] = pcName[ x ];
		/*不要複製整個字串,防止字串之後的ram無法訪問,可能會造成錯誤*/
		if( pcName[ x ] == 0x00 ){
			break;
		}else{
			mtCOVERAGE_TEST_MARKER();
		}
	}

	/* 保證任務名字有結尾。萬一任務名字跟名字陣列一樣長  或者 比名字陣列更長 */
	pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';

	/* 保證任務優先順序在系統設定範圍內 */
	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 ){//使用mutex的話,額外需要記錄的資料
		pxNewTCB->uxBasePriority = uxPriority;
		pxNewTCB->uxMutexesHeld = 0;
	}
	#endif /* configUSE_MUTEXES */

	/*初始化tcb的xStateListItem 和 xEventListItem列表項 以便下面將他們插入到對應的list中去*/
	vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
	vListInitialiseItem( &( pxNewTCB->xEventListItem ) );

	/* 設定列表項的owner */
	listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );

	/* 事件列表一般是按照優先順序排列的*/
	listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
	listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );

	/*初始化TCB堆疊,使其看起來好像任務已經在執行,但是被排程程式打斷了。
	返回地址已設定到任務函式的開頭。一旦堆疊被初始化堆疊變數的頂部被更新。 
	這部分跟任務切換密切相關,之後應該還有一篇系列部落格來分析*/
	pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );

	if( ( void * ) pxCreatedTask != NULL ){
		/* 匿名傳遞任務控制代碼,通過這個控制代碼(其實就是指向tcb的指標)可以對任務進行一些了操作*/
		*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
	}else{
		mtCOVERAGE_TEST_MARKER();
	}
}

/*這個函式將NEW的task加入到就緒列表中去分為好幾種情況
1.如果現在沒有任何任務執行,那麼這個任務機會被立即執行(其實也就是pxCurrentTCB 指向NEW的tcb),需要prvInitialiseTaskLists去初始化各個全域性列表變數
2.如果這不是系統中第一個任務,但是排程器還沒執行,就看pxCurrentTCB 和 pxNewTCB的優先順序,如果pxNewTCB優先順序高的話,就會替代原來的TCB
3.如果不是系統中第一個任務,系統排程器已經開始運行了的話,那就看現在執行的task的pxCurrentTCB 和 pxNewTCB 的優先順序那個高,如果pxNewTCB的優先順序高,那麼就會直接導致任務切換,注意case1 \ 2 不會直接導致任務切換,僅僅是pxCurrentTCB 指標的指向發生變化而已。

這裡不管是case幾,都會呼叫prvAddTaskToReadyList將task加入到readylist中去*/
static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
{
	/* 確保在更新任務列表時中斷不會訪問任務列表。 */
	taskENTER_CRITICAL();{
		uxCurrentNumberOfTasks++;
		if( pxCurrentTCB == NULL ){
		/* 沒有其他task,或者其他task都是處於暫停、掛起態的時候,直接 讓這個NEW任務去執行*/
			pxCurrentTCB = pxNewTCB;
			if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ){
				/* 這是要建立的第一個任務,初步任務也是如此需要初始化。
				如果這個呼叫失敗,我們將無法恢復,但我們會報告失敗。*/
				prvInitialiseTaskLists();
			}else{
				mtCOVERAGE_TEST_MARKER();
			}
		}else{//如果這不是系統中第一個任務
		/* 如果排程器尚未執行,則將此任務設定為當前任務,
		如果它是到目前為止要建立的最高優先順序任務。*/
			if( xSchedulerRunning == pdFALSE ){
				if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority ){
					pxCurrentTCB = pxNewTCB;
				}else{
					mtCOVERAGE_TEST_MARKER();
				}
			}else{
				mtCOVERAGE_TEST_MARKER();
			}
		}

		uxTaskNumber++;
		prvAddTaskToReadyList( pxNewTCB );
		portSETUP_TCB( pxNewTCB );
	}
	taskEXIT_CRITICAL();

	if( xSchedulerRunning != pdFALSE ){//如果系統正在執行
	/* 如果建立的任務的優先順序  比  現在執行的任務優先順序       高 ,他應該立即執行  */
		if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority ){
			taskYIELD_IF_USING_PREEMPTION();
		}else{
			mtCOVERAGE_TEST_MARKER();
		}
	}else{
		mtCOVERAGE_TEST_MARKER();
	}
}

prvAddNewTaskToReadyList又涉及到prvAddTaskToReadyList這個巨集,如下
先是更新已經就緒的最高優先順序任務,然後將此task插入到對應優先順序列表的readylist的列表尾部,主要是實現時間片輪轉排程

#define prvAddTaskToReadyList( pxTCB )													\
	taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority );								\
	vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); 	\


任務刪除

有任務建立就有任務刪除,不僅動態任務可以刪除,靜態任務也可以刪除,動態任務刪除的更徹底,連TCB、stack都會刪掉,靜態任務在刪除的時候由於TCB、stack都是靜態分配的無法刪除。
下面看原始碼

/*任務刪除函式,先將兩個列表項從可能掛接的列表上移除,然後分兩種情況
1.刪除的任務是本身的話,刪除TCB、stack的操作需要在空閒任務中去完成
2.刪除的是別的任務的話,prvDeleteTCB就會將任務tcb、stack刪除
最後如果是刪除任務本身的話,由於本身已經被刪除了,需要立即進行任務切換。*/
void vTaskDelete( TaskHandle_t xTaskToDelete )
{
TCB_t *pxTCB;

	taskENTER_CRITICAL();{
		/* 如果傳入的xTaskToDelete是NULL,那就是刪除當前任務,pxTCB = pxCurrentTCB,
		否則 pxTCB = xTaskToDelete*/
		pxTCB = prvGetTCBFromHandle( xTaskToDelete );

		/* 由於任務即將被刪除了,需要將其從任務狀態列表中刪除,如果刪除
		這個任務之後,這個列表中的列表項個數為0了,那就看你需要修改已經就緒的優先順序了 */
		if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
			taskRESET_READY_PRIORITY( pxTCB->uxPriority );

		/* 由於任務即將被刪除了,也需要將在其他等待列表中的列表項移除了(前提是有掛在別的事件列表上) */
		if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
			( void ) uxListRemove( &( pxTCB->xEventListItem ) );

		/* 增加uxTaskNumber,這樣核心感知偵錯程式也可以檢測任務列表是否需要重新生成。
		這是之前做過的portPRE_TASK_DELETE_HOOK()與巨集將在Windows埠中使用的方法相同
		沒有回來。*/
		uxTaskNumber++;//其實這個操作我也沒搞懂。。。。。

		if( pxTCB == pxCurrentTCB ){//任務刪除自己
		/* 一個任務正在刪除它自己。這不能在任務本身中完成,作為上下文切換到另一個任務是必需的。
		將任務放在終止列表xTasksWaitingTermination中。
		空閒任務將檢查終止列表並釋放所分配的任何記憶體已刪除任務的TCB和堆疊的排程器。*/
			vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );

		/*增加uxDeletedTasksWaitingCleanUp變數,以便空閒任務知道有一個任務已經被刪除,
		因此它應該被刪除檢查xTasksWaitingTermination列表。這在前面講全域性變數的時候提到過*/
			++uxDeletedTasksWaitingCleanUp;

		/*預刪除鉤子主要用於Windows模擬器,在其中執行Windows特定的清理操作,
		在此之後,就不可能放棄這項任務了因此,xYieldPending用於鎖定上下文切換必需的。*/
			portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
		}else{//一個任務刪除另一個任務
			--uxCurrentNumberOfTasks;	//全域性變數減少,
			prvDeleteTCB( pxTCB );		//真正刪除TCB、Stack的函式,會根據是否動態分配來決定
			/* 重置下一個預期的解鎖時間,剛剛刪除的任務就是下一個解鎖的task。*/
			prvResetNextTaskUnblockTime();
		}
	}
	taskEXIT_CRITICAL();
	/* Force a reschedule if it is the currently running task that has just been deleted. */
	if( xSchedulerRunning != pdFALSE ){//如果排程器在執行 且 刪除的任務是當前任務
		if( pxTCB == pxCurrentTCB )
			portYIELD_WITHIN_API();		//那麼久了進行任務切換,因為pxCurrentTCB將要被刪除了
	}
}

其中呼叫了prvDeleteTCBprvResetNextTaskUnblockTime,來看原始碼

/*實際刪除tcb、stack的函式。需要根據ram的申請方式來決定刪除操作,其實很簡單。。。*/
static void prvDeleteTCB( TCB_t *pxTCB ){
	#if( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 0 ) && ( portUSING_MPU_WRAPPERS == 0 ) ){
		/* The task can only have been allocated dynamically - free both
		the stack and TCB. */
		vPortFree( pxTCB->pxStack );
		vPortFree( pxTCB );
	}
	#elif( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE == 1 ){
		/* The task could have been allocated statically or dynamically, so
		check what was statically allocated before trying to free the
		memory. */
		if( pxTCB->ucStaticallyAllocated == tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB ){
			/* Both the stack and TCB were allocated dynamically, so both
			must be freed. */
			vPortFree( pxTCB->pxStack );
			vPortFree( pxTCB );
		}else if( pxTCB->ucStaticallyAllocated == tskSTATICALLY_ALLOCATED_STACK_ONLY ){
			/* Only the stack was statically allocated, so the TCB is theonly memory that must be freed. */
			vPortFree( pxTCB );
		}else{
			/* Neither the stack nor the TCB were allocated dynamically, so
			nothing needs to be freed. */
			configASSERT( pxTCB->ucStaticallyAllocated == tskSTATICALLY_ALLOCATED_STACK_AND_TCB	)
			mtCOVERAGE_TEST_MARKER();
		}
	}
	#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
}

/*主要是更新預期unblock tine,不然每次tick都去嘗試去查delaylist看有沒有任務時間到期很麻煩,佔時間*/
static void prvResetNextTaskUnblockTime( void ){
TCB_t *pxTCB;
	if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE ){
		/* 新的當前延遲列表為空。xNextTaskUnblockTime設定為的最大可能值,所以它是非常不可能的
		if(xTickCount >= xNextTaskUnblockTime)測試將通過,直到延遲列表中有一項。*/
		xNextTaskUnblockTime = portMAX_DELAY;
	}else{
		/* 當前新延遲列表不為空,獲取的值為在延遲列表頂部的專案。這是時間
		應該刪除延遲列表頂部的哪個任務從阻塞狀態。都是列表操作*/
		( pxTCB ) = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
		xNextTaskUnblockTime = listGET_LIST_ITEM_VALUE( &( ( pxTCB )->xStateListItem ) );
	}
}


任務延遲

在裸機程式設計中,我們一般延時是讓CPU空轉的,這樣使得資源極大的浪費,在OS中基本上決不允許這種情況的出現,所以OS提供了兩個函式來供使用者在OS下使用,vTaskDelayUntilvTaskDelay。兩個函式有輕微的區別,詳細分析見程式碼

/*只需要傳入需要delay的tick數即可,將本task加入delaylist阻塞xTicksToDelay個tick*/
void vTaskDelay( const TickType_t xTicksToDelay ){
BaseType_t xAlreadyYielded = pdFALSE;
	/* 延遲tick為0的話,只會強制重新排程*/
	if( xTicksToDelay > ( TickType_t ) 0U ){
		configASSERT( uxSchedulerSuspended == 0 );
		vTaskSuspendAll();{
			traceTASK_DELAY();
			/*從事件列表中刪除的任務排程器被掛起將不會被放置在就緒狀態
			列表或從阻塞列表中刪除,直到排程程式已恢復。
			此任務不能在事件列表中,因為它是當前的執行任務。*/
			prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
		}
		xAlreadyYielded = xTaskResumeAll();
	}
	/* 如果xTaskResumeAll 沒有完成,那就進行一次強制排程,來使當前任務slepp */
	if( xAlreadyYielded == pdFALSE )
		portYIELD_WITHIN_API();
}

/*傳入上一次的喚醒時間pxPreviousWakeTime,和需要延時的xTimeIncrement 。達到精確延時,將在延時之前的一些函式執行時間也算進去了,所以可以很精確的延時,比如以特定頻率執行某一個任務就可以使用vTaskDelayUntil */
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
{
TickType_t xTimeToWake;
BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;

	vTaskSuspendAll();{//阻塞所有任務
		const TickType_t xConstTickCount = xTickCount;

		/* 計算任務想喚醒的那個tick */
		xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;

		if( xConstTickCount < *pxPreviousWakeTime )
		{//如果現在tick已經比上次喚醒的tick小了,說明肯定已經溢位過了
			/* 這部分沒看懂,之後補充 */
			if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) )
			{
				xShouldDelay = pdTRUE;
			}else{
				mtCOVERAGE_TEST_MARKER();
			}
		}else{
			/* tick還沒有溢位過。這樣的話,不論是否喚醒時間是否溢位、或者說tick比wake time小 ,
			我們都會進行延時delay*/
			if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) ){
				xShouldDelay = pdTRUE;
			}else{
				mtCOVERAGE_TEST_MARKER();
			}
		}

		/* 更新喚醒時間,方便下一次呼叫 */
		*pxPreviousWakeTime = xTimeToWake;

		if( xShouldDelay != pdFALSE )
			/* prvAddCurrentTaskToDelayedList() 需要的不是喚醒得到時間,而是阻塞的時間,減去當前tick */
			prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );
	}
	xAlreadyYielded = xTaskResumeAll();//恢復所有任務排程

	/* 如果xTaskResumeAll 沒有完成,那就進行一次強制排程,來使當前任務slepp*/
	if( xAlreadyYielded == pdFALSE )
		portYIELD_WITHIN_API();
}


任務掛起

這部分程式碼主要包括任務掛起,任務解掛等

void vTaskSuspend( TaskHandle_t xTaskToSuspend )
{
TCB_t *pxTCB;

	taskENTER_CRITICAL();{
		/* 如果傳入的是空指標,那就掛起自己*/
		pxTCB = prvGetTCBFromHandle( xTaskToSuspend );
		/* 將此任務從ready delayed的list中移除,方便後續加入到xSuspendedTaskList中去*/
		if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ){
			taskRESET_READY_PRIORITY( pxTCB->uxPriority );
		}
		/* 此任務是否也在等待什麼事件,如果是,就從事件等待列表中移除 */
		if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL ){
			( void ) uxListRemove( &( pxTCB->xEventListItem ) );
		}
		/*將此task加入到xSuspendedTaskList中去。(按照優先順序排列)*/
		vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xStateListItem ) );
	}
	taskEXIT_CRITICAL();

	if( xSchedulerRunning != pdFALSE ){
	/* 重置xNextTaskUnblockTime值,因為此被掛起的任務跟可能與xNextTaskUnblockTime的值有關係(這個函式之前已經分析過)*/
		taskENTER_CRITICAL();{
			prvResetNextTaskUnblockTime();
		}
		taskEXIT_CRITICAL();
	}

	if( pxTCB == pxCurrentTCB ){//如果阻塞的當前任務自身
		if( xSchedulerRunning != pdFALSE )
		{//且排程器在執行的話,那麼立刻出發一次任務切換
			/* The current task has just been suspended. */
			configASSERT( uxSchedulerSuspended == 0 );
			portYIELD_WITHIN_API();
		}else{//如果排程器沒有在執行的話。
			/* The scheduler is not running, but the task that was pointed
			to by pxCurrentTCB has just been suspended and pxCurrentTCB
			must be adjusted to point to a different task. */
			if( listCURRENT_LIST_LENGTH( &xSuspendedTaskList ) == uxCurrentNumberOfTasks )
			{//看看掛起佇列中的任務數,如果等於當前所有任務數的話,說明系統中已經沒有任務可運行了
			//pxCurrentTCB置為NULL。反正排程器沒有執行
				pxCurrentTCB = NULL;
			}else{//不然的話。。我也還沒看懂
				vTaskSwitchContext();
			}
		}
	}
}

void vTaskResume( TaskHandle_t xTaskToResume )
{
TCB_t * const pxTCB = ( TCB_t * ) xTaskToResume;
	/* 很容易理解為什麼引數不能是NULL,pxCurrentTCB */
	if( ( pxTCB != NULL ) && ( pxTCB != pxCurrentTCB ) ){
		taskENTER_CRITICAL();{
			if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE )
			{//檢查pxTCB是不是真的是掛起態
				/* 這段程式碼在臨界區,直接操作相關列表,從 xSuspendedTaskList上
				將此task移除,並加入到readylist中*/
				( void ) uxListRemove(  &( pxTCB->xStateListItem ) );
				prvAddTaskToReadyList( pxTCB );
				/* 有可能pxTCB的優先順序比pxCurrentTCB更高,這樣的話就需要強制進行任務切換 */
				if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ){
				
					taskYIELD_IF_USING_PREEMPTION();
				}
				
			}
		}
		taskEXIT_CRITICAL();
	}
}