FreeRTOS系列第23篇---FreeRTOS隊列分析
ID:技術(shù)讓夢想更偉大
整理:李肖遙
FreeRTOS提供了多種任務(wù)間通訊方式,包括:
任務(wù)通知(版本V8.2以及以上版本) 隊列 二進(jìn)制信號量 計數(shù)信號量 互斥量 遞歸互斥量
其中,二進(jìn)制信號量、計數(shù)信號量、互斥量和遞歸互斥量都是使用隊列來實現(xiàn)的,因此掌握隊列的運(yùn)行機(jī)制,是很有必要的。
隊列是FreeRTOS主要的任務(wù)間通訊方式??梢栽谌蝿?wù)與任務(wù)間、中斷和任務(wù)間傳送信息。
發(fā)送到隊列的消息是通過拷貝實現(xiàn)的,這意味著隊列存儲的數(shù)據(jù)是原數(shù)據(jù),而不是原數(shù)據(jù)的引用。
「先看一下隊列的數(shù)據(jù)結(jié)構(gòu):」
typedef struct QueueDefinition
{
int8_t *pcHead; /* 指向隊列存儲區(qū)起始位置,即第一個隊列項 */
int8_t *pcTail; /* 指向隊列存儲區(qū)結(jié)束后的下一個字節(jié) */
int8_t *pcWriteTo; /* 指向下隊列存儲區(qū)的下一個空閑位置 */
union /* 使用聯(lián)合體用來確保兩個互斥的結(jié)構(gòu)體成員不會同時出現(xiàn) */
{
int8_t *pcReadFrom; /* 當(dāng)結(jié)構(gòu)體用于隊列時,這個字段指向出隊項目中的最后一個. */
UBaseType_t uxRecursiveCallCount;/* 當(dāng)結(jié)構(gòu)體用于互斥量時,用作計數(shù)器,保存遞歸互斥量被"獲取"的次數(shù). */
} u;
List_t xTasksWaitingToSend; /* 因為等待入隊而阻塞的任務(wù)列表,按照優(yōu)先級順序存儲 */
List_t xTasksWaitingToReceive; /* 因為等待隊列項而阻塞的任務(wù)列表,按照優(yōu)先級順序存儲 */
volatile UBaseType_t uxMessagesWaiting;/*< 當(dāng)前隊列的隊列項數(shù)目 */
UBaseType_t uxLength; /* 隊列項的數(shù)目 */
UBaseType_t uxItemSize; /* 每個隊列項的大小 */
volatile BaseType_t xRxLock; /* 隊列上鎖后,存儲從隊列收到的列表項數(shù)目,如果隊列沒有上鎖,設(shè)置為queueUNLOCKED */
volatile BaseType_t xTxLock; /* 隊列上鎖后,存儲發(fā)送到隊列的列表項數(shù)目,如果隊列沒有上鎖,設(shè)置為queueUNLOCKED */
#if ( configUSE_QUEUE_SETS == 1 )
struct QueueDefinition *pxQueueSetContainer;
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxQueueNumber;
uint8_t ucQueueType;
#endif
#if ( configSUPPORT_STATIC_ALLOCATION == 1 )
uint8_t ucStaticAllocationFlags;
#endif
} xQUEUE;
typedef xQUEUE Queue_t;
下面的所有API函數(shù)都是圍繞這個數(shù)據(jù)結(jié)構(gòu)展開,因此數(shù)據(jù)結(jié)構(gòu)的每個成員都需要了解。
如果你是第一次看這篇文章,即使有注釋,可能你對結(jié)構(gòu)體的某些成員還是不理解,不要著急,這是正常的。
后面介紹API函數(shù)的時候,會一一使用這些成員,結(jié)合著具體實例,會很容理解的,你需要做的,是要反復(fù)翻到這里查看。
1.隊列創(chuàng)建函數(shù)
在《FreeRTOS系列第18篇---FreeRTOS隊列API函數(shù)》一文中,我們介紹了創(chuàng)建隊列API函數(shù)xQueueCreate(),但其實這是一個宏,只是定義的像函數(shù)而已。
真正被執(zhí)行的函數(shù)是xQueueGenericCreate(),我們稱這個函數(shù)為通用隊列創(chuàng)建函數(shù)。
我們來分析一下xQueueGenericCreate()函數(shù),函數(shù)原型為:
QueueHandle_t xQueueGenericCreate
(
const UBaseType_t uxQueueLength,
const UBaseType_t uxItemSize,
uint8_t *pucQueueStorage,
StaticQueue_t *pxStaticQueue,
const uint8_t ucQueueType
)
「uxQueueLength」:隊列項數(shù)目 「uxItemSize」:每個隊列項的大小 「pucQueueStorage」:使用靜態(tài)分配隊列時才使用,指向定義隊列存儲空間,如果使用動態(tài)分配隊列空間(默認(rèn)),向這個參數(shù)傳遞NULL。 「pxStaticQueue」:使用靜態(tài)分配隊列時才使用,指向隊列控制結(jié)構(gòu)體,如果使用動態(tài)分配隊列空間(默認(rèn)),向這個參數(shù)傳遞NULL。 「ucQueueType」:類型??赡艿闹禐椋?/section> queueQUEUE_TYPE_BASE:表示隊列 queueQUEUE_TYPE_SET:表示隊列集合 queueQUEUE_TYPE_MUTEX:表示互斥量 queueQUEUE_TYPE_COUNTING_SEMAPHORE:表示計數(shù)信號量 queueQUEUE_TYPE_BINARY_SEMAPHORE:表示二進(jìn)制信號量 queueQUEUE_TYPE_RECURSIVE_MUTEX :表示遞歸互斥量
然而,等下我們看源碼,就會看到,在xQueueGenericCreate()函數(shù)中,參數(shù)ucQueueType只是用來可視化跟蹤調(diào)試用。
xQueueGenericCreate()函數(shù)的源碼如下所示:
QueueHandle_t xQueueGenericCreate(
const UBaseType_t uxQueueLength,
const UBaseType_t uxItemSize,
uint8_t *pucQueueStorage,
StaticQueue_t *pxStaticQueue,
const uint8_t ucQueueType )
{
Queue_t *pxNewQueue;
/* 如果使能可視化跟蹤調(diào)試,這里用來消除編譯器警告. */
( void ) ucQueueType;
/*分配隊列結(jié)構(gòu)體和隊列項存儲空間.可以靜態(tài)也可以動態(tài)分配,取決于參數(shù)值,FreeRTOS默認(rèn)采取動態(tài)分配 */
pxNewQueue = prvAllocateQueueMemory( uxQueueLength, uxItemSize, &pucQueueStorage, pxStaticQueue );
if( pxNewQueue != NULL )
{
if( uxItemSize == ( UBaseType_t ) 0 )
{
/* 沒有為隊列項存儲分配內(nèi)存,但是pcHead指針不能設(shè)置為NULL,因為隊列用作互斥量時,pcHead要設(shè)置成NULL.這里只是將pcHead指向一個已知的區(qū)域 */
pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;
}
else
{
/* 指向隊列項存儲區(qū)域*/
pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;
}
/* 初始化隊列結(jié)構(gòu)體成員*/
pxNewQueue->uxLength = uxQueueLength;
pxNewQueue->uxItemSize = uxItemSize;
( void ) xQueueGenericReset( pxNewQueue, pdTRUE );
#if ( configUSE_TRACE_FACILITY == 1 )
{
pxNewQueue->ucQueueType = ucQueueType;
}
#endif /* configUSE_TRACE_FACILITY */
traceQUEUE_CREATE( pxNewQueue );
}
return ( QueueHandle_t ) pxNewQueue;
}
我們以默認(rèn)的動態(tài)分配隊列存儲空間方式講述一下隊列創(chuàng)建過程。
首先調(diào)用函數(shù)prvAllocateQueueMemory分配隊列結(jié)構(gòu)體和隊列項存儲空間,結(jié)構(gòu)體和隊列項在存儲空間上是連續(xù)的,如圖1-1所示。

如果隊列內(nèi)存申請成功,接下來會初始化隊列結(jié)構(gòu)體成員,先是pcHead成員,然后是uxLength和uxItemSize成員,最后調(diào)用函數(shù)xQueueGenericReset()初始化剩下的結(jié)構(gòu)體成員。
假設(shè)我們申請了3個隊列項,每個隊列項占用4字節(jié)存儲空間(即uxLength=3、uxItemSize=4),則經(jīng)過初始化后的隊列內(nèi)存如圖1-2所示。(這個圖形象的描述了隊列結(jié)構(gòu)體的大部分成員的作用)。

2.入隊
隊列項入隊也稱為投遞(Send),分為帶中斷保護(hù)的入隊操作和不帶中斷保護(hù)的入隊操作。
每種情況下又分為從隊列尾部入隊和從隊列首部入隊兩種操作,從隊列尾部入隊還有一種特殊情況,覆蓋式入隊,即隊列滿后自動覆蓋最舊的隊列項。如表2-1所示。

2.1 xQueueGenericSend()
這個函數(shù)用于入隊操作,絕不可以用在中斷服務(wù)程序中。根據(jù)參數(shù)的不同,可以從隊列尾入隊、從隊列首入隊和覆蓋式入隊。覆蓋式入隊用于只有一個隊列項的場合,入隊時如果隊列已滿,則將之前的隊列項覆蓋掉。函數(shù)原型為:
BaseType_t xQueueGenericSend
(
QueueHandle_t xQueue,
const void * const pvItemToQueue,
TickType_t xTicksToWait,
const BaseType_t xCopyPosition
)
「xQueue」:隊列句柄 「pvItemToQueue」:指針,指向要入隊的項目 「xTicksToWait」:如果隊列滿,等待隊列空閑的最大時間,如果隊列滿并且 xTicksToWait被設(shè)置成0,函數(shù)立刻返回。時間單位為系統(tǒng)節(jié)拍時鐘周期,宏portTICK_PERIOD_MS可以用來輔助計算真實延時值。如果INCLUDE_vTaskSuspend設(shè)置成1,并且指定延時為portMAX_DELAY將引起任務(wù)無限阻塞(沒有超時)。「xCopyPosition」:入隊位置,可以選擇從隊列尾入隊、從隊列首入隊和覆蓋式入隊。
這個函數(shù)為了獲得最高效率而放寬了編碼標(biāo)準(zhǔn):「有多個返回點」。
因此如果純粹以文字方式來講解,我覺得很難達(dá)到好的效果,所以我首先給出整理后的源碼(去除調(diào)試和隊列集合有關(guān)代碼),然后畫出流程圖,對函數(shù)的關(guān)鍵點做重點描述。 「整理后的源碼:」
BaseType_t xQueueGenericSend(
QueueHandle_t xQueue,
const void * const pvItemToQueue,
TickType_t xTicksToWait,
const BaseType_t xCopyPosition )
{
BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired;
TimeOut_t xTimeOut;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
for( ;; )
{
taskENTER_CRITICAL();
{
/* 隊列還有空閑?正在運(yùn)行的任務(wù)一定要比等待訪問隊列的任務(wù)優(yōu)先級高.如果使用覆蓋式入隊,則不需要關(guān)注隊列是否滿*/
if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
{
/*完成數(shù)據(jù)拷貝工作,分為從隊列尾入隊,從隊列首入隊和覆蓋式入隊*/
xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
/* 如果有任務(wù)在此等待隊列數(shù)據(jù)到來,則將該任務(wù)解除阻塞*/
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
{
/*有任務(wù)因等待出隊而阻塞,則將任務(wù)從隊列等待接收列表中刪除,然后加入到就緒列表*/
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
/* 解除阻塞的任務(wù)有更高的優(yōu)先級,則當(dāng)前任務(wù)要讓出CPU,因此觸發(fā)一個上下文切換.又因為現(xiàn)在還在臨界區(qū),要等退出臨界區(qū)后,才會執(zhí)行上下文切換.*/
queueYIELD_IF_USING_PREEMPTION();
}
}
else if( xYieldRequired != pdFALSE )
{
/* 這個分支處理特殊情況*/
queueYIELD_IF_USING_PREEMPTION();
}
taskEXIT_CRITICAL();
return pdPASS;
}
else
{
if( xTicksToWait == ( TickType_t ) 0 )
{
/* 如果隊列滿并且沒有設(shè)置超時,則直接退出 */
taskEXIT_CRITICAL();
/* 返回隊列滿錯誤碼 */
return errQUEUE_FULL;
}
else if( xEntryTimeSet == pdFALSE )
{
/* 隊列滿并且規(guī)定了阻塞時間,因此需要配置超時結(jié)構(gòu)體對象 */
vTaskSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
}
}
}
taskEXIT_CRITICAL();
/* 退出臨界區(qū),至此,中斷和其它任務(wù)可以向這個隊列執(zhí)行入隊(投遞)或出隊(讀取)操作.因為隊列滿,任務(wù)無法入隊,下面的代碼將當(dāng)前任務(wù)將阻塞在這個隊列上,在這段代碼執(zhí)行過程中我們需要掛起調(diào)度器,防止其它任務(wù)操作隊列事件列表;掛起調(diào)度器雖然可以禁止其它任務(wù)操作這個隊列,但并不能阻止中斷服務(wù)程序操作這個隊列,因此還需要將隊列上鎖,防止中斷程序讀取隊列后,使阻塞在出隊操作其它任務(wù)解除阻塞,執(zhí)行上下文切換(因為調(diào)度器掛起后,不允許執(zhí)行上下文切換) */
vTaskSuspendAll();
prvLockQueue( pxQueue );
/* 查看任務(wù)的超時時間是否到期 */
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
{
if( prvIsQueueFull( pxQueue ) != pdFALSE )
{
/*超時時間未到期,并且隊列仍然滿*/
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );
/* 解除隊列鎖,如果有任務(wù)要解除阻塞,則將任務(wù)移到掛起就緒列表中(因為當(dāng)前調(diào)度器掛起,所以不能移到就緒列表)*/
prvUnlockQueue( pxQueue );
/* 恢復(fù)調(diào)度器,將任務(wù)從掛起就緒列表移到就緒列表中*/
if( xTaskResumeAll() == pdFALSE )
{
portYIELD_WITHIN_API();
}
}
else
{
/* 隊列有空閑,重試 */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
}
}
else
{
/* 超時時間到期,返回隊列滿錯誤碼*/
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
traceQUEUE_SEND_FAILED( pxQueue );
return errQUEUE_FULL;
}
}
}
程序流程如圖2-1所示,我們對圖中紅色字體標(biāo)注的部分做詳解。

當(dāng)任務(wù)將數(shù)據(jù)入隊時,如果隊列未滿或者以覆蓋式入隊,情況是最簡單的,調(diào)用函數(shù)prvCopyDataToQueue()將要入隊的數(shù)據(jù)拷貝到隊列。
「這個函數(shù)處理三種入隊情況」
隊列項大小為0時(即隊列結(jié)構(gòu)體成員
uxItemSize為0,比如二進(jìn)制信號量和計數(shù)信號量),不進(jìn)行數(shù)據(jù)拷貝工作,而是將隊列項計數(shù)器加1(即隊列結(jié)構(gòu)體成員uxMessagesWaiting++);從隊列尾入隊時,則將數(shù)據(jù)拷貝到指針
pxQueue->pcWriteTo指向的地方、更新指針指向的位置、隊列項計數(shù)器加1;從隊列首入隊時,則將數(shù)據(jù)拷貝到指針
pxQueue->u.pcReadFrom指向的地方、更新指針指向的位置、隊列項計數(shù)器加1。如果是覆蓋式入隊,還會調(diào)整隊列項計數(shù)器的值。
完成數(shù)據(jù)入隊操作后,還要檢查是否有任務(wù)因為等待出隊而阻塞,因為這次數(shù)據(jù)入隊,隊列至少有一個隊列項,如果有阻塞任務(wù),則阻塞的最高優(yōu)先級任務(wù)可以解除阻塞了。
因等待出隊而阻塞的任務(wù)會將任務(wù)的事件列表項(即任務(wù)TCB結(jié)構(gòu)體成員xEventListItem,我們在《FreeRTOS高級篇2---FreeRTOS任務(wù)創(chuàng)建分析》一文中講到過事件列表項,它是任務(wù)TCB的一個結(jié)構(gòu)體成員)掛接到隊列的等待出隊列表上(即隊列結(jié)構(gòu)體成員xTasksWaitingToReceive)。
現(xiàn)在,因為要解除任務(wù)阻塞,我們需要將任務(wù)的事件列表項從隊列的等待出隊隊列上刪除,并且將任務(wù)移動到就緒列表中。
這一切,都是調(diào)用函數(shù)xTaskRemoveFromEventList()實現(xiàn)的。
之后,如果解除阻塞的任務(wù)優(yōu)先級比當(dāng)前任務(wù)優(yōu)先級更高,則觸發(fā)一個PendSV中斷,等退出臨界區(qū)后,進(jìn)行上下文切換。入隊任務(wù)完成。
上面討論了最理想的情況,過程也簡潔明了,但如果任務(wù)入隊時,隊列滿并且不允許覆蓋入隊,則情況會變得復(fù)雜起來。
在這種情況下,先看一個簡單分支:阻塞時間為0的情況。設(shè)置阻塞時間為0意味著當(dāng)隊列滿時,函數(shù)立即返回,返回一個錯誤代碼,表示隊列滿。
如果阻塞時間不為0,則本任務(wù)會因為等待入隊而進(jìn)入阻塞。
在將任務(wù)設(shè)置為阻塞的過程中,是不希望有其它任務(wù)和中斷操作這個隊列的事件列表的(隊列結(jié)構(gòu)體成員xTasksWaitingToReceive列表和xTasksWaitingToSend列表),因為操作隊列事件列表可能引起其它任務(wù)解除阻塞,這可能會發(fā)生優(yōu)先級翻轉(zhuǎn)。
比如任務(wù)A的優(yōu)先級低于本任務(wù),但是在本任務(wù)進(jìn)入阻塞的過程中,任務(wù)A卻因為其它原因解除阻塞了,這顯然是要絕對禁止的。
因此FreeRTOS使用掛起調(diào)度器來簡單粗暴的禁止其它任務(wù)操作隊列,因為掛起調(diào)度器意味著任務(wù)不能切換并且不準(zhǔn)調(diào)用可能引起任務(wù)切換的API函數(shù)。
但掛起調(diào)度器并不會禁止中斷,中斷服務(wù)函數(shù)仍然可以操作隊列事件列表,可能會解除任務(wù)阻塞、可能會進(jìn)行上下文切換,這是不允許的。
于是,解決辦法是不但掛起調(diào)度器,還要給隊列上鎖!
隊列結(jié)構(gòu)體中有兩個成員跟隊列上鎖有關(guān):xRxLock和xTxLock。
這兩個成員變量為queueUNLOCKED(宏,定義為-1)時,表示隊列未上鎖;
當(dāng)這兩個成員變量為queueLOCKED_UNMODIFIED(宏,定義為0)時,表示隊列上鎖。
給隊列上鎖是調(diào)用宏prvLockQueue()實現(xiàn)的,代碼很簡單,將隊列結(jié)構(gòu)體成員xRxLock和xTxLock都設(shè)置為queueLOCKED_UNMODIFIED。
「我們看一下給隊列上鎖是如何起作用的?!?/strong>
當(dāng)中斷服務(wù)程序操作隊列并且導(dǎo)致阻塞的任務(wù)解除阻塞時,會首先判斷該隊列是否上鎖,如果沒有上鎖,則解除被阻塞的任務(wù),還會根據(jù)需要設(shè)置上下文切換請求標(biāo)志;
如果隊列已經(jīng)上鎖,則不會解除被阻塞的任務(wù),取而代之的是,將xRxLock或xTxLock加1,表示隊列上鎖期間出隊或入隊的數(shù)目,也表示有任務(wù)可以解除阻塞了。
這部分代碼在帶中斷保護(hù)的入隊和出隊API函數(shù)中,后面我們會講到,這里先有個印象。
有將隊列上鎖操作,就會有解除隊列鎖操作。函數(shù)prvUnlockQueue()用于解除隊列鎖,將可以解除阻塞的任務(wù)插入到就緒列表,解除任務(wù)的最大數(shù)量由xRxLock和xTxLock指定。
經(jīng)過一系列的邏輯判斷,發(fā)現(xiàn)本任務(wù)還是要進(jìn)入阻塞狀態(tài),則調(diào)用函數(shù)vTaskPlaceOnEventList()來實現(xiàn)。
這個函數(shù)將揭示任務(wù)因等待特定事件而進(jìn)入阻塞的詳細(xì)步驟,其實非常簡單,只有兩步:
「第一步」,將任務(wù)的事件列表項(任務(wù)TCB結(jié)構(gòu)體成員xEventListItem)插入到隊列的等待入隊列表(隊列結(jié)構(gòu)體成員xTasksWaitingToSend)中;
「第二步」,將任務(wù)的狀態(tài)列表項(任務(wù)TCB結(jié)構(gòu)體成員xStateListItem)從就緒列表中刪除,然后插入到延時列表中,任務(wù)的最大延時時間放入xStateListItem. xItemValue中,每次系統(tǒng)節(jié)拍定時器中斷服務(wù)函數(shù)中,都會檢查這個值,檢測任務(wù)是否超時。
當(dāng)任務(wù)成功阻塞在等待入隊操作后,當(dāng)前任務(wù)就沒有必要再占用CPU了,所以接下來解除隊列鎖、恢復(fù)調(diào)度器、進(jìn)行任務(wù)切換,下一個處于最高優(yōu)先級的就緒任務(wù)就會被運(yùn)行了。
2.2 xQueueGenericSendFromISR ()
這個函數(shù)用于入隊,用于中斷服務(wù)程序中。根據(jù)參數(shù)的不同,可以從隊列尾入隊、從隊列首入隊也可以覆蓋式入隊。覆蓋式入隊用于只有一個隊列項的場合,入隊時如果隊列已滿,則將之前的隊列項覆蓋掉。函數(shù)原型為:
BaseType_t xQueueGenericSendFromISR
(
QueueHandle_t xQueue,
const void * const pvItemToQueue,
BaseType_t * const pxHigherPriorityTaskWoken,
const BaseType_t xCopyPosition
)
「xQueue」:隊列句柄。 「pvItemToQueue」:指針,指向要入隊的項目。 「pxHigherPriorityTaskWoken」:如果入隊導(dǎo)致一個任務(wù)解鎖,并且解鎖的任務(wù)優(yōu)先級高于當(dāng)前運(yùn)行的任務(wù),則該函數(shù)將 *pxHigherPriorityTaskWoken設(shè)置成pdTRUE。如果xQueueSendFromISR()設(shè)置這個值為pdTRUE,則中斷退出前需要一次上下文切換。從FreeRTOS V7.3.0起,pxHigherPriorityTaskWoken稱為一個可選參數(shù),并可以設(shè)置為NULL。「xCopyPosition」:入隊位置,可以選擇從隊列尾入隊、從隊列首入隊和覆蓋式入隊。
這個函數(shù)和xQueueGenericSend()很相似,但是當(dāng)隊列滿時不會阻塞,直接返回一個錯誤碼,表示隊列滿(相當(dāng)于阻塞時間為0)。
因此,有了分析xQueueGenericSend()的基礎(chǔ),這個函數(shù)我們很快就能看完。
「源碼簡化后如下所示:」
BaseType_t xQueueGenericSendFromISR(
QueueHandle_t xQueue,
const void * const pvItemToQueue,
BaseType_t * const pxHigherPriorityTaskWoken,
const BaseType_t xCopyPosition )
{
BaseType_t xReturn;
UBaseType_t uxSavedInterruptStatus;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
{
if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
{
traceQUEUE_SEND_FROM_ISR( pxQueue );
/*完成數(shù)據(jù)拷貝工作,分為從隊列尾入隊,從隊列首入隊和覆蓋式入隊*/
( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
/*檢查隊列是否上鎖,如果上鎖,則隊列事件列表不能被改變 */
if( pxQueue->xTxLock == queueUNLOCKED )
{ /*隊列沒有上鎖*/
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
/* 解除阻塞的任務(wù)優(yōu)先級比當(dāng)前任務(wù)高,記錄上下文切換請求,等返回中斷服務(wù)程序后,可以顯示的強(qiáng)制上下文切換 */
if( pxHigherPriorityTaskWoken != NULL )
{
*pxHigherPriorityTaskWoken = pdTRUE;
}
}
}
}
else
{
/* 隊列上鎖,增加鎖計數(shù)器,等到任務(wù)解除隊列鎖時,使用這個計數(shù)器就可以知道有多少數(shù)據(jù)入隊,可以最多解除多少個因等待從隊列讀數(shù)據(jù)而阻塞的任務(wù) */
++( pxQueue->xTxLock );
}
xReturn = pdPASS;
}
else
{
xReturn = errQUEUE_FULL;
}
}
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
return xReturn;
}
因為沒有阻塞,所以代碼簡單了很多,「唯一值得注意的是」:
當(dāng)成功入隊后,如果有因為等待出隊而阻塞的任務(wù),現(xiàn)在可以將其中最高優(yōu)先級的任務(wù)解除阻塞,在執(zhí)行解除阻塞操作之前,會判斷隊列是否上鎖。
如果沒有上鎖,則解除被阻塞的任務(wù),還會根據(jù)需要設(shè)置上下文切換請求標(biāo)志;
如果隊列已經(jīng)上鎖,則不會解除被阻塞的任務(wù),取而代之的是將xTxLock加1,表示隊列上鎖期間入隊的個數(shù),也表示有任務(wù)可以解除阻塞了。
3.出隊
出隊的API函數(shù)要相對少一些,也分為帶中斷保護(hù)的出隊操作和不帶中斷保護(hù)的出隊操作。
每種出隊情況都可以選擇是否刪除隊列項。出隊API函數(shù)如表3-1所示。

出隊操作和入隊操作有很多相似性,將入隊流程理解透徹,出隊操作不在話下,因此我們不再分析源碼。
嵌入式編程專輯 Linux 學(xué)習(xí)專輯 C/C++編程專輯 Qt進(jìn)階學(xué)習(xí)專輯
關(guān)注我的微信公眾號,回復(fù)“加群”按規(guī)則加入技術(shù)交流群。
點擊“閱讀原文”查看更多分享。
