freertos之timer淺析
背景
freertos的定時器與我所見得到其他RTOS不一樣,我知道的ucosii是在每次tick++的時候會檢查定時器連結串列,smc_rtos也是這樣做的,rtt沒看過原始碼不清楚,而freertos是將定時器實現為一個prvTimerTask。
程式碼分析
freertos是將定時器實現為一個prvTimerTask,一般如果定時n個tick,就會將prvTimerTask阻塞n個tick,但是同時加入xTimerQueue的等待接收列表,如果沒有到n個tick時間,但是接收到了xTimerQueue傳送的訊息就會了喚醒。一般情況下還是會阻塞n個tick,具體n各tick之後是否執行,跟prvTimerTask的任務優先順序有關係;如果將prvTimerTask優先順序設定得較高,在阻塞之後一般會立刻得到執行,但是如果將prvTimerTask優先順序設定得特別低的話,就會導致n個tick之後,prvTimerTask雖然加入到ready_list,但是由於優先順序不夠無法得到準時執行。
定時器的資料結構
這是定時器的資料結構,
typedef struct tmrTimerControl { const char *pcTimerName; /*定時器名稱,核心其實沒有使用,僅僅是方便除錯而已*/ ListItem_t xTimerListItem; /*所有核心特性用於事件管理的標準連結串列項。*/ TickType_t xTimerPeriodInTicks; /*計時器的時間和頻率。 */ UBaseType_t uxAutoReload; /*如果計時器在過期後應該自動重啟,則設定為pdTRUE。 如果計時器實際上是一次性計時器,則設定為pdFALSE*/ void *pvTimerID; /*標識計時器的ID。這允許在對多個計時器使用相同回撥時標識計時器。*/ TimerCallbackFunction_t pxCallbackFunction; /*定時器定時時間到之後呼叫這個函式 */ } xTIMER;
這是定時器接收命令的格式,包含兩種格式,兩種格式是以union的形式組織的,一種是對定時器開啟、停止等操作,一種是執行回撥函式
typedef struct tmrTimerParameters { TickType_t xMessageValue; /*可以選的值,是一系列命令 */ Timer_t * pxTimer; /*具體是哪一個定時器 */ } TimerParameter_t; typedef struct tmrCallbackParameters { PendedFunction_t pxCallbackFunction; /* 會被執行的回撥函式*/ void *pvParameter1; uint32_t ulParameter2; } CallbackParameters_t; /* 包含這兩種訊息型別以及識別符號的結構用於確定哪個訊息型別是有效的, 一個是執行定時器start 、reset、stop型別操作的,一個是執行回撥函式的 */ typedef struct tmrTimerQueueMessage { BaseType_t xMessageID; /*被髮送到timer task的命令 */ union { TimerParameter_t xTimerParameters; /* 如果這個巨集不是1的話,不要加入下面這個結構每一位會讓這個結構變得很大 */ #if ( INCLUDE_xTimerPendFunctionCall == 1 ) CallbackParameters_t xCallbackParameters; #endif } u; } DaemonTaskMessage_t;
對了,定時器也是和xDelayedTaskList一樣採用的雙list的機制解決了tick溢位的問題,在計算本timer溢位時間之後如果超過了tick最大值,就會加入到pxOverflowTimerList中,否則就加入到pxCurrentTimerList中,這些都會在下面具體程式碼分析中體現。
/* 儲存活動計時器的列表。計時器在過期時引用時間順序,
最近的過期時間在列表的前面。只有計時器服務任務允許訪問這些列表。*/
PRIVILEGED_DATA static List_t xActiveTimerList1;
PRIVILEGED_DATA static List_t xActiveTimerList2;
PRIVILEGED_DATA static List_t *pxCurrentTimerList;
PRIVILEGED_DATA static List_t *pxOverflowTimerList;
定時器程式碼分析
- 定時器任務的建立
在上面我們就說過,freertos軟體定時器是用task實現的。
在vTaskStartScheduler
啟動排程器的函式中,如果是使能configUSE_TIMERS
定時器開啟巨集的話,就會執行xTimerCreateTimerTask
定時器任務建立函式,定時器任務會自動執行,不需要使用者去建立
這是簡化的vTaskStartScheduler
:
void vTaskStartScheduler( void ){
#if ( configUSE_TIMERS == 1 ){
if( xReturn == pdPASS ){
xReturn = xTimerCreateTimerTask();
}else{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_TIMERS */
}
下面是定時器建立任務的程式碼,在內部區分了靜態還是動態分配任務tcb、stack:
BaseType_t xTimerCreateTimerTask( void )
{
BaseType_t xReturn = pdFAIL;
/* 檢查所使用的基礎設施計時器服務任務已建立/初始化。如果計時器已經已建立,則初始化將已執行。*/
prvCheckForValidListAndQueue();
if( xTimerQueue != NULL ){
#if( configSUPPORT_STATIC_ALLOCATION == 1 ){
StaticTask_t *pxTimerTaskTCBBuffer = NULL;
StackType_t *pxTimerTaskStackBuffer = NULL;
uint32_t ulTimerTaskStackSize;
vApplicationGetTimerTaskMemory( &pxTimerTaskTCBBuffer, &pxTimerTaskStackBuffer, &ulTimerTaskStackSize );
xTimerTaskHandle = xTaskCreateStatic( prvTimerTask,
"Tmr Svc",
ulTimerTaskStackSize,
NULL,
( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,
pxTimerTaskStackBuffer,
pxTimerTaskTCBBuffer );
if( xTimerTaskHandle != NULL ){
xReturn = pdPASS;
}
}
#else{
xReturn = xTaskCreate( prvTimerTask,
"Tmr Svc",
configTIMER_TASK_STACK_DEPTH,
NULL,
( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,
&xTimerTaskHandle );
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
}else{
mtCOVERAGE_TEST_MARKER();
}
configASSERT( xReturn );
return xReturn;
}
- 定時器建立
跟其他資料結構一樣分為動態建立和靜態建立
TimerHandle_t xTimerCreate( const char * const pcTimerName,
const TickType_t xTimerPeriodInTicks,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction );
TimerHandle_t xTimerCreateStatic( const char * const pcTimerName,
const TickType_t xTimerPeriodInTicks,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction,
StaticTimer_t *pxTimerBuffer );
在區別就是Timer_t的結構是動態分配的,還是靜態的,在有了Timer_t結構之後都會同樣呼叫prvInitialiseNewTimer( pcTimerName, xTimerPeriodInTicks, uxAutoReload, pvTimerID, pxCallbackFunction, pxNewTimer );
來初始化建立的定時器。
定時器在建立之後不會立刻執行,需要人為地手動開啟。其中xTimerPeriodInTicks
是跟定時器定時時間相關的,uxAutoReload
是跟會不會自動重灌載相關的,只有設定為True才會自動重灌載,否則只執行一次。
- 定時器的命令
freertos的定時器功能是用task實現的,但是其命令卻是用佇列queue實現的,這個佇列的訊息是上面講到的DaemonTaskMessage_t
型別,在別的task中傳送xMessage
,可以導致prvTimerTask
立刻從pxDelayedTaskList
中加入pxReadyTasksLists
,但是執行是否要看prvTimerTask
的優先順序了,一旦執行到prvTimerTask
,就會轉去執行接收xMessage
的函式,根據xMessage.xMessageID
,具體來處理命令。
命令主要是下面幾種,分為三類,一類是callback類,一類是真正的定時器命令,還有一類是可以從中斷函式中傳送的定時器命令。
/*可以在計時器佇列上傳送/接收的命令的id。這些是隻能通過組成公共軟體計時器API的巨集
使用,如下定義。從中斷髮送的命令必須使用使用tmrFIRST_FROM_ISR_COMMAND
這樣的最高數字來確定任務是否完成或者使用中斷版本的佇列傳送函式。*/
#define tmrCOMMAND_EXECUTE_CALLBACK_FROM_ISR ( ( BaseType_t ) -2 )
#define tmrCOMMAND_EXECUTE_CALLBACK ( ( BaseType_t ) -1 )
#define tmrCOMMAND_START_DONT_TRACE ( ( BaseType_t ) 0 )
#define tmrCOMMAND_START ( ( BaseType_t ) 1 )
#define tmrCOMMAND_RESET ( ( BaseType_t ) 2 )
#define tmrCOMMAND_STOP ( ( BaseType_t ) 3 )
#define tmrCOMMAND_CHANGE_PERIOD ( ( BaseType_t ) 4 )
#define tmrCOMMAND_DELETE ( ( BaseType_t ) 5 )
#define tmrFIRST_FROM_ISR_COMMAND ( ( BaseType_t ) 6 )
#define tmrCOMMAND_START_FROM_ISR ( ( BaseType_t ) 6 )
#define tmrCOMMAND_RESET_FROM_ISR ( ( BaseType_t ) 7 )
#define tmrCOMMAND_STOP_FROM_ISR ( ( BaseType_t ) 8 )
#define tmrCOMMAND_CHANGE_PERIOD_FROM_ISR ( ( BaseType_t ) 9 )
命令的傳送如下,訊息包含了確定的定時器xTimer
,確定的命令xCommandID
,確定的訊息值xOptionalValue
,在接收函式中解析訊息之後會對xTimer
執行xCommandID
的命令操作。
BaseType_t xTimerGenericCommand( TimerHandle_t xTimer,
const BaseType_t xCommandID,
const TickType_t xOptionalValue,
BaseType_t * const pxHigherPriorityTaskWoken,
const TickType_t xTicksToWait ){
BaseType_t xReturn = pdFAIL;
DaemonTaskMessage_t xMessage;
/* 向計時器服務任務傳送訊息以在指定定時器執行相應的操作。 */
if( xTimerQueue != NULL ){
xMessage.xMessageID = xCommandID;
xMessage.u.xTimerParameters.xMessageValue = xOptionalValue;
xMessage.u.xTimerParameters.pxTimer = ( Timer_t * ) xTimer;
if( xCommandID < tmrFIRST_FROM_ISR_COMMAND ){//根據xCommandID 判斷不是從中斷中傳送的
if( xTaskGetSchedulerState() == taskSCHEDULER_RUNNING ){//如果排程器是在執行中,可阻塞地去傳送訊息
xReturn = xQueueSendToBack( xTimerQueue, &xMessage, xTicksToWait );
}else{//不可阻塞地去傳送訊息
xReturn = xQueueSendToBack( xTimerQueue, &xMessage, tmrNO_DELAY );
}
}else{
xReturn = xQueueSendToBackFromISR( xTimerQueue, &xMessage, pxHigherPriorityTaskWoken );
}
traceTIMER_COMMAND_SEND( xTimer, xCommandID, xOptionalValue, xReturn );
}else{
mtCOVERAGE_TEST_MARKER();
}
return xReturn;
}
命令的接收如下,可以看出,如果是執行回撥函式的命令,就會先執行函式然後就結束了,
如果是定時器命令,不管是從中斷髮出的訊息還是普通的訊息,都會先將命令中指定的定時器從定時器列表中移除(前提是如果在列表中的話);然後執行xTimeNow = prvSampleTimeNow( &xTimerListsWereSwitched );
,在prvSampleTimeNow
中會更新現在的tick值,如果tick溢位等,會立即處理溢位,並更換兩個定時器列表;然後根據xMessage.xMessageID
具體值去做對應處理,其實分為四種命令,分別是開啟定時器、停止定時器、更改定時時間、刪除定時器。
在開啟定時器的時候還要考慮在這之前定時器是否已經過期了,如果過期還要作相應的處理,但是更改定時器時間則不需要看之前是否已經過期(我只能說是這樣處理的,這樣做的原因我也不清楚,可以留言告知哈);停止操作的話已經不需要幹活了,因為之前已經將他從定時器列表中移除了;刪除定時器則需要根據是不是動態分配的記憶體決定是否vportfree釋放記憶體。這個函式只有在prvTimerTask
中被呼叫了一次。
static void prvProcessReceivedCommands( void ){
DaemonTaskMessage_t xMessage;
Timer_t *pxTimer;
BaseType_t xTimerListsWereSwitched, xResult;
TickType_t xTimeNow;
/*如果接收到訊息,可能一次接收多個定時器訊息*/
while( xQueueReceive( xTimerQueue, &xMessage, tmrNO_DELAY ) != pdFAIL ){
#if ( INCLUDE_xTimerPendFunctionCall == 1 ){
/* 負的xMessageID命令是掛起的函式呼叫,而不是計時器命令。*/
if( xMessage.xMessageID < ( BaseType_t ) 0 ){
const CallbackParameters_t * const pxCallback = &( xMessage.u.xCallbackParameters );
/* 呼叫這個函式 */
pxCallback->pxCallbackFunction( pxCallback->pvParameter1, pxCallback->ulParameter2 );
}else{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* INCLUDE_xTimerPendFunctionCall */
/*正的xMessageID命令是定時器命令,而不是掛起的命令函式呼叫。*/
if( xMessage.xMessageID >= ( BaseType_t ) 0 ){
/* 這些訊息使用xTimerParameters成員處理軟體計時器。*/
pxTimer = xMessage.u.xTimerParameters.pxTimer;
if( listIS_CONTAINED_WITHIN( NULL, &( pxTimer->xTimerListItem ) ) == pdFALSE ){
/* 定時器在定時器列表中,刪除它。*/
( void ) uxListRemove( &( pxTimer->xTimerListItem ) );
}else{
mtCOVERAGE_TEST_MARKER();
}
/* 在這種情況下,不使用xTimerListsWereSwitched引數,但是它必須出現在函式呼叫中。
prvSampleTimeNow()函式 必須在xTimerQueue接收到訊息後呼叫,
因此沒有向訊息中新增訊息的高優先順序任務的可能性在計時器守護程序任務(因為它)
之前排隊在設定xTimeNow值之後搶佔計時器守護程序任務)。*/
xTimeNow = prvSampleTimeNow( &xTimerListsWereSwitched );
switch( xMessage.xMessageID )
{
case tmrCOMMAND_START :
case tmrCOMMAND_START_FROM_ISR :
case tmrCOMMAND_RESET :
case tmrCOMMAND_RESET_FROM_ISR :
case tmrCOMMAND_START_DONT_TRACE :
/* Start or restart a timer. */
if( prvInsertTimerInActiveList( pxTimer, xMessage.u.xTimerParameters.xMessageValue + pxTimer->xTimerPeriodInTicks, xTimeNow, xMessage.u.xTimerParameters.xMessageValue ) != pdFALSE )
{
/* The timer expired before it was added to the active timer list. Process it now.
計時器在新增到活動計時器列表之前已過期。現在處理它。*/
pxTimer->pxCallbackFunction( ( TimerHandle_t ) pxTimer );
traceTIMER_EXPIRED( pxTimer );
if( pxTimer->uxAutoReload == ( UBaseType_t ) pdTRUE ){
xResult = xTimerGenericCommand( pxTimer, tmrCOMMAND_START_DONT_TRACE, xMessage.u.xTimerParameters.xMessageValue + pxTimer->xTimerPeriodInTicks, NULL, tmrNO_DELAY );
configASSERT( xResult );
( void ) xResult;
}else{
mtCOVERAGE_TEST_MARKER();
}
}else{
mtCOVERAGE_TEST_MARKER();
}
break;
case tmrCOMMAND_STOP :
case tmrCOMMAND_STOP_FROM_ISR :
/* 定時器已經被從列表中移除了,所以這裡noting to do*/
break;
case tmrCOMMAND_CHANGE_PERIOD :
case tmrCOMMAND_CHANGE_PERIOD_FROM_ISR :
pxTimer->xTimerPeriodInTicks = xMessage.u.xTimerParameters.xMessageValue;
configASSERT( ( pxTimer->xTimerPeriodInTicks > 0 ) );
/* 新的定時器過期時間沒有參考,而且可以比舊定時器的長或者短。
command time因此要設定為當前時間,並且因為定時器時間都是不為0的,
所以下一次定時器過期時間肯定在之後,
這意味著(與上面的xTimerStart()案例不同)存在這裡沒有需要處理的失敗案例。*/
( void ) prvInsertTimerInActiveList( pxTimer, ( xTimeNow + pxTimer->xTimerPeriodInTicks ), xTimeNow, xTimeNow );
break;
case tmrCOMMAND_DELETE :
#if( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 0 ) ){
vPortFree( pxTimer );
}#elif( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 1 ) ){
if( pxTimer->ucStaticallyAllocated == ( uint8_t ) pdFALSE ){
vPortFree( pxTimer );
}else{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
break;
default :
break;
}
}
}
}
- 定時器任務
下面來分析prvTimerTask
任務。
static void prvTimerTask( void *pvParameters ){
TickType_t xNextExpireTime;
BaseType_t xListWasEmpty;
for( ;; ){
/*查詢計時器列表,看它是否包含計時器,如果包含計時器,
獲取下一個計時器將到期的時間。*/
xNextExpireTime = prvGetNextExpireTime( &xListWasEmpty );
/* 如果計時器已過期,請處理它。
否則,阻塞此任務直到計時器過期或接收到命令為止。*/
prvProcessTimerOrBlockTask( xNextExpireTime, xListWasEmpty );
/* Empty the command queue. */
prvProcessReceivedCommands();
}
}
一般task都是一個死迴圈,prvTimerTask也不例外,在這個迴圈中首先獲取下一次定時器過期時間,就是prvGetNextExpireTime函式。
在這個函式中獲取下一次定時器過期時間其實是通過定時器列表第一個列表項的值來確定的,可以這樣做的前提是,定時器列表中所有列表項都是按過期時間來排列的,且是從小到大。
static TickType_t prvGetNextExpireTime( BaseType_t * const pxListWasEmpty )
{
TickType_t xNextExpireTime;
/*計時器是按過期時間順序列出的,位於列表的開頭的定時器會先過期。
獲得時間最近過期的計時器的過期時間。
如果沒有活動的定時器的話,將下一個過期時間設定為0。
這將導致當滴答計數溢位時,此任務將解除阻塞,
此時計時器列表將被切換,下一個過期時間可以重新。*/
*pxListWasEmpty = listLIST_IS_EMPTY( pxCurrentTimerList );
if( *pxListWasEmpty == pdFALSE ){//在不為空的情況下來獲取下一次定時器過期時間,否則為0
xNextExpireTime = listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxCurrentTimerList );
}else{
/*確保tick溢位時任務解除阻塞。*/
xNextExpireTime = ( TickType_t ) 0U;
}
return xNextExpireTime;
}
然後在prvProcessTimerOrBlockTask
中檢查下一次函式過期tick時間,與當前tick時間比較,如果定時器過期則處理,如果沒有過期的定時器就將自己新增到命令佇列中去,且阻塞到下一次定時器過期的時間,將自己新增到延時任務列表中去,定時器任務再次被新增到ready任務列表的條件是 阻塞時間到 或者在阻塞期間 命令佇列收到了定時器訊息。
下面是prvProcessTimerOrBlockTask
原始碼
static void prvProcessTimerOrBlockTask( const TickType_t xNextExpireTime,
BaseType_t xListWasEmpty ){
TickType_t xTimeNow;
BaseType_t xTimerListsWereSwitched;
vTaskSuspendAll();
{
/*獲取現在的時間,對計時器是否計時進行評估是否過期。如果獲取時間會導致列表切換(這裡是指發生tick溢位),
那就不要處理這個定時器,因為所有定時器都會在prvSampleTimeNow()函式中被處理。*/
xTimeNow = prvSampleTimeNow( &xTimerListsWereSwitched );
if( xTimerListsWereSwitched == pdFALSE )
{/*tick沒有溢位*/
/*滴答計數沒有溢位,計時器是否過期呢?*/
if( ( xListWasEmpty == pdFALSE ) && ( xNextExpireTime <= xTimeNow ) )
{/*有定時器已經過期*/
( void ) xTaskResumeAll();
prvProcessExpiredTimer( xNextExpireTime, xTimeNow );//主要的處理定時器溢位的函式
}else{/*沒有定時器過期*/
/*tick計數沒有溢位,下一次過期時間還沒有到。因此,這項任務應該阻塞,
以等待下一個過期時間或者收到一個定時器命令-以先到者為準。
以下程式碼不能被執行,除非xNextExpireTime > xTimeNow當前計時器列表為空時的情況。*/
if( xListWasEmpty != pdFALSE ){
/*當前計時器列表為空,那麼定時器溢位列表也是空的嗎?之前我也很疑惑為什麼還要判斷
另一個定時器列表是否為空,其實在下面vQueueWaitForMessageRestricted函式下面,
如果另一個定時器列表也為空的話,那阻塞次任務的時間就為port_MAX了,主要是為了提升效率吧*/
xListWasEmpty = listLIST_IS_EMPTY( pxOverflowTimerList );
}
/*在這個函式中會呼叫task.c中更加下層的函式vTaskPlaceOnEventListRestricted,
將本task加入到xTimerQueue中的等待列表中去,這樣一旦有定時器訊息被髮送到xTimerQueue列表中去,
本task就會喚醒,然後將阻塞本任務自身,阻塞時間是下一個定時器過期的時候,
如果兩個定時器列表都為空,那就阻塞時間設為最大值。*/
vQueueWaitForMessageRestricted( xTimerQueue,
( xNextExpireTime - xTimeNow ), xListWasEmpty );
if( xTaskResumeAll() == pdFALSE ){
/*進行任務切換*/
portYIELD_WITHIN_API();
}else{
mtCOVERAGE_TEST_MARKER();
}
}
}else{//如果tick已經溢位了,則所有的處理在prvSampleTimeNow已經完成了。
( void ) xTaskResumeAll();
}
}
}
下面看一下在prvProcessTimerOrBlockTask
中呼叫的兩個重要的函式原始碼
prvProcessExpiredTimer
/*** 主要的定時器處理函式*/
static void prvProcessExpiredTimer( const TickType_t xNextExpireTime, const TickType_t xTimeNow ){
BaseType_t xResult;
Timer_t * const pxTimer = ( Timer_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxCurrentTimerList );
/* 從活動計時器列表中刪除計時器。之前一些操作已經保證了列表不為空。*/
( void ) uxListRemove( &( pxTimer->xTimerListItem ) );
/* 如果計時器是自動重新載入計時器,則計算下一個計時器過期時間,
並重新將計時器插入活動計時器列表中。*/
if( pxTimer->uxAutoReload == ( UBaseType_t ) pdTRUE ){
/* 定時器被插入了一個 i 和任何時間相關的 而不是現在時間的列表中。
因此,它將被插入了相對於時間的正確的列表。
就是按過期時間重新插入到定時器列表中去*/
if( prvInsertTimerInActiveList( pxTimer, ( xNextExpireTime + pxTimer->xTimerPeriodInTicks ),
xTimeNow, xNextExpireTime ) != pdFALSE ){
/* 計時器在新增到活動計時器列表之前已過期。現在重新載入。*/
xResult = xTimerGenericCommand( pxTimer, tmrCOMMAND_START_DONT_TRACE, xNextExpireTime, NULL, tmrNO_DELAY );
}else{
mtCOVERAGE_TEST_MARKER();
}
}else{
mtCOVERAGE_TEST_MARKER();
}
pxTimer->pxCallbackFunction( ( TimerHandle_t ) pxTimer );
}
void vQueueWaitForMessageRestricted( QueueHandle_t xQueue,
TickType_t xTicksToWait, const BaseType_t xWaitIndefinitely ){
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
/* 應用程式程式碼不應呼叫此函式,因此名字裡寫著“受限制的”。
它不是公共API的一部分。它是設計用於核心程式碼,並有特殊的呼叫要求。
它可能導致在只能呼叫的列表上呼叫vListInsert()可能會有一個專案,
所以列表會很快,但甚至因此,應該在鎖定排程器的情況下呼叫它,
而不是從關鍵排程器呼叫部分。*/
/* 只有在佇列中沒有訊息時才執行任何操作。這個函式不會導致任務阻塞,
只是把它放在阻塞上列表。它將不會阻塞,直到排程程式被解鎖-在那裡時間將執行良率。
如果一個專案被新增到佇列中佇列被鎖定,呼叫任務在佇列上阻塞,然後當佇列被解鎖時,
呼叫任務將被立即解除阻塞。*/
prvLockQueue( pxQueue );
if( pxQueue->uxMessagesWaiting == ( UBaseType_t ) 0U ){
/* 佇列中沒有任何內容的話,prvTimerTask加入到xTimerQueue 等待接收列表中去,
並阻塞prvTimerTask指定的時間。*/
vTaskPlaceOnEventListRestricted( &( pxQueue->xTasksWaitingToReceive ),
xTicksToWait, xWaitIndefinitely );
}
prvUnlockQueue( pxQueue );
}
void vTaskPlaceOnEventListRestricted( List_t * const pxEventList,
TickType_t xTicksToWait,
const BaseType_t xWaitIndefinitely ){
/* 將TCB的事件列表項放在適當的事件列表中。在這種情況下,假設這是唯一要做的任務
正在等待這個事件列表,所以更快的vListInsertEnd()函式可以用來代替vListInsert。*/
/*將本prvTimerTask的xEventListItem 加入到xTimerQueue 的等待接收列表中去*/
vListInsertEnd( pxEventList, &( pxCurrentTCB->xEventListItem ) );
/* 如果任務應該無限期地阻塞,那麼將阻塞時間設定為一個值,該值將被傳給
prvAddCurrentTaskToDelayedList()函式。*/
if( xWaitIndefinitely != pdFALSE ){//這個只有在兩個定時器列表都為空的時候才會執行
xTicksToWait = portMAX_DELAY;
}
/*將本任務prvTimerTask從ready_list移除,加入到等待列表(可能有兩種)中去,
阻塞xTicksToWait個tick*/
prvAddCurrentTaskToDelayedList( xTicksToWait, xWaitIndefinitely );
}
最後是prvProcessReceivedCommands();
,清空定時器命令列表,這個函式在上面定時器命令已經分析過。
其他函式分析
prvSwitchTimerLists
函式是tick溢位之後必須要得操作,軟體定時器設計兩個定時器列表的目的就是在於解決tick溢位的問題的,這個函式程式碼體現的就是how來解決tick溢位問題的。
static void prvSwitchTimerLists( void )
{
TickType_t xNextExpireTime, xReloadTime;
List_t *pxTemp;
Timer_t *pxTimer;
BaseType_t xResult;
/* tick計數已溢位。必須切換計時器列表。如果當前計時器列表中仍然引用計時器
那麼它們一定已經過期了,應該在列表切換之前進行處理。如果列表不為空,就要一直處理*/
while( listLIST_IS_EMPTY( pxCurrentTimerList ) == pdFALSE ){
xNextExpireTime = listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxCurrentTimerList );
/* 將定時器從定時器列表移除 */
pxTimer = ( Timer_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxCurrentTimerList );
( void ) uxListRemove( &( pxTimer->xTimerListItem ) );
traceTIMER_EXPIRED( pxTimer );
/* 執行它的回撥,然後傳送一個命令重新啟動計時器,如果
它是一個自動重新載入計時器。它不能在這裡作為列表重新啟動還沒有轉換。*/
pxTimer->pxCallbackFunction( ( TimerHandle_t ) pxTimer );
if( pxTimer->uxAutoReload == ( UBaseType_t ) pdTRUE )
{
/*計算過載值,以及過載值是否導致進入相同計時器列表的計時器已經過期
計時器應該重新插入到當前列表中在此迴圈中再次處理。
否則,應該傳送命令重新啟動計時器,以確保它只在之後插入到列表中列表已經交換了。*/
xReloadTime = ( xNextExpireTime + pxTimer->xTimerPeriodInTicks );
if( xReloadTime > xNextExpireTime )
{
listSET_LIST_ITEM_VALUE( &( pxTimer->xTimerListItem ), xReloadTime );
listSET_LIST_ITEM_OWNER( &( pxTimer->xTimerListItem ), pxTimer );
vListInsert( pxCurrentTimerList, &( pxTimer->xTimerListItem ) );
}else{
xResult = xTimerGenericCommand( pxTimer, tmrCOMMAND_START_DONT_TRACE, xNextExpireTime, NULL, tmrNO_DELAY );
configASSERT( xResult );
( void ) xResult;
}
}
}
/*在處理完溢位列表上的定時器之後,才是真正的列表切換操作*/
pxTemp = pxCurrentTimerList;
pxCurrentTimerList = pxOverflowTimerList;
pxOverflowTimerList = pxTemp;
}
定時器函式中有時候需要得到當前的tick數值與最近得到定時器過期時間比較,就使用prvSampleTimeNow
這個函式,巧妙地使用本次xTimeNow和上次xLastTime 的大小就可以得出是否tick溢位,如果溢位就會直接切換定時器列表,就是上面這個函式
/*如果現在tick溢位了,交換兩個定時器連結串列並且處理當前定時器連結串列中的所有定時器*/
static TickType_t prvSampleTimeNow( BaseType_t * const pxTimerListsWereSwitched )
{
TickType_t xTimeNow;
PRIVILEGED_DATA static TickType_t xLastTime = ( TickType_t ) 0U; /*lint !e956 Variable is only accessible to one task. */
xTimeNow = xTaskGetTickCount();
if( xTimeNow < xLastTime )
{
prvSwitchTimerLists();
*pxTimerListsWereSwitched = pdTRUE;
}
else
{
*pxTimerListsWereSwitched = pdFALSE;
}
xLastTime = xTimeNow;
return xTimeNow;
}