FreeRTOS進(jìn)階之隊(duì)列示例完全解析
前言
FreeRTOS提供了多種任務(wù)間通訊方式,包括:
- 任務(wù)通知(版本V8.2以及以上版本)
- 隊(duì)列
- 二進(jìn)制信號(hào)量
- 計(jì)數(shù)信號(hào)量
- 互斥量
- 遞歸互斥量
其中,二進(jìn)制信號(hào)量、計(jì)數(shù)信號(hào)量、互斥量和遞歸互斥量都是使用隊(duì)列來(lái)實(shí)現(xiàn)的,因此掌握隊(duì)列的運(yùn)行機(jī)制,是很有必要的。
隊(duì)列是FreeRTOS主要的任務(wù)間通訊方式??梢栽谌蝿?wù)與任務(wù)間、中斷和任務(wù)間傳送信息。發(fā)送到隊(duì)列的消息是通過(guò)拷貝實(shí)現(xiàn)的,這意味著隊(duì)列存儲(chǔ)的數(shù)據(jù)是原數(shù)據(jù),而不是原數(shù)據(jù)的引用。先看一下隊(duì)列的數(shù)據(jù)結(jié)構(gòu):
typedef struct QueueDefinition { int8_t *pcHead; /* 指向隊(duì)列存儲(chǔ)區(qū)起始位置,即第一個(gè)隊(duì)列項(xiàng) */ int8_t *pcTail; /* 指向隊(duì)列存儲(chǔ)區(qū)結(jié)束后的下一個(gè)字節(jié) */ int8_t *pcWriteTo; /* 指向下隊(duì)列存儲(chǔ)區(qū)的下一個(gè)空閑位置 */ union /* 使用聯(lián)合體用來(lái)確保兩個(gè)互斥的結(jié)構(gòu)體成員不會(huì)同時(shí)出現(xiàn) */ { int8_t *pcReadFrom; /* 當(dāng)結(jié)構(gòu)體用于隊(duì)列時(shí),這個(gè)字段指向出隊(duì)項(xiàng)目中的最后一個(gè). */ UBaseType_t uxRecursiveCallCount;/* 當(dāng)結(jié)構(gòu)體用于互斥量時(shí),用作計(jì)數(shù)器,保存遞歸互斥量被"獲取"的次數(shù). */ } u; List_t xTasksWaitingToSend; /* 因?yàn)榈却腙?duì)而阻塞的任務(wù)列表,按照優(yōu)先級(jí)順序存儲(chǔ) */ List_t xTasksWaitingToReceive; /* 因?yàn)榈却?duì)列項(xiàng)而阻塞的任務(wù)列表,按照優(yōu)先級(jí)順序存儲(chǔ) */ volatile UBaseType_t uxMessagesWaiting;/*< 當(dāng)前隊(duì)列的隊(duì)列項(xiàng)數(shù)目 */ UBaseType_t uxLength; /* 隊(duì)列項(xiàng)的數(shù)目 */ UBaseType_t uxItemSize; /* 每個(gè)隊(duì)列項(xiàng)的大小 */ volatile BaseType_t xRxLock; /* 隊(duì)列上鎖后,存儲(chǔ)從隊(duì)列收到的列表項(xiàng)數(shù)目,如果隊(duì)列沒(méi)有上鎖,設(shè)置為queueUNLOCKED */ volatile BaseType_t xTxLock; /* 隊(duì)列上鎖后,存儲(chǔ)發(fā)送到隊(duì)列的列表項(xiàng)數(shù)目,如果隊(duì)列沒(méi)有上鎖,設(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ù)都是圍繞這個(gè)數(shù)據(jù)結(jié)構(gòu)展開(kāi),因此數(shù)據(jù)結(jié)構(gòu)的每個(gè)成員都需要了解。如果你是第一次看這篇文章,即使有注釋?zhuān)赡苣銓?duì)結(jié)構(gòu)體的某些成員還是不理解,不要著急,這是正常的。后面介紹API函數(shù)的時(shí)候,會(huì)一一使用這些成員,結(jié)合著具體實(shí)例,會(huì)很容理解的,你需要做的,是要反復(fù)翻到這里查看。
1.隊(duì)列創(chuàng)建函數(shù)
在FreeRTOS隊(duì)列API函數(shù)一文中,我們介紹了創(chuàng)建隊(duì)列API函數(shù)xQueueCreate(),但其實(shí)這是一個(gè)宏,只是定義的像函數(shù)而已。真正被執(zhí)行的函數(shù)是xQueueGenericCreate(),我們稱(chēng)這個(gè)函數(shù)為通用隊(duì)列創(chuàng)建函數(shù)。
我們來(lái)分析一下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
:隊(duì)列項(xiàng)數(shù)目
uxItemSize
:每個(gè)隊(duì)列項(xiàng)的大小
pucQueueStorage
:使用靜態(tài)分配隊(duì)列時(shí)才使用,指向定義隊(duì)列存儲(chǔ)空間,如果使用動(dòng)態(tài)分配隊(duì)列空間(默認(rèn)),向這個(gè)參數(shù)傳遞NULL。
pxStaticQueue
:使用靜態(tài)分配隊(duì)列時(shí)才使用,指向隊(duì)列控制結(jié)構(gòu)體,如果使用動(dòng)態(tài)分配隊(duì)列空間(默認(rèn)),向這個(gè)參數(shù)傳遞NULL。
ucQueueType:類(lèi)型??赡艿闹禐椋?/p>
queueQUEUE_TYPE_BASE:表示隊(duì)列
queueQUEUE_TYPE_SET:表示隊(duì)列集合
queueQUEUE_TYPE_MUTEX:表示互斥量
queueQUEUE_TYPE_COUNTING_SEMAPHORE:表示計(jì)數(shù)信號(hào)量
queueQUEUE_TYPE_BINARY_SEMAPHORE:表示二進(jìn)制信號(hào)量
queueQUEUE_TYPE_RECURSIVE_MUTEX :表示遞歸互斥量
然而,等下我們看源碼,就會(huì)看到,在xQueueGenericCreate()函數(shù)中,參數(shù)ucQueueType只是用來(lái)可視化跟蹤調(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)試,這里用來(lái)消除編譯器警告. */ ( void ) ucQueueType; /*分配隊(duì)列結(jié)構(gòu)體和隊(duì)列項(xiàng)存儲(chǔ)空間.可以靜態(tài)也可以動(dòng)態(tài)分配,取決于參數(shù)值,FreeRTOS默認(rèn)采取動(dòng)態(tài)分配 */ pxNewQueue = prvAllocateQueueMemory( uxQueueLength, uxItemSize, &pucQueueStorage, pxStaticQueue ); if( pxNewQueue != NULL ) { if( uxItemSize == ( UBaseType_t ) 0 ) { /* 沒(méi)有為隊(duì)列項(xiàng)存儲(chǔ)分配內(nèi)存,但是pcHead指針不能設(shè)置為NULL,因?yàn)殛?duì)列用作互斥量時(shí),pcHead要設(shè)置成NULL.這里只是將pcHead指向一個(gè)已知的區(qū)域 */ pxNewQueue->pcHead = ( int8_t * ) pxNewQueue; } else { /* 指向隊(duì)列項(xiàng)存儲(chǔ)區(qū)域*/ pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage; } /* 初始化隊(duì)列結(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)的動(dòng)態(tài)分配隊(duì)列存儲(chǔ)空間方式講述一下隊(duì)列創(chuàng)建過(guò)程。首先調(diào)用函數(shù)prvAllocateQueueMemory分配隊(duì)列結(jié)構(gòu)體和隊(duì)列項(xiàng)存儲(chǔ)空間,結(jié)構(gòu)體和隊(duì)列項(xiàng)在存儲(chǔ)空間上是連續(xù)的,如圖1-1所示。
圖1-1:為隊(duì)列分配的內(nèi)存
如果隊(duì)列內(nèi)存申請(qǐng)成功,接下來(lái)會(huì)初始化隊(duì)列結(jié)構(gòu)體成員,先是pcHead成員,然后是uxLength和uxItemSize成員,最后調(diào)用函數(shù)xQueueGenericReset()初始化剩下的結(jié)構(gòu)體成員。
假設(shè)我們申請(qǐng)了3個(gè)隊(duì)列項(xiàng),每個(gè)隊(duì)列項(xiàng)占用4字節(jié)存儲(chǔ)空間(即uxLength=3、uxItemSize=4),則經(jīng)過(guò)初始化后的隊(duì)列內(nèi)存如圖1-2所示。(這個(gè)圖形象的描述了隊(duì)列結(jié)構(gòu)體的大部分成員的作用)。
圖1-2:初始化后的隊(duì)列項(xiàng)內(nèi)存
2.入隊(duì)
隊(duì)列項(xiàng)入隊(duì)也稱(chēng)為投遞(Send),分為帶中斷保護(hù)的入隊(duì)操作和不帶中斷保護(hù)的入隊(duì)操作。每種情況下又分為從隊(duì)列尾部入隊(duì)和從隊(duì)列首部入隊(duì)兩種操作,從隊(duì)列尾部入隊(duì)還有一種特殊情況,覆蓋式入隊(duì),即隊(duì)列滿后自動(dòng)覆蓋最舊的隊(duì)列項(xiàng)。如表2-1所示。
表2-1:入隊(duì)API接口列表
2.1 xQueueGenericSend()
這個(gè)函數(shù)用于入隊(duì)操作,絕不可以用在中斷服務(wù)程序中。根據(jù)參數(shù)的不同,可以從隊(duì)列尾入隊(duì)、從隊(duì)列首入隊(duì)和覆蓋式入隊(duì)。覆蓋式入隊(duì)用于只有一個(gè)隊(duì)列項(xiàng)的場(chǎng)合,入隊(duì)時(shí)如果隊(duì)列已滿,則將之前的隊(duì)列項(xiàng)覆蓋掉。函數(shù)原型為:
BaseType_t xQueueGenericSend ( QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait, const BaseType_t xCopyPosition )
xQueue
:隊(duì)列句柄pvItemToQueue:指針,指向要入隊(duì)的項(xiàng)目
xTicksToWait
:如果隊(duì)列滿,等待隊(duì)列空閑的最大時(shí)間,如果隊(duì)列滿并且xTicksToWait被設(shè)置成0,函數(shù)立刻返回。時(shí)間單位為系統(tǒng)節(jié)拍時(shí)鐘周期,宏portTICK_PERIOD_MS可以用來(lái)輔助計(jì)算真實(shí)延時(shí)值。
如果INCLUDE_vTaskSuspend設(shè)置成1,并且指定延時(shí)為portMAX_DELAY將引起任務(wù)無(wú)限阻塞(沒(méi)有超時(shí))。
xCopyPosition
:入隊(duì)位置,可以選擇從隊(duì)列尾入隊(duì)、從隊(duì)列首入隊(duì)和覆蓋式入隊(duì)。
這個(gè)函數(shù)為了獲得最高效率而放寬了編碼標(biāo)準(zhǔn):有多個(gè)返回點(diǎn)。因此如果純粹以文字方式來(lái)講解,我覺(jué)得很難達(dá)到好的效果,所以我首先給出整理后的源碼(去除調(diào)試和隊(duì)列集合有關(guān)代碼),然后畫(huà)出流程圖,對(duì)函數(shù)的關(guān)鍵點(diǎn)做重點(diǎ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(); { /* 隊(duì)列還有空閑?正在運(yùn)行的任務(wù)一定要比等待訪問(wèn)隊(duì)列的任務(wù)優(yōu)先級(jí)高.如果使用覆蓋式入隊(duì),則不需要關(guān)注隊(duì)列是否滿*/ if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) ) { /*完成數(shù)據(jù)拷貝工作,分為從隊(duì)列尾入隊(duì),從隊(duì)列首入隊(duì)和覆蓋式入隊(duì)*/ xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition ); /* 如果有任務(wù)在此等待隊(duì)列數(shù)據(jù)到來(lái),則將該任務(wù)解除阻塞*/ if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ) { /*有任務(wù)因等待出隊(duì)而阻塞,則將任務(wù)從隊(duì)列等待接收列表中刪除,然后加入到就緒列表*/ if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ) { /* 解除阻塞的任務(wù)有更高的優(yōu)先級(jí),則當(dāng)前任務(wù)要讓出CPU,因此觸發(fā)一個(gè)上下文切換.又因?yàn)楝F(xiàn)在還在臨界區(qū),要等退出臨界區(qū)后,才會(huì)執(zhí)行上下文切換.*/ queueYIELD_IF_USING_PREEMPTION(); } } else if( xYieldRequired != pdFALSE ) { /* 這個(gè)分支處理特殊情況*/ queueYIELD_IF_USING_PREEMPTION(); } taskEXIT_CRITICAL(); return pdPASS; } else { if( xTicksToWait == ( TickType_t ) 0 ) { /* 如果隊(duì)列滿并且沒(méi)有設(shè)置超時(shí),則直接退出 */ taskEXIT_CRITICAL(); /* 返回隊(duì)列滿錯(cuò)誤碼 */ return errQUEUE_FULL; } else if( xEntryTimeSet == pdFALSE ) { /* 隊(duì)列滿并且規(guī)定了阻塞時(shí)間,因此需要配置超時(shí)結(jié)構(gòu)體對(duì)象 */ vTaskSetTimeOutState( &xTimeOut ); xEntryTimeSet = pdTRUE; } } } taskEXIT_CRITICAL(); /* 退出臨界區(qū),至此,中斷和其它任務(wù)可以向這個(gè)隊(duì)列執(zhí)行入隊(duì)(投遞)或出隊(duì)(讀取)操作.因?yàn)殛?duì)列滿,任務(wù)無(wú)法入隊(duì),下面的代碼將當(dāng)前任務(wù)將阻塞在這個(gè)隊(duì)列上,在這段代碼執(zhí)行過(guò)程中我們需要掛起調(diào)度器,防止其它任務(wù)操作隊(duì)列事件列表;掛起調(diào)度器雖然可以禁止其它任務(wù)操作這個(gè)隊(duì)列,但并不能阻止中斷服務(wù)程序操作這個(gè)隊(duì)列,因此還需要將隊(duì)列上鎖,防止中斷程序讀取隊(duì)列后,使阻塞在出隊(duì)操作其它任務(wù)解除阻塞,執(zhí)行上下文切換(因?yàn)檎{(diào)度器掛起后,不允許執(zhí)行上下文切換) */ vTaskSuspendAll(); prvLockQueue( pxQueue ); /* 查看任務(wù)的超時(shí)時(shí)間是否到期 */ if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ) { if( prvIsQueueFull( pxQueue ) != pdFALSE ) { /*超時(shí)時(shí)間未到期,并且隊(duì)列仍然滿*/ vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait ); /* 解除隊(duì)列鎖,如果有任務(wù)要解除阻塞,則將任務(wù)移到掛起就緒列表中(因?yàn)楫?dāng)前調(diào)度器掛起,所以不能移到就緒列表)*/ prvUnlockQueue( pxQueue ); /* 恢復(fù)調(diào)度器,將任務(wù)從掛起就緒列表移到就緒列表中*/ if( xTaskResumeAll() == pdFALSE ) { portYIELD_WITHIN_API(); } } else { /* 隊(duì)列有空閑,重試 */ prvUnlockQueue( pxQueue ); ( void ) xTaskResumeAll(); } } else { /* 超時(shí)時(shí)間到期,返回隊(duì)列滿錯(cuò)誤碼*/ prvUnlockQueue( pxQueue ); ( void ) xTaskResumeAll(); traceQUEUE_SEND_FAILED( pxQueue ); return errQUEUE_FULL; } } }
程序流程如圖2-1所示,我們對(duì)圖中紅色字體標(biāo)注的部分做詳解。
圖2-1:通用入隊(duì)操作流程圖
當(dāng)任務(wù)將數(shù)據(jù)入隊(duì)時(shí),如果隊(duì)列未滿或者以覆蓋式入隊(duì),情況是最簡(jiǎn)單的,調(diào)用函數(shù)prvCopyDataToQueue()將要入隊(duì)的數(shù)據(jù)拷貝到隊(duì)列。
這個(gè)函數(shù)處理三種入隊(duì)情況
第一種是隊(duì)列項(xiàng)大小為0時(shí)(即隊(duì)列結(jié)構(gòu)體成員uxItemSize為0,比如二進(jìn)制信號(hào)量和計(jì)數(shù)信號(hào)量),不進(jìn)行數(shù)據(jù)拷貝工作,而是將隊(duì)列項(xiàng)計(jì)數(shù)器加1(即隊(duì)列結(jié)構(gòu)體成員uxMessagesWaiting++);
第二種情況是從隊(duì)列尾入隊(duì)時(shí),則將數(shù)據(jù)拷貝到指針pxQueue->pcWriteTo指向的地方、更新指針指向的位置、隊(duì)列項(xiàng)計(jì)數(shù)器加1;
第三種情況是從隊(duì)列首入隊(duì)時(shí),則將數(shù)據(jù)拷貝到指針pxQueue->u.pcReadFrom指向的地方、更新指針指向的位置、隊(duì)列項(xiàng)計(jì)數(shù)器加1。如果是覆蓋式入隊(duì),還會(huì)調(diào)整隊(duì)列項(xiàng)計(jì)數(shù)器的值。
完成數(shù)據(jù)入隊(duì)操作后,還要檢查是否有任務(wù)因?yàn)榈却鲫?duì)而阻塞,因?yàn)檫@次數(shù)據(jù)入隊(duì),隊(duì)列至少有一個(gè)隊(duì)列項(xiàng),如果有阻塞任務(wù),則阻塞的最高優(yōu)先級(jí)任務(wù)可以解除阻塞了。
因等待出隊(duì)而阻塞的任務(wù)會(huì)將任務(wù)的事件列表項(xiàng)(即任務(wù)TCB結(jié)構(gòu)體成員xEventListItem,我們?cè)?a href="http://www.dbjr.com.cn/article/243826.htm" target="_blank">FreeRTOS任務(wù)創(chuàng)建分析一文中講到過(guò)事件列表項(xiàng),它是任務(wù)TCB的一個(gè)結(jié)構(gòu)體成員)掛接到隊(duì)列的等待出隊(duì)列表上(即隊(duì)列結(jié)構(gòu)體成員xTasksWaitingToReceive)。
現(xiàn)在,因?yàn)橐獬蝿?wù)阻塞,我們需要將任務(wù)的事件列表項(xiàng)從隊(duì)列的等待出隊(duì)隊(duì)列上刪除,并且將任務(wù)移動(dòng)到就緒列表中。這一切,都是調(diào)用函數(shù)xTaskRemoveFromEventList()實(shí)現(xiàn)的。
之后,如果解除阻塞的任務(wù)優(yōu)先級(jí)比當(dāng)前任務(wù)優(yōu)先級(jí)更高,則觸發(fā)一個(gè)PendSV中斷,等退出臨界區(qū)后,進(jìn)行上下文切換。入隊(duì)任務(wù)完成。
上面討論了最理想的情況,過(guò)程也簡(jiǎn)潔明了,但如果任務(wù)入隊(duì)時(shí),隊(duì)列滿并且不允許覆蓋入隊(duì),則情況會(huì)變得復(fù)雜起來(lái)。
在這種情況下,先看一個(gè)簡(jiǎn)單分支:阻塞時(shí)間為0的情況。設(shè)置阻塞時(shí)間為0意味著當(dāng)隊(duì)列滿時(shí),函數(shù)立即返回,返回一個(gè)錯(cuò)誤代碼,表示隊(duì)列滿。
如果阻塞時(shí)間不為0,則本任務(wù)會(huì)因?yàn)榈却腙?duì)而進(jìn)入阻塞。在將任務(wù)設(shè)置為阻塞的過(guò)程中,是不希望有其它任務(wù)和中斷操作這個(gè)隊(duì)列的事件列表的(隊(duì)列結(jié)構(gòu)體成員xTasksWaitingToReceive列表和xTasksWaitingToSend列表),因?yàn)椴僮麝?duì)列事件列表可能引起其它任務(wù)解除阻塞,這可能會(huì)發(fā)生優(yōu)先級(jí)翻轉(zhuǎn)。
比如任務(wù)A的優(yōu)先級(jí)低于本任務(wù),但是在本任務(wù)進(jìn)入阻塞的過(guò)程中,任務(wù)A卻因?yàn)槠渌蚪獬枞耍@顯然是要絕對(duì)禁止的。因此FreeRTOS使用掛起調(diào)度器來(lái)簡(jiǎn)單粗暴的禁止其它任務(wù)操作隊(duì)列,因?yàn)閽炱鹫{(diào)度器意味著任務(wù)不能切換并且不準(zhǔn)調(diào)用可能引起任務(wù)切換的API函數(shù)。
但掛起調(diào)度器并不會(huì)禁止中斷,中斷服務(wù)函數(shù)仍然可以操作隊(duì)列事件列表,可能會(huì)解除任務(wù)阻塞、可能會(huì)進(jìn)行上下文切換,這是不允許的。于是,解決辦法是不但掛起調(diào)度器,還要給隊(duì)列上鎖!
隊(duì)列結(jié)構(gòu)體中有兩個(gè)成員跟隊(duì)列上鎖有關(guān):xRxLock和xTxLock。
這兩個(gè)成員變量為queueUNLOCKED(宏,定義為-1)時(shí),表示隊(duì)列未上鎖;
當(dāng)這兩個(gè)成員變量為queueLOCKED_UNMODIFIED(宏,定義為0)時(shí),表示隊(duì)列上鎖。
給隊(duì)列上鎖是調(diào)用宏prvLockQueue()實(shí)現(xiàn)的,代碼很簡(jiǎn)單,將隊(duì)列結(jié)構(gòu)體成員xRxLock和xTxLock都設(shè)置為queueLOCKED_UNMODIFIED。
我們看一下給隊(duì)列上鎖是如何起作用的。當(dāng)中斷服務(wù)程序操作隊(duì)列并且導(dǎo)致阻塞的任務(wù)解除阻塞時(shí),會(huì)首先判斷該隊(duì)列是否上鎖,如果沒(méi)有上鎖,則解除被阻塞的任務(wù),還會(huì)根據(jù)需要設(shè)置上下文切換請(qǐng)求標(biāo)志;
如果隊(duì)列已經(jīng)上鎖,則不會(huì)解除被阻塞的任務(wù),取而代之的是,將xRxLock或xTxLock加1,表示隊(duì)列上鎖期間出隊(duì)或入隊(duì)的數(shù)目,也表示有任務(wù)可以解除阻塞了。這部分代碼在帶中斷保護(hù)的入隊(duì)和出隊(duì)API函數(shù)中,后面我們會(huì)講到,這里先有個(gè)印象。
有將隊(duì)列上鎖操作,就會(huì)有解除隊(duì)列鎖操作。函數(shù)prvUnlockQueue()用于解除隊(duì)列鎖,將可以解除阻塞的任務(wù)插入到就緒列表,解除任務(wù)的最大數(shù)量由xRxLock和xTxLock指定。
經(jīng)過(guò)一系列的邏輯判斷,發(fā)現(xiàn)本任務(wù)還是要進(jìn)入阻塞狀態(tài),則調(diào)用函數(shù)vTaskPlaceOnEventList()來(lái)實(shí)現(xiàn)。這個(gè)函數(shù)將揭示任務(wù)因等待特定事件而進(jìn)入阻塞的詳細(xì)步驟,其實(shí)非常簡(jiǎn)單,只有兩步:第一步,將任務(wù)的事件列表項(xiàng)(任務(wù)TCB結(jié)構(gòu)體成員xEventListItem)插入到隊(duì)列的等待入隊(duì)列表(隊(duì)列結(jié)構(gòu)體成員xTasksWaitingToSend)中;第二步,將任務(wù)的狀態(tài)列表項(xiàng)(任務(wù)TCB結(jié)構(gòu)體成員xStateListItem)從就緒列表中刪除,然后插入到延時(shí)列表中,任務(wù)的最大延時(shí)時(shí)間放入xStateListItem. xItemValue中,每次系統(tǒng)節(jié)拍定時(shí)器中斷服務(wù)函數(shù)中,都會(huì)檢查這個(gè)值,檢測(cè)任務(wù)是否超時(shí)。
當(dāng)任務(wù)成功阻塞在等待入隊(duì)操作后,當(dāng)前任務(wù)就沒(méi)有必要再占用CPU了,所以接下來(lái)解除隊(duì)列鎖、恢復(fù)調(diào)度器、進(jìn)行任務(wù)切換,下一個(gè)處于最高優(yōu)先級(jí)的就緒任務(wù)就會(huì)被運(yùn)行了。
2.2 xQueueGenericSendFromISR ()
這個(gè)函數(shù)用于入隊(duì),用于中斷服務(wù)程序中。根據(jù)參數(shù)的不同,可以從隊(duì)列尾入隊(duì)、從隊(duì)列首入隊(duì)也可以覆蓋式入隊(duì)。覆蓋式入隊(duì)用于只有一個(gè)隊(duì)列項(xiàng)的場(chǎng)合,入隊(duì)時(shí)如果隊(duì)列已滿,則將之前的隊(duì)列項(xiàng)覆蓋掉。函數(shù)原型為:
BaseType_t xQueueGenericSendFromISR ( QueueHandle_t xQueue, const void * const pvItemToQueue, BaseType_t * const pxHigherPriorityTaskWoken, const BaseType_t xCopyPosition )
xQueue
:隊(duì)列句柄。
pvItemToQueue
:指針,指向要入隊(duì)的項(xiàng)目。
pxHigherPriorityTaskWoken
:如果入隊(duì)導(dǎo)致一個(gè)任務(wù)解鎖,并且解鎖的任務(wù)優(yōu)先級(jí)高于當(dāng)前運(yùn)行的任務(wù),則該函數(shù)將*pxHigherPriorityTaskWoken設(shè)置成pdTRUE。
如果xQueueSendFromISR()設(shè)置這個(gè)值為pdTRUE,則中斷退出前需要一次上下文切換。從FreeRTOS V7.3.0起,pxHigherPriorityTaskWoken稱(chēng)為一個(gè)可選參數(shù),并可以設(shè)置為NULL。
xCopyPosition
:入隊(duì)位置,可以選擇從隊(duì)列尾入隊(duì)、從隊(duì)列首入隊(duì)和覆蓋式入隊(duì)。
這個(gè)函數(shù)和xQueueGenericSend()很相似,但是當(dāng)隊(duì)列滿時(shí)不會(huì)阻塞,直接返回一個(gè)錯(cuò)誤碼,表示隊(duì)列滿(相當(dāng)于阻塞時(shí)間為0)。因此,有了分析xQueueGenericSend()的基礎(chǔ),這個(gè)函數(shù)我們很快就能看完。源碼簡(jiǎn)化后如下所示:
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ù)拷貝工作,分為從隊(duì)列尾入隊(duì),從隊(duì)列首入隊(duì)和覆蓋式入隊(duì)*/ ( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition ); /*檢查隊(duì)列是否上鎖,如果上鎖,則隊(duì)列事件列表不能被改變 */ if( pxQueue->xTxLock == queueUNLOCKED ) { /*隊(duì)列沒(méi)有上鎖*/ if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ) { if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ) { /* 解除阻塞的任務(wù)優(yōu)先級(jí)比當(dāng)前任務(wù)高,記錄上下文切換請(qǐng)求,等返回中斷服務(wù)程序后,可以顯示的強(qiáng)制上下文切換 */ if( pxHigherPriorityTaskWoken != NULL ) { *pxHigherPriorityTaskWoken = pdTRUE; } } } } else { /* 隊(duì)列上鎖,增加鎖計(jì)數(shù)器,等到任務(wù)解除隊(duì)列鎖時(shí),使用這個(gè)計(jì)數(shù)器就可以知道有多少數(shù)據(jù)入隊(duì),可以最多解除多少個(gè)因等待從隊(duì)列讀數(shù)據(jù)而阻塞的任務(wù) */ ++( pxQueue->xTxLock ); } xReturn = pdPASS; } else { xReturn = errQUEUE_FULL; } } portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus ); return xReturn; }
因?yàn)闆](méi)有阻塞,所以代碼簡(jiǎn)單了很多,唯一值得注意的是,當(dāng)成功入隊(duì)后,如果有因?yàn)榈却鲫?duì)而阻塞的任務(wù),現(xiàn)在可以將其中最高優(yōu)先級(jí)的任務(wù)解除阻塞,在執(zhí)行解除阻塞操作之前,會(huì)判斷隊(duì)列是否上鎖。
如果沒(méi)有上鎖,則解除被阻塞的任務(wù),還會(huì)根據(jù)需要設(shè)置上下文切換請(qǐng)求標(biāo)志;
如果隊(duì)列已經(jīng)上鎖,則不會(huì)解除被阻塞的任務(wù),取而代之的是將xTxLock加1,表示隊(duì)列上鎖期間入隊(duì)的個(gè)數(shù),也表示有任務(wù)可以解除阻塞了。
3.出隊(duì)
出隊(duì)的API函數(shù)要相對(duì)少一些,也分為帶中斷保護(hù)的出隊(duì)操作和不帶中斷保護(hù)的出隊(duì)操作。每種出隊(duì)情況都可以選擇是否刪除隊(duì)列項(xiàng)。出隊(duì)API函數(shù)如表3-1所示。
表3-1:出隊(duì)API接口列表
出隊(duì)操作和入隊(duì)操作有很多相似性,將入隊(duì)流程理解透徹,出隊(duì)操作不在話下,因此我們不再分析源碼。
以上就是FreeRTOS進(jìn)階之隊(duì)列示例分析的詳細(xì)內(nèi)容,更多關(guān)于FreeRTOS進(jìn)階隊(duì)列分析的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
FreeRTOS實(shí)時(shí)操作系統(tǒng)多任務(wù)管理基礎(chǔ)知識(shí)
這篇文章主要為大家介紹了FreeRTOS實(shí)時(shí)操作系統(tǒng)多任務(wù)管理的基礎(chǔ)知識(shí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04FreeRTOS進(jìn)階系統(tǒng)節(jié)拍時(shí)鐘示例的完全解析
這篇文章主要為大家介紹了FreeRTOS進(jìn)階系統(tǒng)節(jié)拍時(shí)鐘示例的完全解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04FreeRTOS進(jìn)階之調(diào)度器啟動(dòng)過(guò)程分析
這篇文章主要為大家介紹了FreeRTOS進(jìn)階之調(diào)度器啟動(dòng)過(guò)程分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04FreeRTOS實(shí)時(shí)操作系統(tǒng)的任務(wù)創(chuàng)建與任務(wù)切換
這篇文章主要為大家介紹了FreeRTOS實(shí)時(shí)操作系統(tǒng)的任務(wù)創(chuàng)建與任務(wù)切換,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04FreeRTOS實(shí)時(shí)操作系統(tǒng)信號(hào)量基礎(chǔ)
這篇文章主要為大家介紹了FreeRTOS實(shí)時(shí)操作系統(tǒng)信號(hào)量基礎(chǔ),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04freertos實(shí)時(shí)操作系統(tǒng)臨界段保護(hù)開(kāi)關(guān)中斷及進(jìn)入退出
這篇文章主要介紹了freertos實(shí)時(shí)操作系統(tǒng)臨界段保護(hù)開(kāi)關(guān)中斷及進(jìn)入退出,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04FreeRTOS實(shí)時(shí)操作系統(tǒng)Cortex-M內(nèi)核使用注意事項(xiàng)
這篇文章主要為大家介紹了FreeRTOS實(shí)時(shí)操作系統(tǒng)Cortex-M內(nèi)核使用注意事項(xiàng),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04FreeRTOS實(shí)時(shí)操作系統(tǒng)的多優(yōu)先級(jí)實(shí)現(xiàn)
這篇文章主要為大家介紹了FreeRTOS實(shí)時(shí)操作系統(tǒng)的多優(yōu)先級(jí)實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04FreeRTOS實(shí)時(shí)操作系統(tǒng)的任務(wù)應(yīng)用函數(shù)詳解
這篇文章主要為大家介紹了FreeRTOS實(shí)時(shí)操作系統(tǒng)的任務(wù)應(yīng)用函數(shù)的解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04FreeRTOS實(shí)時(shí)操作系統(tǒng)的任務(wù)概要講解
這篇文章主要為大家介紹了FreeRTOS實(shí)時(shí)操作系統(tǒng)的任務(wù)概要講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04