前言
本文學習RT-Thread的消息隊列,支持不定長度消息的收發,涉及消息隊列的工作機制、消息隊列相關函數以及基于STM32的RT-Thread消息隊列應用示例,采用RTT&正點原子聯合出品潘多拉開發板進行實驗,基于STM32L475VET6。
一、消息隊列的工作機制
消息隊列能夠接收來自線程或中斷服務例程中不固定長度的消息,并把消息緩存在自己的內存空間中。其他線程也能夠從消息隊列中讀取相應的消息,而當消息隊列是空的時候,可以掛起讀取線程。當有新的消息到達時,掛起的線程將被喚醒以接收并處理消息。消息隊列是一種異步的通信方式。
如下圖所示,線程或中斷服務例程可以將一條或多條消息放入消息隊列中。同樣,一個或多個線程也可以從消息隊列中獲得消息。當有多個消息發送到消息隊列時,通常將先進入消息隊列的消息先傳給線程,也就是說,線程先得到的是最先進入消息隊列的消息,即先進先出原則 (FIFO)。
消息隊列工作示意圖(來源RT-Thread編程指南)
二、消息隊列的相關函數
1、創建動態消息隊列函數:創建動態消息隊列時先從對象管理器中分配一個消息隊列對象,然后給消息隊列對象分配一塊內存空間,組織成空閑消息鏈表,這塊內存的大小 =[消息大小 + 消息頭(用于鏈表連接)的大小]X 消息隊列最大個數,接著再初始化消息隊列,此時消息隊列為空。創建動態消息隊列的函數接口如下所示:
1rt_mq_trt_mq_create(constchar*name,2rt_size_tmsg_size,3rt_size_tmax_msgs,4rt_uint8_tflag);
(1)入口參數:
name:消息隊列的名稱。msg_size:消息隊列中一條消息的最大長度,單位字節。max_msgs:消息隊列的最大個數。flag:消息隊列采用的等待方式,它可以取如下數值:RT_IPC_FLAG_FIFO或RT_IPC_FLAG_PRIO。
(2)返回值:
RT_EOK:發送成功 消息隊列對象的句柄成功。RT_NULL:失敗。
2、刪除動態消息隊列函數:當動態消息隊列不再被使用時,應該刪除它以釋放系統資源,一旦操作完成,動態消息隊列將被永久性地刪除。刪除動態消息隊列時,如果有線程被掛起在該消息隊列等待隊列上,則內核先喚醒掛起在該消息等待隊列上的所有線程(線程返回值是 - RT_ERROR),然后再釋放消息隊列使用的內存,最后刪除消息隊列對象。函數接口如下:
1rt_err_trt_mq_delete(rt_mq_tmq);
(1)入口參數:
mq:消息隊列對象的句柄。
(2)返回值:
RT_EOK:成功。
3、創建靜態消息隊列函數:創建靜態消息隊列和《RT-Thread編程指南》所講的初始化靜態消息隊列是一樣的,靜態消息隊列對象的內存是在系統編譯時由編譯器分配的,一般放于讀數據段或未初始化數據段中。消息隊列初始化后所有消息都掛在空閑消息鏈表上,消息隊列為空,函數接口如下:
1rt_err_trt_mq_init(rt_mq_tmq,2constchar*name,3void*msgpool,4rt_size_tmsg_size,5rt_size_tpool_size,6rt_uint8_tflag);
(1)入口參數:
mq:消息隊列對象的句柄。name:消息隊列的名稱。msgpool:指向存放消息的緩沖區的指針。msg_size:消息隊列中一條消息的最大長度,單位字節。pool_size:存放消息的緩沖區大小。flag:消息隊列采用的等待方式,它可以取如下數值:RT_IPC_FLAG_FIFO 或RT_IPC_FLAG_PRIO。
(2)返回值:
RT_EOK:成功。
4、刪除靜態消息隊列函數:刪除靜態消息隊列和《RT-Thread編程指南》所講的脫離消息隊列是一樣的,脫離消息隊列將使消息隊列對象被從內核對象管理器中脫離。內核先喚醒所有掛在該消息等待隊列對象上的線程(線程返回值是 RT_ERROR),然后將該消息隊列對象從內核對象管理器中脫離。函數接口如下:
1rt_err_trt_mq_detach(rt_mq_tmq);
(1)入口參數:
mq:消息隊列對象的句柄。
(2)返回值:
RT_EOK:成功。
5、發送消息函數:線程或者中斷服務程序都可以給消息隊列發送消息。當發送消息時,消息隊列對象先從空閑消息鏈表上取下一個空閑消息塊,把線程或者中斷服務程序發送的消息內容復制到消息塊上,然后把該消息塊掛到消息隊列的尾部。當且僅當空閑消息鏈表上有可用的空閑消息塊時,發送者才能成功發送消息;當空閑消息鏈表上無可用消息塊,說明消息隊列已滿,此時,發送消息的的線程或者中斷程序會收到一個錯誤碼(-RT_EFULL)。在發送一個普通消息之后,空閑消息鏈表上的隊首消息被轉移到了消息隊列尾。函數接口如下:
1rt_err_trt_mq_send(rt_mq_tmq,void*buffer,rt_size_tsize);
(1)入口參數:
mq:消息隊列對象的句柄。buffer:消息內容。size:消息大小。
(2)返回值:
RT_EOK:成功。RT_EFULL:消息隊列已滿。RT_ERROR:失敗,表示發送的消息長度大于消息隊列中消息的最大長度。
6、發送緊急消息函數:發送緊急消息的過程與發送消息幾乎一樣,唯一的不同是,當發送緊急消息時,從空閑消息鏈表上取下來的消息塊不是掛到消息隊列的隊尾,而是掛到隊首,這樣,接收者就能夠優先接收到緊急消息,從而及時進行消息處理。發送緊急消息的函數接口如下:
1rt_err_trt_mq_urgent(rt_mq_tmq,void*buffer,rt_size_tsize);
(1)入口參數:
mq:消息隊列對象的句柄。buffer:消息內容。size:消息大小。
(2)返回值:
RT_EOK:成功。RT_EFULL:消息隊列已滿。RT_ERROR:失敗。
7、接收消息函數:當消息隊列中有消息時,接收者才能接收消息,否則接收者會根據超時時間設置,或掛起在消息隊列的等待線程隊列上,或直接返回。接收消息時,接收者需指定存儲消息的消息隊列對象句柄,并且指定一個內存緩沖區,接收到的消息內容將被復制到該緩沖區里。此外,還需指定未能及時取到消息時的超時時間。接收一個消息后消息隊列上的隊首消息被轉移到了空閑消息鏈表的尾部。函數接口如下:
1rt_err_trt_mq_recv(rt_mq_tmq,2void*buffer,3rt_size_tsize,4rt_int32_ttimeout);
(1)入口參數:
mq:消息隊列對象的句柄。buffer:消息內容。size:消息大小。timeout:指定的超時時間。
(2)返回值:
RT_EOK:成功收到。RT_ETIMEOUT:超時。RT_ERROR:失敗,返回錯誤。
三、基于STM32的消息隊列應用示例
前將了很多消息隊列的理論知識,光說不練都是假把式,那么接下來我們就進行實際操作,基于潘多拉開發板進行實驗。創建一個消息隊列和兩個線程,其中一個線程用于發送消息和發送緊急消息,另外一個線程用于接收消息。通過按下按鍵KEY0發送消息msg1,按下按鍵KEY1發送緊急消息msg2,按下按鍵KEY2接收消息,接收到消息后打印出來。
1、實現代碼:
1/*線程句柄*/ 2staticrt_thread_tthread1=RT_NULL; 3staticrt_thread_tthread2=RT_NULL; 4 5/*消息隊列句柄*/ 6staticrt_mq_tmsgqueue1=RT_NULL; 7 8 9/************************************************************** 10函數名稱:thread1_recv_msgqueue 11函數功能:線程1入口函數,用于接收消息 12輸入參數:parameter:入口參數 13返回值:無 14備注:無 15**************************************************************/ 16voidthread1_recv_msgqueue(void*parameter) 17{ 18u8key; 19charbuf[64]; 20 21while(1) 22{ 23key=key_scan(0); 24 25if(key==KEY2_PRES) 26{ 27if(rt_mq_recv(msgqueue1,buf,sizeof(buf),RT_WAITING_FOREVER)==RT_EOK) 28{ 29rt_kprintf("recvmsg:%s\r\n",buf); 30} 31else 32{ 33rt_kprintf("recvmsgfailed\r\n"); 34} 35} 36 37rt_thread_mdelay(1); 38} 39} 40 41/************************************************************** 42函數名稱:thread2_send_mailbox_msg 43函數功能:線程2入口函數,用于發送消息 44輸入參數:parameter:入口參數 45返回值:無 46備注:無 47**************************************************************/ 48voidthread2_send_msgqueue(void*parameter) 49{ 50u8key; 51rt_err_tres; 52 53while(1) 54{ 55key=key_scan(0); 56 57if(key==KEY0_PRES) 58{ 59res=rt_mq_send(msgqueue1,"msg1",sizeof("msg1")); 60 61if(res==RT_EOK) 62{ 63rt_kprintf("msgqueuesendmsg1successful\r\n"); 64} 65else 66{ 67rt_kprintf("msgqueuesendmsg1failed\r\n"); 68} 69} 70elseif(key==KEY1_PRES) 71{ 72rt_mq_urgent(msgqueue1,"msg2",sizeof("msg2")); 73 74if(res==RT_EOK) 75{ 76rt_kprintf("msgqueuesendmsg2successful\r\n"); 77} 78else 79{ 80rt_kprintf("msgqueuesendmsg2failed\r\n"); 81} 82} 83 84rt_thread_mdelay(1); 85} 86} 87 88 89voidrtthread_msgqueue_test(void) 90{ 91msgqueue1=rt_mq_create("msgqueue1", 9264,/*消息最大長度*/ 9310,/*消息隊列最大容量*/ 94RT_IPC_FLAG_FIFO);/*FIFO模式*/ 95 96if(msgqueue1!=RT_NULL) 97{ 98rt_kprintf("RT-Threadcreatemsgqueuesuccessful\r\n"); 99}100else101{102rt_kprintf("RT-Threadcreatemsgqueuefailed\r\n");103return;104}105106thread1=rt_thread_create("thread1",107thread1_recv_msgqueue,108NULL,109512,1103,11120);112113if(thread1!=RT_NULL)114{115rt_thread_startup(thread1);;116}117else118{119rt_kprintf("createthread1failed\r\n");120return;121}122123thread2=rt_thread_create("thread2",124thread2_send_msgqueue,125NULL,126512,1272,12820);129130if(thread2!=RT_NULL)131{132rt_thread_startup(thread2);;133}134else135{136rt_kprintf("createthread2failed\r\n");137return;138}139140}
2、觀察FinSH:
(1)開機,按下KEY2接收消息,沒有任何反應,為什么呢?因為此時消息隊列為空,還沒有任何消息,輸入list_msgqueue可查看當前的消息隊列有msgqueue1,消息隊列里面有0條消息,以及掛起等待消息的線程為thread1:
(2)按下KEY0發送消息msg1,提示發送成功以及打印出接收到msg1的內容(因為前面步驟(1)我們已經先按下KEY2然后thread1掛起等待接收消息了,所有為直接打印出msg1):
(3)接著連續按下10次KEY0發送消息msg1,當按下第11次時,發送消息msg1失敗,因為初始化消息隊列時最大只有支持10條消息:
(4)連續按下10次KEY2接收消息:
(5)先按下兩次KEY0發送兩次消息msg1,再按下一次KEY1發送一次緊急消息msg2:
(6)連續按下3次KEY2接收消息,先接收到的是一條msg2,再是兩條msg1(因為前面步驟2按下KEY1是發送緊急消息,所以先接收到的是msg2):
四、消息隊列的使用場合
消息隊列可以應用于發送不定長消息的場合,包括線程與線程間的消息交換,以及中斷服務例程中給線程發送消息(中斷服務例程不能接收消息)。
1、發送消息
(1)消息隊列和郵箱的明顯不同是消息的長度并不限定在 4 個字節以內;另外,消息隊列也包括了一個發送緊急消息的函數接口。但是當創建的是一個所有消息的最大長度是 4 字節的消息隊列時,消息隊列對象將蛻化成郵箱。這個不限定長度的消息,也及時的反應到了代碼編寫的場合上,同樣是類似郵箱的代碼:
1structmsg2{3rt_uint8_t*data_ptr;/*數據塊首地址*/4rt_uint32_tdata_size;/*數據塊大小*/5};
(2)和郵箱例子相同的消息結構定義,假設依然需要發送這樣一個消息給接收線程。在郵箱例子中,這個結構只能夠發送指向這個結構的指針(在函數指針被發送過去后,接收線程能夠正確的訪問指向這個地址的內容,通常這塊數據需要留給接收線程來釋放)。而使用消息隊列的方式則大不相同:
1voidsend_op(void*data,rt_size_tlength)2{3structmsgmsg_ptr;4msg_ptr.data_ptr=data;/*指向相應的數據塊地址*/5msg_ptr.data_size=length;/*數據塊的長度*/6/*發送這個消息指針給mq消息隊列*/7rt_mq_send(mq,(void*)&msg_ptr,sizeof(structmsg));8}
(3)注意,上面的代碼中,是把一個局部變量的數據內容發送到了消息隊列中。在接收線程中,同樣也采用局部變量進行消息接收的結構體:
1voidmessage_handler() 2{ 3structmsgmsg_ptr;/*用于放置消息的局部變量*/ 4 5/*從消息隊列中接收消息到msg_ptr中*/ 6if(rt_mq_recv(mq,(void*)&msg_ptr,sizeof(structmsg))==RT_EOK) 7{ 8/*成功接收到消息,進行相應的數據處理*/ 9}10}
因為消息隊列是直接的數據內容復制,所以在上面的例子中,都采用了局部變量的方式保存消息結構體,這樣也就免去動態內存分配的煩惱了(也就不用擔心,接收線程在接收到消息時,消息內存空間已經被釋放)。
2、同步消息
在一般的系統設計中會經常遇到要發送同步消息的問題,這個時候就可以根據當時狀態的不同選擇相應的實現:兩個線程間可以采用 [消息隊列 + 信號量或郵箱] 的形式實現。發送線程通過消息發送的形式發送相應的消息給消息隊列,發送完畢后希望獲得接收線程的收到確認,工作示意圖如下圖所示:
同步消息示意圖(來源RT-Thread編程指南)
根據消息確認的不同,可以把消息結構體定義成:
(1)消息采用了信號量來作為確認標志。信號量作為確認標志只能夠單一的通知發送線程,消息已經確認接收
1structmsg 2{ 3/*消息結構其他成員*/ 4structrt_semaphoreack; 5};
(2)消息使用了郵箱來作為確認標志,郵箱作為確認標志,代表著接收線程能夠通知一些狀態值給發送線程:
-
函數
+關注
關注
3文章
4345瀏覽量
62953 -
線程
+關注
關注
0文章
505瀏覽量
19754 -
消息隊列
+關注
關注
0文章
33瀏覽量
3016
原文標題:社區新人的RT-Thread學習筆記7——消息隊列
文章出處:【微信號:RTThread,微信公眾號:RTThread物聯網操作系統】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論