RT-Thread学习笔记——邮箱
前言
前面講了RT-Thread的信號量、互斥量以及事件集這些都是線程間的同步方式。在我們進行實際的項目開發的時候,經常會涉及到一個線程更新某個全局變量值,然后另外一個線程去讀取這個全局變量值,根據這個全局變量值的不同而去執行不同的操作,在RT-Thread 中則提供了更多的工具幫助在不同的線程中間傳遞信息,包括郵箱、消息隊列、信號用于線程間的通信方式。本文將RT-Thread的郵箱服務,包括郵箱工作機制、工作管理方式以及應用示例,基于潘多拉開發板進行實驗,單片機為STM32L475VET6。
一、郵箱的工作機制
RT-Thread 操作系統的郵箱用于線程間通信,特點是開銷比較低,效率較高。郵箱中的每一封郵件只能容納固定的 4 字節內容(針對 32 位處理系統,指針的大小即為 4 個字節,所以一封郵件恰好能夠容納一個指針)。典型的郵箱也稱作交換消息,如下圖所示,線程或中斷服務例程把一封 4 字節長度的郵件發送到郵箱中,而一個或多個線程可以從郵箱中接收這些郵件并進行處理。?
郵箱工作示意圖(來源RT-Thread編程指南)?
?(1)非阻塞方式的郵件發送過程能夠安全的應用于中斷服務中,是線程、中斷服務、定時器向線程發送消息的有效手段。通常來說,郵件收取過程可能是阻塞的,這取決于郵箱中是否有郵件,以及收取郵件時設置的超時時間。當郵箱中不存在郵件且超時時間不為 0 時,郵件收取過程將變成阻塞方式。在這類情況下,只能由線程進行郵件的收取。
(2)當一個線程向郵箱發送郵件時,如果郵箱沒滿,將把郵件復制到郵箱中。如果郵箱已經滿了,發送線程可以設置超時時間,選擇等待掛起或直接返回? RT_EFULL。如果發送線程選擇掛起等待,那么當郵箱中的郵件被收取而空出空間來時,等待掛起的發送線程將被喚醒繼續發送。
(3)當一個線程從郵箱中接收郵件時,如果郵箱是空的,接收線程可以選擇是否等待掛起直到收到新的郵件而喚醒,或可以設置超時時間。當達到設置的超時時間,郵箱依然未收到郵件時,這個選擇超時等待的線程將被喚醒并返回 RT_ETIMEOUT。如果郵箱中存在郵件,那么接收線程將復制郵箱中的 4 個字節郵件到接收緩存中。
?
二、郵箱的相關函數
1、創建動態郵箱函數:創建郵箱對象時會先從對象管理器中分配一個郵箱對象,然后給郵箱動態分配一塊內存空間用來存放郵件,這塊內存的大小等于郵件大小(4 字節)與郵箱容量的乘積,接著初始化接收郵件數目和發送郵件在郵箱中的偏移量,動態創建一個郵箱對象可以調用如下的函數接口:
rt_mailbox_t rt_mb_create(const char *name, rt_size_t size, rt_uint8_t flag);(1)入口參數:
?name:郵箱名稱。
?size:郵箱容量。
?flag:郵箱標志,它可以取如下數值:RT_IPC_FLAG_FIFO 或RT_IPC_FLAG_PRIO。
(2)返回值:
?RT_NULL:創建失敗。
?郵箱對象的句柄:創建成功。
?
2、刪除動態郵箱函數:當用 rt_mb_create() 創建的郵箱不再被使用時,應該刪除它來釋放相應的系統資源,一旦操作完成,郵箱將被永久性的刪除。刪除郵箱時,如果有線程被掛起在該郵箱對象上,內核先喚醒掛起在該郵箱上的所有線程(線程返回值是 RT_ERROR),然后再釋放郵箱使用的內存,最后刪除郵箱對象。刪除郵箱的函數接口如下:
rt_err_t rt_mb_delete (rt_mailbox_t mb);(1)入口參數:
?mb:要刪除的郵箱對象的句柄。
(2)返回值:
?RT_EOK:成功。
?
3、創建靜態郵箱函數:這里所說的創建靜態郵箱和《RT-Thread編程指南》所講的初始化郵箱是一樣的,跟動態創建郵箱類似,只是初始化郵箱用于靜態郵箱對象的初始化。與創建郵箱不同的是,靜態郵箱對象的內存是在系統編譯時由編譯器分配的,一般放于讀寫數據段或未初始化數據段中,其余的初始化工作與創建郵箱時相同。初始化郵箱時,該函數接口需要獲得用戶已經申請獲得的郵箱對象控制塊,緩沖區的指針,以及郵箱名稱和郵箱容量(能夠存儲的郵件數)。函數接口如下:
rt_err_t rt_mb_init(rt_mailbox_t mb,const char *name,void *msgpool,rt_size_t size,rt_uint8_t flag);(1)入口參數:
?mb:郵箱對象的句柄。
?name:郵箱名稱。
?msgpool:緩沖區指針。
?size:郵箱容量。
?flag:郵箱標志,它可以取如下數值:RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO。
(2)返回值:
?RT_EOK:成功。
?注意:這里的 size 參數指定的是郵箱的容量,即如果 msgpool 指向的緩沖區的字節數是 N,那么郵箱容量應該是 N/4。
?
4、刪除靜態郵箱函數:這里所說的刪除靜態郵箱和《RT-Thread編程指南》所講的脫離郵箱是一樣的,脫離郵箱將把靜態初始化的郵箱對象從內核對象管理器中脫離,內核先喚醒所有掛在該郵箱上的線程(線程獲得返回值是 RT_ERROR),然后將該郵箱對象從內核對象管理器中脫離。脫離郵箱使用下面的接口:
rt_err_t rt_mb_detach(rt_mailbox_t mb);(1)入口參數:
?mb:郵箱對象的句柄。
(2)返回值:
?RT_EOK:成功。
?
5、發送郵件函數:線程或者中斷服務程序可以通過郵箱給其他線程發送郵件,發送的郵件可以是 32 位任意格式的數據,一個整型值或者一個指向緩沖區的指針。當郵箱中的郵件已經滿時,發送郵件的線程或者中斷程序會收到 RT_EFULL 的返回值。函數接口如下:
rt_err_t rt_mb_send (rt_mailbox_t mb, rt_uint32_t value);(1)入口參數:
?mb:郵箱對象的句柄。
?value:郵件內容。
(2)返回值:
?RT_EOK:發送成功。
?RT_EFULL:郵箱已經滿了。
?
6、等待方式發送郵件函數:用戶也可以通過如下的函數接口向指定郵箱發送郵件:
rt_err_t rt_mb_send_wait(rt_mailbox_t mb,rt_uint32_t value,rt_int32_t timeout);rt_mb_send_wait() 與 rt_mb_send() 的區別在于有等待時間,如果郵箱已經滿了,那么發送線程將根據設定的 timeout 參數等待郵箱中因為收取郵件而空出空間。如果設置的超時時間到達依然沒有空出空間,這時發送線程將被喚醒并返回錯誤碼。
(1)入口參數:
?mb:郵箱對象的句柄。
?value:郵件內容。
?timeout:超時時間。
(2)返回值:
?RT_EOK:發送成功。
?RT_ETIMEOUT:超時。
?RT_ERROR:失敗,返回錯誤。
?
7、接收郵件函數:接收郵件時,接收者需指定接收郵件的郵箱句柄,并指定接收到的郵件存放位置以及最多能夠等待的超時時間。接收郵件函數接口如下:
rt_err_t rt_mb_recv (rt_mailbox_t mb, rt_uint32_t* value, rt_int32_t timeout);(1)入口參數:
? mb:郵箱對象的句柄。
? value:郵件內容。
? timeout:超時時間。
?(2)返回值:
? RT_EOK:發送成功。
? RT_ETIMEOUT:超時。
? RT_ERROR:失敗,返回錯誤。
?
三、基于STM32的郵箱示例
光說不練都是假把式,那么接下來我們進行RT-Thread的郵箱實驗,采用RTT&正點原子聯合出品潘多拉開發板,基于STM32。創建一個郵箱,兩個線程,其中一個線程用于發送郵件,另外一個線程由于接收郵件。通過按下不同按鍵發送不同的郵件內容,根據讀取到右郵件內容執行不同操作。當讀取到內容為KEY0按下時點亮RGB紅燈,其他熄滅,當讀取到內容為KEY1按下時點亮RGB藍燈,其他熄滅,當讀取到內容為KEY0按下時點亮RGB綠燈,其他熄滅。
1、實現代碼:
#include "rtthread.h" #include "string.h" #include "mailbox_app.h" #include "led.h" #include "key.h"/* 線程句柄 */ static rt_thread_t thread1 = RT_NULL; static rt_thread_t thread2 = RT_NULL;/* 郵箱句柄 */ static rt_mailbox_t mailbox1 = RT_NULL;char mailbox_msg_key0_press[] = "mailbox_msg_key0_press"; char mailbox_msg_key1_press[] = "mailbox_msg_key1_press"; char mailbox_msg_key2_press[] = "mailbox_msg_key2_press";/************************************************************** 函數名稱 : thread1_recv_mailbox_msg 函數功能 : 線程1入口函數,用于接收郵件 輸入參數 : parameter:入口參數 返回值 : 無 備注 : 無 **************************************************************/ void thread1_recv_mailbox_msg(void *parameter) {char *mb_msg;while(1){if(rt_mb_recv(mailbox1, (rt_uint32_t *)&mb_msg, RT_WAITING_FOREVER) == RT_EOK){rt_kprintf("recv mb_msg:%s\r\n", mb_msg);if(0 == strcmp(mb_msg, "mailbox_msg_key0_press")){LED_R(0);LED_B(1);LED_G(1);}else if(0 == strcmp(mb_msg, "mailbox_msg_key1_press")){LED_R(1);LED_B(0);LED_G(1);}else if(0 == strcmp(mb_msg, "mailbox_msg_key2_press")){LED_R(1);LED_B(1);LED_G(0);}}rt_thread_mdelay(1);} }/************************************************************** 函數名稱 : thread2_send_mailbox_msg 函數功能 : 線程2入口函數,用于發送郵件 輸入參數 : parameter:入口參數 返回值 : 無 備注 : 無 **************************************************************/ void thread2_send_mailbox_msg(void *parameter) {u8 key;while(1){key = key_scan(0);if(key== KEY0_PRES){rt_mb_send(mailbox1, (rt_uint32_t)&mailbox_msg_key0_press);}else if(key== KEY1_PRES){rt_mb_send(mailbox1, (rt_uint32_t)&mailbox_msg_key1_press);}else if(key== KEY2_PRES){rt_mb_send(mailbox1, (rt_uint32_t)&mailbox_msg_key2_press);}rt_thread_mdelay(1);} }void rtthread_mailbox_test(void) {mailbox1 = rt_mb_create("mailbox1", 12, RT_IPC_FLAG_FIFO); /* FIFO模式 */if(mailbox1 != RT_NULL){rt_kprintf("RT-Thread create mailbox successful\r\n");}else{rt_kprintf("RT-Thread create mailbox failed\r\n");return;}thread1 = rt_thread_create("thread1",thread1_recv_mailbox_msg,NULL,512,3,20);if(thread1 != RT_NULL){rt_thread_startup(thread1);;}else{rt_kprintf("create thread1 failed\r\n");return;}thread2 = rt_thread_create("thread2",thread2_send_mailbox_msg,NULL,1024,2,20);if(thread2 != RT_NULL){rt_thread_startup(thread2);;}else{rt_kprintf("create thread2 failed\r\n");return;} }2、觀察FInSH和執行效果:
(1)輸入list_mailbox,可以看到由哪些郵箱,郵箱的容量以及當前掛起等待郵箱內容的線程。
(2)按下KEY0,打印如下郵件的內容,同時RGB紅燈亮。
(3)按下KEY1,打印如下郵件內容,同時RGB藍燈亮。
(4)按下KEY2,打印如下郵件內容,同時RGB綠燈亮。
?
四、郵箱的使用場合及技巧
郵箱是一種簡單的線程間消息傳遞方式,特點是開銷比較低,效率較高。在 RT-Thread 操作系統的實現中能夠一次傳遞一個 4 字節大小的郵件,并且郵箱具備一定的存儲功能,能夠緩存一定數量的郵件數 (郵件數由創建、初始化郵箱時指定的容量決定)。郵箱中一封郵件的最大長度是 4 字節,所以郵箱能夠用于不超過 4 字節的消息傳遞。
由于在 32 系統上 4 字節的內容恰好可以放置一個指針,因此當需要在線程間傳遞比較大的消息時,可以把指向一個緩沖區的指針作為郵件發送到郵箱中,即郵箱也可以傳遞指針,例如:
struct msg {rt_uint8_t *data_ptr;rt_uint32_t data_size; };對于這樣一個消息結構體,其中包含了指向數據的指針 data_ptr 和數據塊長度的變量 data_size。當一個線程需要把這個消息發送給另外一個線程時,可以采用如下的操作:
struct msg* msg_ptr;msg_ptr = (struct msg*)rt_malloc(sizeof(struct msg)); msg_ptr->data_ptr = ...; /* 指 向 相 應 的 數 據 塊 地 址 */ msg_ptr->data_size = len; /* 數 據 塊 的 長 度 *//* 發 送 這 個 消 息 指 針 給 mb 郵 箱 */ rt_mb_send(mb, (rt_uint32_t)msg_ptr);申請結構體大小的內存空間,返回的指針指向了結構體,當結構體中的信息處理完,那么可以將指向結構體的指針作為郵件發送到郵箱中,而在接收郵件的線程中完成對結構體信息的讀取操作,在完成操作后應當釋放內存,因為收取過來的是指針,而 msg_ptr 是一個新分配出來的內存塊,所以在接收線程處理完畢后,需要釋放相應的內存塊:
struct msg* msg_ptr;if (rt_mb_recv(mb, (rt_uint32_t*)&msg_ptr) == RT_EOK) {/* 在 接 收 線 程 處 理 完 畢 后, 需 要 釋 放 相 應 的 內 存 塊 */rt_free(msg_ptr); }?
參考文獻:
1、[野火?]《RT-Thread 內核實現與應用開發實戰—基于STM32》
2、《RT-THREAD 編程指南》
?
總結
以上是生活随笔為你收集整理的RT-Thread学习笔记——邮箱的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Cocos Creator Androi
- 下一篇: Jenkins邮箱配置过程(qq + 1