FreeRTOS學習筆記——任務延時

主機環境:Windows

開發環境:MDK4.7.2

FreeRTOS版本:FreeRTOS8.1.2

目標環境:STM32F030C8T6

FreeRTOS的任務有以下幾種狀態:運行態、就緒態、阻塞態、掛起態,如下圖

其中如果任務調用了延時函數就會進入阻塞態,延時函數有兩個:vTaskDelay()和vTaskDelayUtil()前者是相對延時,後者是絕對延時,可以查看Using the FreeRTOS Real Time Kernel - a Practical Guide文檔來幫助理解。先來了解一下vTaskDelay()函數吧,代碼如下

void vTaskDelay( const TickType_t xTicksToDelay )
{
	TickType_t xTimeToWake;
	BaseType_t xAlreadyYielded = pdFALSE;

	/* A delay time of zero just forces a reschedule. */
	if( xTicksToDelay > ( TickType_t ) 0U )
	{
		configASSERT( uxSchedulerSuspended == 0 );
		vTaskSuspendAll();
		{
			traceTASK_DELAY();

			xTimeToWake = xTickCount + xTicksToDelay;

			/* We must remove ourselves from the ready list before adding
			ourselves to the blocked list as the same list item is used for
			both lists. */
			if( uxListRemove( &( pxCurrentTCB->xGenericListItem ) ) == ( UBaseType_t ) 0 )
			{
				/* The current task must be in a ready list, so there is
				no need to check, and the port reset macro can be called
				directly. */
				portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
			prvAddCurrentTaskToDelayedList( xTimeToWake );
		}
		xAlreadyYielded = xTaskResumeAll();
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}

	/* Force a reschedule if xTaskResumeAll has not already done so, we may
	have put ourselves to sleep. */
	if( xAlreadyYielded == pdFALSE )
	{
		portYIELD_WITHIN_API();
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
}
代碼還是比較精簡的,進來之後首先檢測延時時間的有效性,如果爲0的話則表明立即執行一次任務切換,一般而言都是正常值大於0的,這裏進入了vTaskSuspendAll()函數

void vTaskSuspendAll( void )
{
	/* A critical section is not required as the variable is of type
	BaseType_t.  Please read Richard Barry's reply in the following link to a
	post in the FreeRTOS support forum before reporting this as a bug! -
	http://goo.gl/wu4acr */
	++uxSchedulerSuspended;
}
這個網址沒打開,所以也還不曉得原因,這裏是把調度器掛起,在此期間任務切換是不會執行的,但不會影響中斷的響應,然後計算任務的喚醒時間,接着將任務從就緒表中移除,添加入延時鏈表中,

static void prvAddCurrentTaskToDelayedList( const TickType_t xTimeToWake )
{
	/* The list item will be inserted in wake time order. */
	listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xGenericListItem ), xTimeToWake );

	if( xTimeToWake < xTickCount )
	{
		/* Wake time has overflowed.  Place this item in the overflow list. */
		vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xGenericListItem ) );
	}
	else
	{
		/* The wake time has not overflowed, so the current block list is used. */
		vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xGenericListItem ) );

		/* If the task entering the blocked state was placed at the head of the
		list of blocked tasks then xNextTaskUnblockTime needs to be updated
		too. */
		if( xTimeToWake < xNextTaskUnblockTime )
		{
			xNextTaskUnblockTime = xTimeToWake;
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
}
根據喚醒時間是否溢出,來將任務插入到不同的延時鏈表中,如果喚醒時間沒有溢出則插入正常的延時鏈表中,如果喚醒時間溢出的話則插入延時溢出鏈表中。鏈表中的元素是按照xItemValue升序排列的,以便於我們喚醒任務時的查找。最後還要檢測是否需要更新下一任務的喚醒時間xNextTaskUnblockTime

接着喚醒調度器

BaseType_t xTaskResumeAll( void )
{
	TCB_t *pxTCB;
	BaseType_t xAlreadyYielded = pdFALSE;

	/* If uxSchedulerSuspended is zero then this function does not match a
	previous call to vTaskSuspendAll(). */
	configASSERT( uxSchedulerSuspended );

	taskENTER_CRITICAL();
	{
		--uxSchedulerSuspended;

		if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
		{
			if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U )
			{
				/* Move any readied tasks from the pending list into the
				appropriate ready list. */
				while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE )
				{
					pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) );
					( void ) uxListRemove( &( pxTCB->xEventListItem ) );
					( void ) uxListRemove( &( pxTCB->xGenericListItem ) );
					prvAddTaskToReadyList( pxTCB );

					/* If we have moved a task that has a priority higher than
					the current task then we should yield. */
					if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
					{
						xYieldPending = pdTRUE;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}

				/* If any ticks occurred while the scheduler was suspended then
				they should be processed now.  This ensures the tick count does
				not	slip, and that any delayed tasks are resumed at the correct
				time. */
				if( uxPendedTicks > ( UBaseType_t ) 0U )
				{
					while( uxPendedTicks > ( UBaseType_t ) 0U )
					{
						if( xTaskIncrementTick() != pdFALSE )
						{
							xYieldPending = pdTRUE;
						}
						else
						{
							mtCOVERAGE_TEST_MARKER();
						}
						--uxPendedTicks;
					}
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}

				if( xYieldPending == pdTRUE )
				{
					#if( configUSE_PREEMPTION != 0 )
					{
						xAlreadyYielded = pdTRUE;
					}
					#endif
					taskYIELD_IF_USING_PREEMPTION();
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	taskEXIT_CRITICAL();

	return xAlreadyYielded;
}
在喚醒調度器時根據xAlreadyYield變量來標記是否需要執行任務切換,進入臨界區後對uxSchedulerSuspended變量進行減減操作喚醒調度器,如果更新後的值爲pdFALSE則表明調度器喚醒成功,調度器喚醒時我們需要檢測是否有中斷將任務喚醒進入了懸掛就緒態,即檢測xPendingReadyList鏈表是否爲空,如果不爲空則將任務從xPendingReadyList鏈表中移除添加入就緒鏈表中,同樣也需要從事件鏈表中移除該任務(有關任務鏈表的說明還沒看到後面再看吧)。將任務喚醒後檢測其優先級是否比當前執行任務的優先級高,如果要高更新xYieldPend爲pdTRUE,需要執行一次任務切換,這裏是將掛起就緒鏈表中的所有任務都添加到就緒表中。

接着下面判斷了在調度器掛起期間是否有時間中斷髮生,當uxPendedTicks的值大於0時表明在調度器掛起期間有SysTick中斷髮生,這裏需要處理,以確保延時任務在正確的時間喚醒,在代碼中是根據uxPendedTicks的數值來循環調用下TaskIncrementTick()函數,直到uxPendedTIcks值變爲0,並根據需要更新xYieldPending的值,最後如果啓用搶佔式調用的話則標記xAlreadyYielded的值爲pdTRUE,並調用taskYIELD_IF_USING_PREEMPTION()宏來執行任務切換,該宏最後也是調用vPortYield()函數,產生一次NVIC_PENDSV中斷以此來執行任務切換,當然這次任務切換不會立即執行因爲還沒退出臨界區代碼,最後退出臨界區並返回xAlreadyYielded值。該值在vTaskDelay()中還要用到,如果已經產生了任務切換則在vTaskDelay()函數中就不再產生任務切換,vTaskDelay()函數也就分析完畢了。該函數的分析應該和SysTick中斷一起分析纔好理解我覺得,留待下回吧。。。



發佈了98 篇原創文章 · 獲贊 101 · 訪問量 51萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章