東莞企業(yè)黃頁百度關(guān)鍵詞優(yōu)化送網(wǎng)站
前言
- 本篇文章主要對(duì) FreeRTOS 中消息隊(duì)列的概念和相關(guān)函數(shù)進(jìn)行了詳解
- 消息隊(duì)列【下篇】詳細(xì)剖析了消息隊(duì)列中發(fā)送、接收時(shí)隊(duì)列消息控制塊中各種指針的行為,以及幾個(gè)發(fā)送消息和接收消息的函數(shù)的運(yùn)作流程
- 筆者有關(guān)于 【FreeRTOS】【應(yīng)用篇】消息隊(duì)列【上篇】——隊(duì)列基本概念、創(chuàng)建和刪除,講解了消息隊(duì)列的基本概念(作用、存儲(chǔ)結(jié)構(gòu)、出入隊(duì)列邏輯、阻塞機(jī)制)以及相關(guān)函數(shù)(隊(duì)列創(chuàng)建和刪除)
- 一部分代碼和圖片參考野火 FreeRTOS 教程
文章目錄
- 前言
- 一、發(fā)送、接收時(shí)隊(duì)列消息控制塊中各種指針的行為
- 1. 初始化后
- 2. 發(fā)送消息到隊(duì)列時(shí)
- ① 發(fā)送到隊(duì)尾(普通消息)
- ② 發(fā)送到隊(duì)頭(緊急消息)
- 3. 從隊(duì)列中讀取消息時(shí)
- 二、消息發(fā)送 API
- 1. 概要
- ① 適用于任務(wù)中的消息發(fā)送函數(shù)
- ② 適用于中斷中的消息發(fā)送函數(shù)
- 2. xQueueSend()與 xQueueSendToBack()
- ① xQueueSend() 代碼
- ② xQueueSend() 使用示例
- ③ xQueueSendToBack() 代碼
- 3. xQueueSendFromISR()與 xQueueSendToBackFromISR()
- ① 代碼
- ② 使用示例
- 4. xQueueSendToFront()
- ① 代碼
- 5. xQueueSendToFrontFromISR()
- ① 代碼
- 三、通用的任務(wù)中發(fā)送消息函數(shù) xQueueGenericSend()
- 1. xQueueGenericSend() 函數(shù)定義
- 2. xQueueGenericSend() 函數(shù)流程圖
- 3. prvCopyDataToQueue() 函數(shù)說明
- ① 如何被調(diào)用
- ② 參數(shù)說明
- ③ prvCopyDataToQueue() 函數(shù)完整代碼
- 4. 超時(shí)狀態(tài)結(jié)構(gòu)的配置 vTaskSetTimeOutState()
- ① 超時(shí)狀態(tài)結(jié)構(gòu)如何配置
- ② vTaskSetTimeOutState() 函數(shù)定義
- 5. 掛起調(diào)度器和鎖上隊(duì)列的意義
- ① 如何操作
- ② 意義
- ③ 隊(duì)列上鎖代碼
- 6. 檢查延時(shí)是否到期函數(shù) xTaskCheckForTimeOut()
- ① 使用
- ② 主要邏輯
- ③ 源代碼
- 7. 將任務(wù)插入任務(wù)等待發(fā)送列表 vTaskPlaceOnEventList()
- ① 使用
- ② 函數(shù)定義
- 8. 隊(duì)列解鎖函數(shù) prvUnlockQueue()
- ① 函數(shù)原型
- ② 函數(shù)結(jié)構(gòu)分析
- ③ 計(jì)數(shù)鎖的原理
- 四、用于中斷中的 通用的消息發(fā)送函數(shù) xQueueGenericSendFromISR()
- 1. 函數(shù)概要
- 2. 函數(shù)代碼
- 五、消息讀取 API
- 1. 簡介
- 2. xQueueReceive()
- ① 原型及作用
- ② 宏展開
- ③ 應(yīng)用
- 3. xQueuePeek()
- ① 作用
- ② 宏展開
- 4. xQueueReceiveFromISR() 與 xQueuePeekFromISR()
- ① xQueueReceiveFromISR() 使用示例
- 六、通用讀取消息函數(shù) xQueueGenericReceive()
- 1. 函數(shù)結(jié)構(gòu)分析
- 2. 函數(shù)定義
- 四、性能提示
- 后記
一、發(fā)送、接收時(shí)隊(duì)列消息控制塊中各種指針的行為
1. 初始化后
- 消息空間從第一個(gè)單元到最后一個(gè)單元是從低地址增長到高地址
- pcHead 指向消息空間第一個(gè)消息單元,此后固定不動(dòng),是為了方便快速定位消息空間頭部而設(shè)定的指針
- pcTail 指向消息空間最后一個(gè)消息單元(這個(gè)消息單元不作存儲(chǔ)消息使用,前一個(gè)單元才是被用作存儲(chǔ)的最后一個(gè)單元),此后固定不動(dòng),是為了方便快速定位消息空間尾部而設(shè)定的指針
- pcWriteTo 指向 pcHead,指向下一個(gè)插入到隊(duì)尾的消息的存儲(chǔ)單元
- pcReadFrom 指向 pcTail 的前一個(gè)消息單元,指向隊(duì)列中下一個(gè)要被讀取的消息的前一個(gè)單元
2. 發(fā)送消息到隊(duì)列時(shí)
① 發(fā)送到隊(duì)尾(普通消息)
- xQueueSend() 和 xQueueSendToBack()
- 消息被拷貝到 pcWriteTo 指向的消息單元,然后 pcWriteTo 自增一個(gè)消息單元
( void ) memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( size_t ) pxQueue->uxItemSize );
pxQueue->pcWriteTo += pxQueue->uxItemSize;
- 當(dāng) pcWriteTo 自增到 pcTail 位置時(shí),返回 pcHead
if( pxQueue->pcWriteTo >= pxQueue->pcTail ) {pxQueue->pcWriteTo = pxQueue->pcHead;}
② 發(fā)送到隊(duì)頭(緊急消息)
- xQueueSendToFront()
- 消息被拷貝到 pcReadFrom 指向的消息單元,然后 pcReadFrom 自減一個(gè)消息單元
( void ) memcpy( ( void * ) pxQueue->u.pcReadFrom, pvItemToQueue, ( size_t ) pxQueue->uxItemSize );
pxQueue->u.pcReadFrom -= pxQueue->uxItemSize;
- pcReadFrom 指向 pcHead 的上一個(gè)消息單元時(shí)(此時(shí)越界),就重置 pcReadFrom 指向 pcTail 的前一個(gè)消息單元
if( pxQueue->u.pcReadFrom < pxQueue->pcHead ){pxQueue->u.pcReadFrom = ( pxQueue->pcTail - pxQueue->uxItemSize );}
3. 從隊(duì)列中讀取消息時(shí)
- 首先自增 pcReadFrom,使其指向要讀取的消息單元(pcReadFrom 初始化后保持為指向要讀取的消息單元的前一個(gè)單元)
pxQueue->u.pcReadFrom += pxQueue->uxItemSize;
- 然后判斷自增后的 pcReadFrom 是否越界,如果指向的位置等于或者大于 pcTail 指向的位置,那么使 pcReadFrom 重新指向 pcHead
if( pxQueue->u.pcReadFrom >= pxQueue->pcTail ) /*lint !e946 MISRA exception justified as use of the relational operator is the cleanest solutions. */{pxQueue->u.pcReadFrom = pxQueue->pcHead;}
- 最后從隊(duì)列中將 pcReadFrom 指向的要讀取的消息拷貝出來
( void ) memcpy( ( void * ) pvBuffer, ( void * ) pxQueue->u.pcReadFrom, ( size_t ) pxQueue->uxItemSize );
二、消息發(fā)送 API
1. 概要
① 適用于任務(wù)中的消息發(fā)送函數(shù)
適用于任務(wù)中的消息發(fā)送函數(shù)有三個(gè)版本:
-
xQueueSend() 等同于 xQueueSendToBack(),作用都是將消息插入隊(duì)列尾部
- 從尾部插入的消息是普通消息,會(huì)按照插入順序的先后被先后讀取
- 注意不是指 pcTail 所指的位置,pcTail 只是一個(gè)為了方便快速定位消息空間尾部設(shè)定的指針,在隊(duì)列創(chuàng)建后就固定不動(dòng)了
- 這里所說的插入到隊(duì)尾是指插入到 pcWriteTo 所指的位置,插入完成后,pcWriteTo 自增
-
xQueueSendToFront() 作用是將消息插入隊(duì)列頭部
- 從頭部插入的消息是緊急消息,緊急消息會(huì)比普通消息先被讀取
- 這里所說的插入頭部并不是指插入到 pcHead 所指的位置,和 pcTail 一樣,pcHead 只是一個(gè)為了方便快速定位消息空間頭部設(shè)定的指針,在隊(duì)列創(chuàng)建后就固定不動(dòng)了
- 這里所說的插入到隊(duì)頭是指插入到 pcReadFrom 所指的位置,插入完成后,pcReadFrom 自減(保持指向下一個(gè)要讀取的消息的前一個(gè)消息)
-
這三個(gè)適用于任務(wù)中的消息發(fā)送函數(shù)實(shí)際上都調(diào)用了 xQueueGenericSend(),只是傳入的發(fā)送位置的參數(shù)不同
② 適用于中斷中的消息發(fā)送函數(shù)
適用于中斷中的消息發(fā)送函數(shù)也有三個(gè)版本(只能用于中斷中執(zhí)行,是不帶阻塞機(jī)制的):
- xQueueSendToBackFromISR() 等同于 xQueueSendFromISR()
- 作用是將消息插入隊(duì)列尾部,與上文的 xQueueSend() 說明一致
- xQueueSendToFrontFromISR() 將消息插入隊(duì)列頭部,與上文的 xQueueSendToFront() 說明一致
2. xQueueSend()與 xQueueSendToBack()
① xQueueSend() 代碼
- 原型
BaseType_t xQueueSend(QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait);
-
參數(shù)說明
- xQueue:隊(duì)列句柄
- pvItemToQueue:指針,指向要發(fā)送到隊(duì)列尾部的隊(duì)列消息
- xTicksToWait:隊(duì)列滿時(shí),等待隊(duì)列空閑的最大超時(shí)時(shí)間。如果隊(duì)列滿并且 xTicksToWait 被設(shè)置成 0,函數(shù)立刻返回。超時(shí)時(shí)間的單位為系統(tǒng)節(jié)拍周期,常量 portTICK_PERIOD_MS 用于輔助計(jì)算真實(shí)的時(shí)間,單位為 ms。如果 INCLUDE_vTaskSuspend 設(shè)置成 1,并且指定延時(shí)為 portMAX_DELAY 將導(dǎo)致任務(wù)掛起(沒有超時(shí))
-
宏定義
#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
② xQueueSend() 使用示例
該宏是為了向后兼容沒有包含 xQueueSendToFront() 和 xQueueSendToBack() 這兩個(gè)宏的 FreeRTOS 版本。
static void Send_Task(void* parameter)
{BaseType_t xReturn = pdPASS; /* 定義一個(gè)創(chuàng)建信息返回值,默認(rèn)為 pdPASS */uint32_t send_data1 = 1;uint32_t send_data2 = 2;while (1) {if (Key_Scan(KEY1_GPIO_PORT, KEY1_GPIO_PIN) == KEY_ON) {/* K1 被按下 */printf("發(fā)送消息 send_data1!\n");xReturn = xQueueSend(Test_Queue, /* 消息隊(duì)列的句柄 */&send_data1, /* 發(fā)送的消息內(nèi)容 */0); /* 等待時(shí)間 0 */if (pdPASS == xReturn)printf("消息 send_data1 發(fā)送成功!\n\n");}if (Key_Scan(KEY2_GPIO_PORT, KEY2_GPIO_PIN) == KEY_ON) {/* K2 被按下 */printf("發(fā)送消息 send_data2!\n");xReturn = xQueueSend(Test_Queue, /* 消息隊(duì)列的句柄 */&send_data2, /* 發(fā)送的消息內(nèi)容 */0); /* 等待時(shí)間 0 */if (pdPASS == xReturn)printf("消息 send_data2 發(fā)送成功!\n\n");}vTaskDelay(20); /* 延時(shí) 20 個(gè) tick */}
}
③ xQueueSendToBack() 代碼
- 原型
與 xQueueSend() 一樣 - 宏定義
#define xQueueSendToBack( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
3. xQueueSendFromISR()與 xQueueSendToBackFromISR()
xQueueSendFromISR() 與 xQueueSendToBackFromISR() 是等同的。
① 代碼
- 原型
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);
- 參數(shù)說明
- xQueue:隊(duì)列句柄
- pvItemToQueue:指針,指向要發(fā)送到隊(duì)列尾部的消息
- pxHigherPriorityTaskWoken:如果入隊(duì)導(dǎo)致一個(gè)任務(wù)解鎖,并且解鎖的任務(wù)優(yōu)先級(jí)高于當(dāng)前被中斷的任務(wù),則將 *pxHigherPriorityTaskWoken設(shè)置成 pdTRUE,然后在中斷退出前需要進(jìn)行一次上下文切換, 去執(zhí)行被喚醒的優(yōu)先級(jí)更高的任務(wù) 。
- 宏定義
#define xQueueSendToFrontFromISR(xQueue, pvItemToQueue, pxHigherPriorityTaskWoken) \xQueueGenericSendFromISR((xQueue), (pvItemToQueue), (pxHigherPriorityTaskWoken), queueSEND_TO_FRONT)
#define xQueueSendToBackFromISR(xQueue, pvItemToQueue, pxHigherPriorityTaskWoken) \xQueueGenericSendFromISR((xQueue), (pvItemToQueue), (pxHigherPriorityTaskWoken), queueSEND_TO_BACK)
② 使用示例
使用 pxHigherPriorityTaskWoken 來確定是否需要上下文切換:
void vBufferISR(void)
{char cIn;BaseType_t xHigherPriorityTaskWoken;/* 在 ISR 開始的時(shí)候,我們并沒有喚醒任務(wù) */xHigherPriorityTaskWoken = pdFALSE;/* 直到緩沖區(qū)為空 */do {/* 從緩沖區(qū)獲取一個(gè)字節(jié)的數(shù)據(jù) */cIn = portINPUT_BYTE(RX_REGISTER_ADDRESS);/* 發(fā)送這個(gè)數(shù)據(jù) */xQueueSendFromISR(xRxQueue, &cIn, &xHigherPriorityTaskWoken);} while (portINPUT_BYTE(BUFFER_COUNT));/* 這時(shí)候 buffer 已經(jīng)為空,如果需要?jiǎng)t進(jìn)行上下文切換 */if (xHigherPriorityTaskWoken) {/* 上下文切換,這是一個(gè)宏,不同的處理器,具體的方法不一樣 */taskYIELD_FROM_ISR();}
}
4. xQueueSendToFront()
用于向隊(duì)列隊(duì)首發(fā)送一個(gè)消息,該消息會(huì)被優(yōu)先讀取。
① 代碼
- 原型
BaseType_t xQueueSendToFront( QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait );
- 參數(shù)說明
- xQueue 隊(duì)列句柄
- pvItemToQueue 指針,指向要發(fā)送到隊(duì)首的消息
- xTicksToWait 隊(duì)列滿時(shí),等待隊(duì)列空閑的最大超時(shí)時(shí)間。如果隊(duì)列滿并且 xTicksToWait 被設(shè)置成 0,函數(shù)立刻返回。超時(shí)時(shí)間的單位為系統(tǒng)節(jié)拍周期,常量 portTICK_PERIOD_MS 用于輔助計(jì)算真實(shí)的時(shí)間,單位為 ms。如果 INCLUDE_vTaskSuspend 設(shè)置成 1,并且指定延時(shí)為 portMAX_DELAY 將導(dǎo)致任務(wù)無限阻塞(沒有超時(shí))
- 宏定義
#define xQueueSendToFront(xQueue, pvItemToQueue, xTicksToWait) \xQueueGenericSend((xQueue), (pvItemToQueue), \(xTicksToWait), queueSEND_TO_FRONT)
5. xQueueSendToFrontFromISR()
用于在中斷中向隊(duì)首發(fā)送信息。
① 代碼
- 原型
BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);
- 參數(shù)說明
- xQueue 隊(duì)列句柄
- pvItemToQueue 指針,指向要發(fā)送到隊(duì)首的消息
- pxHigherPriorityTaskWoken 如果入隊(duì)導(dǎo)致一個(gè)任務(wù)解鎖,并且解鎖的任務(wù)優(yōu)先級(jí)高于當(dāng)前被中斷的任務(wù),則將 *pxHigherPriorityTaskWoken 設(shè)置成 pdTRUE,然后在中斷退出前需要進(jìn)行一次上下文切換,去執(zhí)行被喚醒的優(yōu)先級(jí)更高的任務(wù) 。
- 宏定義
#define xQueueSendToFrontFromISR(xQueue, pvItemToQueue, pxHigherPriorityTaskWoken) \xQueueGenericSendFromISR((xQueue), (pvItemToQueue), \(pxHigherPriorityTaskWoken), queueSEND_TO_FRONT)
三、通用的任務(wù)中發(fā)送消息函數(shù) xQueueGenericSend()
上述的任務(wù)中的消息發(fā)送函數(shù) API 經(jīng)過宏定義展開后實(shí)際上就是傳入不同參數(shù)的 xQueueGenericSend(),下面將對(duì)這個(gè)函數(shù)進(jìn)行詳細(xì)的介紹。
1. xQueueGenericSend() 函數(shù)定義
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;configASSERT( pxQueue );configASSERT( !( ( pvItemToQueue == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );configASSERT( !( ( xCopyPosition == queueOVERWRITE ) && ( pxQueue->uxLength != 1 ) ) );#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) ){configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );}#endif/* This function relaxes the coding standard somewhat to allow returnstatements within the function itself. This is done in the interestof execution time efficiency. */for( ;; ){taskENTER_CRITICAL();{/* Is there room on the queue now? The running task must be thehighest priority task wanting to access the queue. If the head itemin the queue is to be overwritten then it does not matter if thequeue is full. *///消息空間未滿或允許覆蓋寫入if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) ){traceQUEUE_SEND( pxQueue );xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );//此處省略隊(duì)列集相關(guān)代碼taskEXIT_CRITICAL();return pdPASS;}else//消息空間已滿且不允許覆蓋寫入{if( xTicksToWait == ( TickType_t ) 0 ){/* The queue was full and no block time is specified (orthe block time has expired) so leave now. *///消息空間已滿且不允許覆蓋寫入,且阻塞時(shí)間設(shè)置為0 或 阻塞時(shí)間已過taskEXIT_CRITICAL();/* Return to the original privilege level before exitingthe function. */traceQUEUE_SEND_FAILED( pxQueue );return errQUEUE_FULL;}else if( xEntryTimeSet == pdFALSE ){/* The queue was full and a block time was specified soconfigure the timeout structure. *///消息空間已滿且不允許覆蓋寫入,并且阻塞時(shí)間大于0所以需要配置超時(shí)結(jié)構(gòu)vTaskSetTimeOutState( &xTimeOut );xEntryTimeSet = pdTRUE;}else{/* Entry time was already set. *///已經(jīng)設(shè)置了進(jìn)入時(shí)間mtCOVERAGE_TEST_MARKER();}}}taskEXIT_CRITICAL();/* Interrupts and other tasks can send to and receive from the queuenow the critical section has been exited. *///掛起調(diào)度器 并 鎖定隊(duì)列的等待發(fā)送任務(wù)列表和等待接收任務(wù)列表://這兩個(gè)操作都是為了防止其它任務(wù)或者中斷操作 xTasksWaitingToReceive 列表和 xTasksWaitingToSend 列表而引起優(yōu)先級(jí)翻轉(zhuǎn)//問:為什么可能會(huì)優(yōu)先級(jí)翻轉(zhuǎn)?//答:設(shè)想當(dāng)前要進(jìn)入阻塞的任務(wù)B 的優(yōu)先級(jí)比已經(jīng)在阻塞中的任務(wù)A 的優(yōu)先級(jí)高。//正常情況下,當(dāng)任務(wù)B 也進(jìn)入阻塞后,此時(shí)兩個(gè)任務(wù)都在阻塞中(比如都在阻塞等待入隊(duì)),//那么當(dāng)可以入隊(duì)時(shí)應(yīng)該是優(yōu)先級(jí)高的B 線解除阻塞并入隊(duì);//但是可能會(huì)發(fā)生這樣一種情況,在設(shè)置任務(wù)B 進(jìn)入阻塞的過程中,//其它任務(wù)或者中斷操作了 xTasksWaitingToReceive 列表和 xTasksWaitingToSend 列表導(dǎo)致可以入隊(duì)了,//正確的邏輯應(yīng)該是解除優(yōu)先級(jí)較高的任務(wù)B 的阻塞而任務(wù)A 繼續(xù)阻塞等待入隊(duì),//但是此時(shí)由于任務(wù)B 還在設(shè)置進(jìn)入阻塞的過程中,所以阻塞列表中只有任務(wù)A,導(dǎo)致只能解除//任務(wù)A 的阻塞。掛起調(diào)度器來禁止其它任務(wù)對(duì)兩個(gè)列表的操作 和 鎖定中斷對(duì)兩個(gè)列表的操作可以確保//在任務(wù)B 成功進(jìn)入阻塞后在通過優(yōu)先級(jí)高低來解除任務(wù)的阻塞,防止優(yōu)先級(jí)較低的任務(wù)A 先于任務(wù)B 解除阻塞。vTaskSuspendAll(); //暫停任務(wù)切換,防止在運(yùn)行其它任務(wù)時(shí)該任務(wù)修改隊(duì)列的等待接收和等待發(fā)送列表prvLockQueue( pxQueue ); //鎖定隊(duì)列,防止此時(shí)有中斷觸發(fā)修改隊(duì)列的等待接收和等待發(fā)送列表/* Update the timeout state to see if it has expired yet. */if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )//延時(shí)未到期且還未插進(jìn)去{if( prvIsQueueFull( pxQueue ) != pdFALSE )//如果隊(duì)列還是滿,就把未到期且未成功插入(等待插入)的任務(wù)放入 xTasksWaitingToSend 列表中{traceBLOCKING_ON_QUEUE_SEND( pxQueue );vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );//等待發(fā)送的任務(wù)放進(jìn)等待發(fā)送列表中/* Unlocking the queue means queue events can effect theevent list. It is possible that interrupts occurring nowremove this task from the event list again - but as thescheduler is suspended the task will go onto the pendingready last instead of the actual ready list. */prvUnlockQueue( pxQueue );//任務(wù)插入完成,解鎖中斷對(duì)兩個(gè)列表的操作,但此時(shí)調(diào)度器還是掛起的狀態(tài)/* Resuming the scheduler will move tasks from the pendingready list into the ready list - so it is feasible that thistask is already in a ready list before it yields - in whichcase the yield will not cause a context switch unless thereis also a higher priority task in the pending ready list. *///恢復(fù)任務(wù)調(diào)度器,根據(jù)返回值確定是否要進(jìn)行任務(wù)切換//如果掛起的就緒列表中有優(yōu)先級(jí)較高的任務(wù),恢復(fù)后調(diào)度器后才需要進(jìn)行任務(wù)切換if( xTaskResumeAll() == pdFALSE ){portYIELD_WITHIN_API();}}else//延時(shí)未到期但是隊(duì)列未滿時(shí),解除對(duì)隊(duì)列列表的鎖定從而可以再次嘗試插入隊(duì)列{/* Try again. */prvUnlockQueue( pxQueue );( void ) xTaskResumeAll();}}else//延時(shí)到期了還沒有插進(jìn)去,返回由于隊(duì)列滿插入失敗{/* The timeout has expired. */prvUnlockQueue( pxQueue );( void ) xTaskResumeAll();traceQUEUE_SEND_FAILED( pxQueue );return errQUEUE_FULL;}}
}
/*-----------------------------------------------------------*/
2. xQueueGenericSend() 函數(shù)流程圖
下面將對(duì) xQueueGenericSend() 中調(diào)用到的函數(shù)進(jìn)行詳細(xì)的說明:
- prvCopyDataToQueue() 消息拷貝到隊(duì)列函數(shù)
- vTaskSetTimeOutState() 超時(shí)結(jié)構(gòu)配置函數(shù)
- 掛起調(diào)度器函數(shù) vTaskSuspendAll() 和 隊(duì)列上鎖函數(shù) prvLockQueue() 的作用
- 檢查延時(shí)是否到期函數(shù) xTaskCheckForTimeOut()
- 將任務(wù)插入任務(wù)等待發(fā)送列表 vTaskPlaceOnEventList()
- 隊(duì)列解鎖函數(shù) prvUnlockQueue()
3. prvCopyDataToQueue() 函數(shù)說明
① 如何被調(diào)用
- 在 xQueueGenericSend() 中被調(diào)用
- 隊(duì)列未滿或者允許覆蓋寫入時(shí),使用 prvCopyDataToQueue() 將消息拷貝到隊(duì)列中:
if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) ){traceQUEUE_SEND( pxQueue );xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );}
② 參數(shù)說明
- prvCopyDataToQueue() 需要三個(gè)參數(shù):
- 指向操作的隊(duì)列指針
- 指向要拷貝到隊(duì)列中的消息指針
- 說明拷貝位置的參數(shù)(隊(duì)頭、隊(duì)尾、覆蓋)
- 插入隊(duì)尾時(shí)(xPosition == queueSEND_TO_BACK),為普通消息的發(fā)送,將消息拷貝到 pcWriteTo 所指向的消息單元后 pcWriteTo 自增,pcWriteTo 自增越界后返回 pcHead
else if( xPosition == queueSEND_TO_BACK ){( void ) memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( size_t ) pxQueue->uxItemSize );pxQueue->pcWriteTo += pxQueue->uxItemSize;if( pxQueue->pcWriteTo >= pxQueue->pcTail ){pxQueue->pcWriteTo = pxQueue->pcHead;}}
- 插入隊(duì)頭時(shí),為緊急消息的發(fā)送,將消息拷貝到 pcReadFrom 指向的消息單元,然后 pcReadFrom 自減一個(gè)單元。當(dāng) pcReadFrom 自減越界時(shí),重置為指向 pcTail 的前一個(gè)單元
( void ) memcpy( ( void * ) pxQueue->u.pcReadFrom, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); pxQueue->u.pcReadFrom -= pxQueue->uxItemSize;if( pxQueue->u.pcReadFrom < pxQueue->pcHead ) {pxQueue->u.pcReadFrom = ( pxQueue->pcTail - pxQueue->uxItemSize );}
- 覆蓋寫入時(shí)( xPosition == queueOVERWRITE ),即使隊(duì)列已滿也可以寫入隊(duì)列,這個(gè)功能旨在用于長度為 1 的隊(duì)列(來自 FreeRTOS 官方),插入的位置和插入隊(duì)頭的位置是一樣的
③ prvCopyDataToQueue() 函數(shù)完整代碼
static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue, const void *pvItemToQueue, const BaseType_t xPosition )
{
BaseType_t xReturn = pdFALSE;
UBaseType_t uxMessagesWaiting;/* This function is called from a critical section. */uxMessagesWaiting = pxQueue->uxMessagesWaiting;if( pxQueue->uxItemSize == ( UBaseType_t ) 0 ){#if ( configUSE_MUTEXES == 1 ){if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ){/* The mutex is no longer being held. */xReturn = xTaskPriorityDisinherit( ( void * ) pxQueue->pxMutexHolder );pxQueue->pxMutexHolder = NULL;}else{mtCOVERAGE_TEST_MARKER();}}#endif /* configUSE_MUTEXES */}else if( xPosition == queueSEND_TO_BACK ){( void ) memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); /*lint !e961 !e418 MISRA exception as the casts are only redundant for some ports, plus previous logic ensures a null pointer can only be passed to memcpy() if the copy size is 0. */pxQueue->pcWriteTo += pxQueue->uxItemSize;if( pxQueue->pcWriteTo >= pxQueue->pcTail ) /*lint !e946 MISRA exception justified as comparison of pointers is the cleanest solution. */{pxQueue->pcWriteTo = pxQueue->pcHead;}else{mtCOVERAGE_TEST_MARKER();}}else{( void ) memcpy( ( void * ) pxQueue->u.pcReadFrom, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */pxQueue->u.pcReadFrom -= pxQueue->uxItemSize;if( pxQueue->u.pcReadFrom < pxQueue->pcHead ) /*lint !e946 MISRA exception justified as comparison of pointers is the cleanest solution. */{pxQueue->u.pcReadFrom = ( pxQueue->pcTail - pxQueue->uxItemSize );}else{mtCOVERAGE_TEST_MARKER();}if( xPosition == queueOVERWRITE ){if( uxMessagesWaiting > ( UBaseType_t ) 0 ){/* An item is not being added but overwritten, so subtractone from the recorded number of items in the queue so whenone is added again below the number of recorded items remainscorrect. */--uxMessagesWaiting;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}pxQueue->uxMessagesWaiting = uxMessagesWaiting + 1;return xReturn;
}
/*-----------------------------------------------------------*/
4. 超時(shí)狀態(tài)結(jié)構(gòu)的配置 vTaskSetTimeOutState()
① 超時(shí)狀態(tài)結(jié)構(gòu)如何配置
- 在 xQueueGenericSend() 中被調(diào)用
- 消息空間已滿且不允許覆蓋寫入,并且阻塞時(shí)間大于 0 且未配置超時(shí)結(jié)構(gòu)時(shí)需要配置超時(shí)狀態(tài)結(jié)構(gòu):
vTaskSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
- 超時(shí)結(jié)構(gòu)的配置主要是 溢出次數(shù)、進(jìn)入阻塞時(shí)間的配置:
② vTaskSetTimeOutState() 函數(shù)定義
void vTaskSetTimeOutState( TimeOut_t * const pxTimeOut )
{configASSERT( pxTimeOut );pxTimeOut->xOverflowCount = xNumOfOverflows; //溢出次數(shù)pxTimeOut->xTimeOnEntering = xTickCount; //進(jìn)入時(shí)間 == 當(dāng)前時(shí)間
}
5. 掛起調(diào)度器和鎖上隊(duì)列的意義
① 如何操作
//掛起調(diào)度器 并 鎖定隊(duì)列的等待發(fā)送任務(wù)列表和等待接收任務(wù)列表:
//這兩個(gè)操作都是為了防止其它任務(wù)或者中斷操作 xTasksWaitingToReceive 列表 xTasksWaitingToSend 列表而引起優(yōu)先級(jí)翻轉(zhuǎn)
vTaskSuspendAll(); //暫停任務(wù)切換,防止在運(yùn)行其它任務(wù)時(shí)該任務(wù)修改隊(duì)列的等待接收和等待發(fā)送列表prvLockQueue( pxQueue ); //鎖定隊(duì)列,防止此時(shí)有中斷觸發(fā)修改隊(duì)列的等待接收和等待發(fā)送列表
② 意義
對(duì)于掛起調(diào)度器和鎖上隊(duì)列的意義,筆者思考了很久,如下:
掛起調(diào)度器 并 鎖定隊(duì)列的等待發(fā)送任務(wù)列表和等待接收任務(wù)列表:這兩個(gè)操作都是為了防止其它任務(wù)或者中斷操作 xTasksWaitingToReceive 列表和 xTasksWaitingToSend 列表而引起優(yōu)先級(jí)翻轉(zhuǎn)。
-
當(dāng)要使任務(wù)進(jìn)入阻塞時(shí),假設(shè)此任務(wù)(任務(wù)B)的優(yōu)先級(jí)比已經(jīng)在阻塞中的任務(wù)(任務(wù)A)優(yōu)先級(jí)高。
-
正常情況下,兩個(gè)任務(wù)都應(yīng)該在阻塞列表中等待操作,當(dāng)可以進(jìn)行隊(duì)列操作時(shí),應(yīng)該優(yōu)先解除優(yōu)先級(jí)高的任務(wù)B的阻塞并進(jìn)行操作。
-
但是在某種情況下,可能發(fā)生在設(shè)置任務(wù)B進(jìn)入阻塞的過程中,有其他任務(wù)或者中斷對(duì)等待接收和等待發(fā)送列表進(jìn)行了操作,使得可以進(jìn)行隊(duì)列操作。
- 正確的行為應(yīng)該是先解除優(yōu)先級(jí)高的任務(wù)B的阻塞,任務(wù)A繼續(xù)保持阻塞狀態(tài)等待入隊(duì)。
- 但是由于任務(wù)B還在設(shè)置進(jìn)入阻塞的過程中,阻塞列表中只有任務(wù)A,導(dǎo)致只能解除任務(wù)A的阻塞。這就導(dǎo)致了優(yōu)先級(jí)較低的任務(wù)A先于任務(wù)B解除阻塞,出現(xiàn)了優(yōu)先級(jí)翻轉(zhuǎn)。
-
為了防止這種情況發(fā)生,代碼中使用了兩個(gè)控制操作:
- vTaskSuspendAll() 函數(shù)暫停任務(wù)切換,即暫停調(diào)度器,防止在運(yùn)行其他任務(wù)時(shí)當(dāng)前任務(wù)修改隊(duì)列的等待接收和等待發(fā)送列表。
- prvLockQueue(pxQueue) 函數(shù)鎖定隊(duì)列,防止此時(shí)發(fā)生中斷并觸發(fā)了對(duì)等待接收和等待發(fā)送列表的修改。
通過掛起調(diào)度器和鎖定隊(duì)列,可以確保在任務(wù)B成功進(jìn)入阻塞狀態(tài)后,根據(jù)優(yōu)先級(jí)高低來解除任務(wù)阻塞,防止優(yōu)先級(jí)較低的任務(wù)A先于任務(wù)B解除阻塞,從而避免了優(yōu)先級(jí)翻轉(zhuǎn)的問題。
這樣的保護(hù)措施可以確保在任務(wù)切換及隊(duì)列操作過程中,保持任務(wù)的正確優(yōu)先級(jí)順序和避免優(yōu)先級(jí)翻轉(zhuǎn)對(duì)系統(tǒng)的影響。
③ 隊(duì)列上鎖代碼
掛起調(diào)度器的代碼在前面的文章中我們已經(jīng)介紹過,這里不再贅述。
【學(xué)習(xí)日記】【FreeRTOS】調(diào)度器函數(shù)實(shí)現(xiàn)詳解
此處介紹隊(duì)列上鎖代碼:
- 進(jìn)入臨界段
- 標(biāo)記隊(duì)列控制塊中的發(fā)送鎖和接收鎖
/** Macro to mark a queue as locked. Locking a queue prevents an ISR from* accessing the queue event lists.*/
#define prvLockQueue( pxQueue ) \taskENTER_CRITICAL(); \{ \if( ( pxQueue )->cRxLock == queueUNLOCKED ) \{ \( pxQueue )->cRxLock = queueLOCKED_UNMODIFIED; \} \if( ( pxQueue )->cTxLock == queueUNLOCKED ) \{ \( pxQueue )->cTxLock = queueLOCKED_UNMODIFIED; \} \} \taskEXIT_CRITICAL()
/*-----------------------------------------------------------*/
6. 檢查延時(shí)是否到期函數(shù) xTaskCheckForTimeOut()
① 使用
傳入超時(shí)狀態(tài)結(jié)構(gòu)和設(shè)置的超時(shí)時(shí)長進(jìn)行檢查:
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ){//...}
② 主要邏輯
- 當(dāng)使能了任務(wù)掛起且設(shè)定延時(shí)時(shí)間為 portMAX_DELAY 時(shí),任務(wù)將永遠(yuǎn)被阻塞不會(huì)超時(shí)
- 如果現(xiàn)在的時(shí)間比進(jìn)入延時(shí)的時(shí)間大 且 現(xiàn)在的時(shí)間計(jì)時(shí)溢出過,這意味著時(shí)基計(jì)數(shù)已經(jīng)跑過一輪了,那么延時(shí)肯定到期了
- 或者是當(dāng)前時(shí)間減進(jìn)入時(shí)間 < 需要等待的時(shí)間,意味著延時(shí)還未到期
- 現(xiàn)在需要等待的時(shí)間 = 原來需要等待的時(shí)間 - 已經(jīng)等待的時(shí)間
③ 源代碼
BaseType_t xTaskCheckForTimeOut( TimeOut_t * const pxTimeOut, TickType_t * const pxTicksToWait )
{
BaseType_t xReturn;configASSERT( pxTimeOut );configASSERT( pxTicksToWait );taskENTER_CRITICAL();{/* Minor optimisation. The tick count cannot change in this block. */const TickType_t xConstTickCount = xTickCount;#if( INCLUDE_xTaskAbortDelay == 1 )if( pxCurrentTCB->ucDelayAborted != pdFALSE ){/* The delay was aborted, which is not the same as a time out,but has the same result. */pxCurrentTCB->ucDelayAborted = pdFALSE;xReturn = pdTRUE;}else#endif#if ( INCLUDE_vTaskSuspend == 1 )if( *pxTicksToWait == portMAX_DELAY ){/* If INCLUDE_vTaskSuspend is set to 1 and the block timespecified is the maximum block time then the task should blockindefinitely, and therefore never time out. */xReturn = pdFALSE;}else#endifif( ( xNumOfOverflows != pxTimeOut->xOverflowCount ) && ( xConstTickCount >= pxTimeOut->xTimeOnEntering ) ) /*lint !e525 Indentation preferred as is to make code within pre-processor directives clearer. */{/* The tick count is greater than the time at whichvTaskSetTimeout() was called, but has also overflowed sincevTaskSetTimeOut() was called. It must have wrapped all the wayaround and gone past again. This passed since vTaskSetTimeout()was called. *///現(xiàn)在的時(shí)間比進(jìn)入延時(shí)的時(shí)間大 且 現(xiàn)在的時(shí)間計(jì)時(shí)溢出過//意味著時(shí)基計(jì)數(shù)已經(jīng)跑過一輪了,延時(shí)肯定到期了xReturn = pdTRUE; //延時(shí)肯定到期了}else if( ( ( TickType_t ) ( xConstTickCount - pxTimeOut->xTimeOnEntering ) ) < *pxTicksToWait ) /*lint !e961 Explicit casting is only redundant with some compilers, whereas others require it to prevent integer conversion errors. */{//當(dāng)前時(shí)間減進(jìn)入時(shí)間 < 需要等待的時(shí)間,意味著延時(shí)還未到期/* Not a genuine timeout. Adjust parameters for time remaining. *///現(xiàn)在需要等待的時(shí)間 = 原來需要等待的時(shí)間 - 已經(jīng)等待的時(shí)間 *pxTicksToWait -= ( xConstTickCount - pxTimeOut->xTimeOnEntering );vTaskSetTimeOutState( pxTimeOut );xReturn = pdFALSE; //延時(shí)沒有到期}else{xReturn = pdTRUE;}}taskEXIT_CRITICAL();return xReturn;
}
7. 將任務(wù)插入任務(wù)等待發(fā)送列表 vTaskPlaceOnEventList()
① 使用
用于將延時(shí)未到期且暫時(shí)無法入隊(duì)的任務(wù)插入任務(wù)等待列表:
- 傳入指向任務(wù)等待發(fā)送列表的指針
- 傳入要延時(shí)的時(shí)長
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );//等待發(fā)送的任務(wù)放進(jìn)等待發(fā)送列表中
② 函數(shù)定義
- 將當(dāng)前 TCB 的事件項(xiàng)目插入 xTasksWaitingToSend 標(biāo)識(shí)該任務(wù)正在等待發(fā)送消息
- 將當(dāng)前 TCB 插入延時(shí)列表中進(jìn)行阻塞延時(shí)
void vTaskPlaceOnEventList( List_t * const pxEventList, const TickType_t xTicksToWait )
{configASSERT( pxEventList );/* THIS FUNCTION MUST BE CALLED WITH EITHER INTERRUPTS DISABLED OR THESCHEDULER SUSPENDED AND THE QUEUE BEING ACCESSED LOCKED. *//* Place the event list item of the TCB in the appropriate event list.This is placed in the list in priority order so the highest priority taskis the first to be woken by the event. The queue that contains the eventlist is locked, preventing simultaneous access from interrupts. */vListInsert( pxEventList, &( pxCurrentTCB->xEventListItem ) );prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
}
/*-----------------------------------------------------------*/
8. 隊(duì)列解鎖函數(shù) prvUnlockQueue()
① 函數(shù)原型
/* Constants used with the cRxLock and cTxLock structure members. */
#define queueUNLOCKED ( ( int8_t ) -1 )
#define queueLOCKED_UNMODIFIED ( ( int8_t ) 0 )static void prvUnlockQueue( Queue_t * const pxQueue )
{/* THIS FUNCTION MUST BE CALLED WITH THE SCHEDULER SUSPENDED. *//* The lock counts contains the number of extra data items placed orremoved from the queue while the queue was locked. When a queue islocked items can be added or removed, but the event lists cannot beupdated. */taskENTER_CRITICAL();{int8_t cTxLock = pxQueue->cTxLock;/* See if data was added to the queue while it was locked. */while( cTxLock > queueLOCKED_UNMODIFIED ){/* Data was posted while the queue was locked. Are any tasksblocked waiting for data to become available? */#if ( configUSE_QUEUE_SETS == 1 ){if( pxQueue->pxQueueSetContainer != NULL ){if( prvNotifyQueueSetContainer( pxQueue, queueSEND_TO_BACK ) != pdFALSE ){/* The queue is a member of a queue set, and posting tothe queue set caused a higher priority task to unblock.A context switch is required. */vTaskMissedYield();}else{mtCOVERAGE_TEST_MARKER();}}else{/* Tasks that are removed from the event list will getadded to the pending ready list as the scheduler is stillsuspended. */if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ){if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){/* The task waiting has a higher priority so record that acontext switch is required. */vTaskMissedYield();}else{mtCOVERAGE_TEST_MARKER();}}else{break;}}}#else /* configUSE_QUEUE_SETS */{/* Tasks that are removed from the event list will get added tothe pending ready list as the scheduler is still suspended. */if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ){if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){/* The task waiting has a higher priority so record thata context switch is required. */vTaskMissedYield();}else{mtCOVERAGE_TEST_MARKER();}}else{break;}}#endif /* configUSE_QUEUE_SETS */--cTxLock;}pxQueue->cTxLock = queueUNLOCKED;}taskEXIT_CRITICAL();/* Do the same for the Rx lock. */taskENTER_CRITICAL();{int8_t cRxLock = pxQueue->cRxLock;while( cRxLock > queueLOCKED_UNMODIFIED ){if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE ){if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE ){vTaskMissedYield();}else{mtCOVERAGE_TEST_MARKER();}--cRxLock;}else{break;}}pxQueue->cRxLock = queueUNLOCKED;}taskEXIT_CRITICAL();
}
/*-----------------------------------------------------------*/
② 函數(shù)結(jié)構(gòu)分析
這段代碼是 FreeRTOS 中的 prvUnlockQueue 函數(shù)的實(shí)現(xiàn)。以下是函數(shù)執(zhí)行的主要步驟:
- 進(jìn)入臨界區(qū)。
- 檢查發(fā)送鎖的計(jì)數(shù)器 cTxLock,如果大于 queueLOCKED_UNMODIFIED(表示有鎖被添加或刪除),則執(zhí)行以下步驟:
a. 如果隊(duì)列是隊(duì)列集的成員,且添加數(shù)據(jù)導(dǎo)致更高優(yōu)先級(jí)的任務(wù)解除阻塞,則記錄需要進(jìn)行任務(wù)切換。
b. 否則,從等待接收任務(wù)列表中移除任務(wù),并記錄需要進(jìn)行任務(wù)切換。
c. 遞減 cTxLock 計(jì)數(shù)器。 - 將發(fā)送鎖設(shè)置為解鎖狀態(tài)。
- 再次進(jìn)入臨界區(qū)。
- 檢查接收鎖的計(jì)數(shù)器 cRxLock,如果大于 queueLOCKED_UNMODIFIED(表示有鎖被添加或刪除),則執(zhí)行以下步驟:
a. 從等待發(fā)送任務(wù)列表中移除任務(wù),并記錄需要進(jìn)行任務(wù)切換。
b. 遞減 cRxLock 計(jì)數(shù)器。 - 將接收鎖設(shè)置為解鎖狀態(tài)。
- 離開臨界區(qū)。
可以看到,忽略鎖的計(jì)數(shù)器操作(鎖的計(jì)數(shù)器主要用于對(duì)隊(duì)列的并發(fā)訪問),3 和 6 說明只要調(diào)用這個(gè)函數(shù),隊(duì)列的等待發(fā)送任務(wù)列表和等待接收任務(wù)列表都會(huì)解鎖。
③ 計(jì)數(shù)鎖的原理
在FreeRTOS中,隊(duì)列的cRxLock
大于0的情況是在調(diào)用接收數(shù)據(jù)的API(比如xQueueReceive()
函數(shù))時(shí)。這是由于隊(duì)列需要在多任務(wù)環(huán)境中保持線程安全性。當(dāng)一個(gè)任務(wù)正在從隊(duì)列接收數(shù)據(jù)時(shí),會(huì)通過增加cRxLock
的計(jì)數(shù)來鎖定隊(duì)列,防止其他任務(wù)同時(shí)進(jìn)行接收操作。
具體來說,以下情況會(huì)使得cRxLock
大于0:
- 任務(wù) A 調(diào)用了接收數(shù)據(jù)的 API,隊(duì)列的
cRxLock
值增加到 1。 - 在任務(wù) A 還未完成接收操作之前,另一個(gè)任務(wù) B 也調(diào)用了接收數(shù)據(jù)的 API,此時(shí)
cRxLock
值增加到 2。 - 當(dāng)任務(wù) A 完成接收操作后,
cRxLock
值減少到 1。 - 當(dāng)任務(wù) B 完成接收操作后,
cRxLock
值減少到 0。
這個(gè)鎖定機(jī)制確保了隊(duì)列在多任務(wù)環(huán)境中的正確操作,避免了競(jìng)態(tài)條件的發(fā)生。
四、用于中斷中的 通用的消息發(fā)送函數(shù) xQueueGenericSendFromISR()
上述的任務(wù)中的消息發(fā)送函數(shù) API 經(jīng)過宏定義展開后實(shí)際上就是傳入不同參數(shù)的 xQueueGenericSendFromISR(),下面將對(duì)這個(gè)函數(shù)進(jìn)行詳細(xì)的介紹。
1. 函數(shù)概要
- 這個(gè)函數(shù)用于在中斷中向隊(duì)列發(fā)送消息,由于是在中斷中使用的,所以不帶阻塞機(jī)制
- 隊(duì)列未滿時(shí)或者可以覆蓋寫入時(shí)直接進(jìn)行消息拷貝,否則直接返回隊(duì)列已滿錯(cuò)誤
- 因?yàn)槭窃谥袛嘀袌?zhí)行,需要對(duì)隊(duì)列發(fā)送鎖進(jìn)行判斷和操作
- 如果隊(duì)列發(fā)送鎖是解鎖狀態(tài),表示沒有任務(wù)正在進(jìn)行隊(duì)列的發(fā)送操作,因此其他任務(wù)可以嘗試從阻塞狀態(tài)中恢復(fù),并依據(jù)優(yōu)先級(jí)高低進(jìn)行上下文切換
- 如果隊(duì)列發(fā)送鎖是上鎖狀態(tài),每調(diào)用一次 xQueueGenericSendFromISR(),隊(duì)列發(fā)送鎖就自增 1,任務(wù)需要等待鎖釋放才能進(jìn)行隊(duì)列的發(fā)送操作,隊(duì)列發(fā)送鎖記錄隊(duì)列上鎖時(shí)有多少數(shù)據(jù)嘗試入隊(duì)
- 因?yàn)槭窃谥袛嘀袌?zhí)行,需要對(duì)隊(duì)列發(fā)送鎖進(jìn)行判斷和操作
2. 函數(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;configASSERT( pxQueue );configASSERT( !( ( pvItemToQueue == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );configASSERT( !( ( xCopyPosition == queueOVERWRITE ) && ( pxQueue->uxLength != 1 ) ) );/* RTOS ports that support interrupt nesting have the concept of a maximumsystem call (or maximum API call) interrupt priority. Interrupts that areabove the maximum system call priority are kept permanently enabled, evenwhen the RTOS kernel is in a critical section, but cannot make any calls toFreeRTOS API functions. If configASSERT() is defined in FreeRTOSConfig.hthen portASSERT_IF_INTERRUPT_PRIORITY_INVALID() will result in an assertionfailure if a FreeRTOS API function is called from an interrupt that has beenassigned a priority above the configured maximum system call priority.Only FreeRTOS functions that end in FromISR can be called from interruptsthat have been assigned a priority at or (logically) below the maximumsystem call interrupt priority. FreeRTOS maintains a separate interruptsafe API to ensure interrupt entry is as fast and as simple as possible.More information (albeit Cortex-M specific) is provided on the followinglink: http://www.freertos.org/RTOS-Cortex-M3-M4.html */portASSERT_IF_INTERRUPT_PRIORITY_INVALID();/* Similar to xQueueGenericSend, except without blocking if there is no roomin the queue. Also don't directly wake a task that was blocked on a queueread, instead return a flag to say whether a context switch is required ornot (i.e. has a task with a higher priority than us been woken by thispost). */uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();{if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) ){const int8_t cTxLock = pxQueue->cTxLock;traceQUEUE_SEND_FROM_ISR( pxQueue );/* Semaphores use xQueueGiveFromISR(), so pxQueue will not be asemaphore or mutex. That means prvCopyDataToQueue() cannot resultin a task disinheriting a priority and prvCopyDataToQueue() can becalled here even though the disinherit function does not check ifthe scheduler is suspended before accessing the ready lists. */( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );/* The event list is not altered if the queue is locked. This willbe done when the queue is unlocked later. */if( cTxLock == queueUNLOCKED ){#if ( configUSE_QUEUE_SETS == 1 ){if( pxQueue->pxQueueSetContainer != NULL ){if( prvNotifyQueueSetContainer( pxQueue, xCopyPosition ) != pdFALSE ){/* The queue is a member of a queue set, and postingto the queue set caused a higher priority task tounblock. A context switch is required. */if( pxHigherPriorityTaskWoken != NULL ){*pxHigherPriorityTaskWoken = pdTRUE;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}else{if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ){if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){/* The task waiting has a higher priority sorecord that a context switch is required. */if( pxHigherPriorityTaskWoken != NULL ){*pxHigherPriorityTaskWoken = pdTRUE;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}}#else /* configUSE_QUEUE_SETS */{if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ){if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){/* The task waiting has a higher priority so record that acontext switch is required. */if( pxHigherPriorityTaskWoken != NULL ){*pxHigherPriorityTaskWoken = pdTRUE;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}#endif /* configUSE_QUEUE_SETS */}else{/* Increment the lock count so the task that unlocks the queueknows that data was posted while it was locked. */pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 );}xReturn = pdPASS;}else{traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue );xReturn = errQUEUE_FULL;}}portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );return xReturn;
}
/*-----------------------------------------------------------*/
五、消息讀取 API
1. 簡介
- 任務(wù)中讀取消息時(shí),也可以指定阻塞超時(shí)時(shí)間,在隊(duì)列中沒有消息可供讀取時(shí)對(duì)任務(wù)進(jìn)行阻塞。隊(duì)列中存入數(shù)據(jù)后自動(dòng)轉(zhuǎn)為就緒態(tài)進(jìn)行數(shù)據(jù)讀取或者等到超時(shí)后變回就緒態(tài)
- 而中斷中讀取消息時(shí),就不能指定阻塞機(jī)制,但同樣可以輸入一個(gè)提示是否進(jìn)行上下文切換的參數(shù)來進(jìn)行上下文的切換判斷
- 讀取消息的函數(shù)分為讀完將消息從隊(duì)列中刪除和讀完后將消息放回隊(duì)列中原位的兩種函數(shù)
2. xQueueReceive()
① 原型及作用
- 用于從一個(gè)隊(duì)列中接收消息并把消息從隊(duì)列中刪除
- 接收的消息是以拷貝的形式進(jìn)行的
BaseType_t xQueueReceive(QueueHandle_t xQueue,void *pvBuffer,TickType_t xTicksToWait);
- xQueue 隊(duì)列句柄
- pvBuffer 指針,指向接收到要保存的數(shù)據(jù)
- xTicksToWait 隊(duì)列空時(shí),阻塞超時(shí)的最大時(shí)間。如果該參數(shù)設(shè)置為 0,函數(shù)立刻返回。超時(shí)時(shí)間的單位為系統(tǒng)節(jié)拍周期,常量 portTICK_PERIOD_MS 用于輔助計(jì)算真實(shí)的時(shí)間,單位為 ms。如果 INCLUDE_vTaskSuspend 設(shè)置成 1,并且指定延時(shí)為 portMAX_DELAY 將導(dǎo)致任務(wù)無限阻塞(永遠(yuǎn)不會(huì)超時(shí))
② 宏展開
#define xQueueReceive( xQueue, pvBuffer, xTicksToWait ) \xQueueGenericReceive( ( xQueue ), ( pvBuffer ), \( xTicksToWait ), pdFALSE )
③ 應(yīng)用
static void Receive_Task(void* parameter)
{BaseType_t xReturn = pdTRUE; /* 定義一個(gè)創(chuàng)建信息返回值,默認(rèn)為pdPASS */uint32_t r_queue; /* 定義一個(gè)接收消息的變量 */while (1) {xReturn = xQueueReceive(Test_Queue, /* 消息隊(duì)列的句柄 */&r_queue, /* 接收的消息內(nèi)容 */portMAX_DELAY); /* 等待時(shí)間 一直等 */if (pdTRUE == xReturn)printf("本次接收到的數(shù)據(jù)是:%d\n\n", r_queue);elseprintf("數(shù)據(jù)接收出錯(cuò),錯(cuò)誤代碼: 0x%lx\n", xReturn);}
}
3. xQueuePeek()
① 作用
xQueuePeek()函數(shù)接收消息完畢不會(huì)刪除消息隊(duì)列中的消息。
② 宏展開
#define xQueuePeek( xQueue, pvBuffer, xTicksToWait ) \xQueueGenericReceive( ( xQueue ), ( pvBuffer ), \( xTicksToWait ), pdTRUE )
4. xQueueReceiveFromISR() 與 xQueuePeekFromISR()
- 類同于上文 API,但是只能在中斷中使用
- 隊(duì)列項(xiàng)接收成功返回 pdTRUE,否則返回 pdFALSE
① xQueueReceiveFromISR() 使用示例
QueueHandle_t xQueue;/* 創(chuàng)建一個(gè)隊(duì)列,并往隊(duì)列里面發(fā)送一些數(shù)據(jù) */
void vAFunction(void *pvParameters)
{char cValueToPost;const TickType_t xTicksToWait = (TickType_t)0xff;/* 創(chuàng)建一個(gè)可以容納10個(gè)字符的隊(duì)列 */xQueue = xQueueCreate(10, sizeof(char));if (xQueue == 0) {/* 隊(duì)列創(chuàng)建失敗 */}/* ... 任務(wù)其他代碼 *//* 往隊(duì)列里面發(fā)送兩個(gè)字符如果隊(duì)列滿了則等待xTicksToWait個(gè)系統(tǒng)節(jié)拍周期*/cValueToPost = 'a';xQueueSend(xQueue, (void *)&cValueToPost, xTicksToWait);cValueToPost = 'b';xQueueSend(xQueue, (void *)&cValueToPost, xTicksToWait);/* 繼續(xù)往隊(duì)列里面發(fā)送字符當(dāng)隊(duì)列滿的時(shí)候該任務(wù)將被阻塞 */cValueToPost = 'c';xQueueSend(xQueue, (void *)&cValueToPost, xTicksToWait);
}/* 中斷服務(wù)程序:輸出所有從隊(duì)列中接收到的字符 */
void vISR_Routine(void)
{BaseType_t xTaskWokenByReceive = pdFALSE;char cRxedChar;while (xQueueReceiveFromISR(xQueue,(void *)&cRxedChar,&xTaskWokenByReceive)) {/* 接收到一個(gè)字符,然后輸出這個(gè)字符 */vOutputCharacter(cRxedChar);/* 如果從隊(duì)列移除一個(gè)字符串后喚醒了向此隊(duì)列投遞字符的任務(wù),那么參數(shù)xTaskWokenByReceive將會(huì)設(shè)置成pdTRUE,這個(gè)循環(huán)無論重復(fù)多少次,僅會(huì)有一個(gè)任務(wù)被喚醒 */}if (xTaskWokenByReceive != pdFALSE) {/* 我們應(yīng)該進(jìn)行一次上下文切換,當(dāng)ISR返回的時(shí)候則執(zhí)行另外一個(gè)任務(wù) *//* 這是一個(gè)上下文切換的宏,不同的處理器,具體處理的方式不一樣 */taskYIELD();}
}
六、通用讀取消息函數(shù) xQueueGenericReceive()
1. 函數(shù)結(jié)構(gòu)分析
這段代碼是FreeRTOS中的xQueueGenericReceive函數(shù)的實(shí)現(xiàn)。以下是函數(shù)執(zhí)行的主要步驟:
- 檢查輸入?yún)?shù)的有效性。
- 進(jìn)入臨界區(qū)。
- 檢查隊(duì)列中是否有數(shù)據(jù)。
- 如果有數(shù)據(jù),則:
- 如果只是查看數(shù)據(jù)(xJustPeeking為pdTRUE),則將數(shù)據(jù)復(fù)制到pvBuffer,并恢復(fù)讀指針,然后從等待接收任務(wù)列表中移除任務(wù)。
- 如果要?jiǎng)h除數(shù)據(jù)(xJustPeeking為pdFALSE),則將數(shù)據(jù)復(fù)制到pvBuffer,并更新隊(duì)列中的消息計(jì)數(shù)、處理互斥(如果隊(duì)列類型是互斥),并從等待發(fā)送任務(wù)列表中移除任務(wù)。
- 退出臨界區(qū),返回pdPASS表示成功。
- 如果隊(duì)列為空且等待時(shí)間為0,則退出臨界區(qū),返回errQUEUE_EMPTY表示隊(duì)列為空。
- 如果隊(duì)列為空且需要等待一段時(shí)間,則設(shè)置超時(shí)結(jié)構(gòu)體xTimeOut,并繼續(xù)循環(huán)。
- 如果有數(shù)據(jù),則:
- 退出臨界區(qū)。
- 掛起調(diào)度器,并鎖定隊(duì)列。
- 更新超時(shí)狀態(tài),并檢查超時(shí)時(shí)間是否已過期。
- 如果超時(shí)時(shí)間未過期且隊(duì)列仍為空,則:
- 如果隊(duì)列是互斥隊(duì)列類型,則提升互斥的持有任務(wù)的優(yōu)先級(jí)。
- 將任務(wù)添加到等待接收任務(wù)列表中,并解鎖隊(duì)列。
- 恢復(fù)調(diào)度器狀態(tài),如果返回pdFALSE則進(jìn)行任務(wù)切換。
- 如果超時(shí)時(shí)間已過期,則返回errQUEUE_EMPTY表示隊(duì)列為空。
- 如果隊(duì)列非空,則繼續(xù)循環(huán)。
- 如果超時(shí)時(shí)間未過期且隊(duì)列仍為空,則:
- 循環(huán)直到成功或超時(shí)。
總之,這個(gè)函數(shù)的主要功能是從隊(duì)列中接收數(shù)據(jù),并在數(shù)據(jù)不可用時(shí)進(jìn)行阻塞等待,直到有數(shù)據(jù)可用或超時(shí)。它還處理了互斥隊(duì)列和任務(wù)優(yōu)先級(jí)的協(xié)調(diào)。
2. 函數(shù)定義
BaseType_t xQueueGenericReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait, const BaseType_t xJustPeeking )
{
BaseType_t xEntryTimeSet = pdFALSE;
TimeOut_t xTimeOut;
int8_t *pcOriginalReadPosition;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;configASSERT( pxQueue );configASSERT( !( ( pvBuffer == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) ){configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );}#endif/* This function relaxes the coding standard somewhat to allow returnstatements within the function itself. This is done in the interestof execution time efficiency. */for( ;; ){taskENTER_CRITICAL();{const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;/* Is there data in the queue now? To be running the calling taskmust be the highest priority task wanting to access the queue. */if( uxMessagesWaiting > ( UBaseType_t ) 0 ){/* Remember the read position in case the queue is only beingpeeked. */pcOriginalReadPosition = pxQueue->u.pcReadFrom;prvCopyDataFromQueue( pxQueue, pvBuffer );if( xJustPeeking == pdFALSE ){traceQUEUE_RECEIVE( pxQueue );/* Actually removing data, not just peeking. */pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1;#if ( configUSE_MUTEXES == 1 ){if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ){/* Record the information required to implementpriority inheritance should it become necessary. */pxQueue->pxMutexHolder = ( int8_t * ) pvTaskIncrementMutexHeldCount(); /*lint !e961 Cast is not redundant as TaskHandle_t is a typedef. */}else{mtCOVERAGE_TEST_MARKER();}}#endif /* configUSE_MUTEXES */if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE ){if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE ){queueYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}else{traceQUEUE_PEEK( pxQueue );/* The data is not being removed, so reset the readpointer. */pxQueue->u.pcReadFrom = pcOriginalReadPosition;/* The data is being left in the queue, so see if there areany other tasks waiting for the data. */if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ){if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){/* The task waiting has a higher priority than this task. */queueYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}taskEXIT_CRITICAL();return pdPASS;}else{if( xTicksToWait == ( TickType_t ) 0 ){/* The queue was empty and no block time is specified (orthe block time has expired) so leave now. */taskEXIT_CRITICAL();traceQUEUE_RECEIVE_FAILED( pxQueue );return errQUEUE_EMPTY;}else if( xEntryTimeSet == pdFALSE ){/* The queue was empty and a block time was specified soconfigure the timeout structure. */vTaskSetTimeOutState( &xTimeOut );xEntryTimeSet = pdTRUE;}else{/* Entry time was already set. */mtCOVERAGE_TEST_MARKER();}}}taskEXIT_CRITICAL();/* Interrupts and other tasks can send to and receive from the queuenow the critical section has been exited. */vTaskSuspendAll();prvLockQueue( pxQueue );/* Update the timeout state to see if it has expired yet. */if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ){if( prvIsQueueEmpty( pxQueue ) != pdFALSE ){traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );#if ( configUSE_MUTEXES == 1 ){if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ){taskENTER_CRITICAL();{vTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );}taskEXIT_CRITICAL();}else{mtCOVERAGE_TEST_MARKER();}}#endifvTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );prvUnlockQueue( pxQueue );if( xTaskResumeAll() == pdFALSE ){portYIELD_WITHIN_API();}else{mtCOVERAGE_TEST_MARKER();}}else{/* Try again. */prvUnlockQueue( pxQueue );( void ) xTaskResumeAll();}}else{prvUnlockQueue( pxQueue );( void ) xTaskResumeAll();if( prvIsQueueEmpty( pxQueue ) != pdFALSE ){traceQUEUE_RECEIVE_FAILED( pxQueue );return errQUEUE_EMPTY;}else{mtCOVERAGE_TEST_MARKER();}}}
}
/*-----------------------------------------------------------*/
四、性能提示
無論是發(fā)送或者是接收消息都是以拷貝的方式進(jìn)行,如果消息過于龐大,可以將消息的地址作為消息進(jìn)行發(fā)送、接收。
后記
如果您覺得本文寫得不錯(cuò),可以點(diǎn)個(gè)贊激勵(lì)一下作者!
如果您發(fā)現(xiàn)本文的問題,歡迎在評(píng)論區(qū)或者私信共同探討!
共勉!