RT-Thread實現了三種線程間通信方式,郵箱(mailbox)、消息隊列(messagequeue)、和信號(signal)。
郵箱
郵箱中的每一封郵件只能容納固定的 4 字節內容。任何線程都可以發送或接受郵件消息。
郵箱控制塊
struct rt_mailbox
{
struct rt_ipc_object parent;
rt_uint32_t* msg_pool; /* 郵箱緩衝區的開始地址 */
rt_uint16_t size; /* 郵箱緩衝區的大小 */
rt_uint16_t entry; /* 郵箱中郵件的數目 */
rt_uint16_t in_offset, out_offset; /* 郵箱緩衝的進出指針 */
rt_list_t suspend_sender_thread; /* 發送線程的掛起等待隊列 */
};
typedef struct rt_mailbox* rt_mailbox_t;
創建郵箱
rt_mailbox_t rt_mb_create(const char *name, rt_size_t size, rt_uint8_t flag)
.msg_pool
就是一個循環隊列,FIFO,通過.in_offset
,.out_offset
記錄進出位置下標。.suspend_sender_thread
當郵箱滿時,發送線程會掛在該鏈上。
發送郵件
rt_err_t rt_mb_send_wait (rt_mailbox_t mb, rt_uint32_t value)
當發送郵件時, 當且僅當郵箱還沒滿郵件的時候才能進行發送,如果郵箱已滿,可以根據用戶設定的等待時間進行等待,當郵箱中的郵件被收取而空出空間來時,等待掛起的發送線程將被喚醒繼續發送的過程,當等待時間到了還未完成發送郵件,或者未設置等待時間,此時發送郵件失敗,發送郵件的線程或者中斷程序會收到一個錯誤碼( -RT_EFULL)。如果郵件發送成功,會喚起等待接收郵件的線程。線程發送郵件可以帶阻塞,但在中斷中不能採用任何帶阻塞的方式發送郵件。
接受郵件
rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_ubase_t *value, rt_int32_t timeout)
接收郵件時, 根據郵箱控制塊中的 entry 判斷隊列是否有郵件,如果郵箱的郵件非空,那麼可以根據 out_offset 找到最先發送到郵箱中的郵件進行接收。在接收時如果郵箱爲空,如果用戶設置了等待超時時間,系統會將當前線程掛起,當達到設置的超時時間,郵箱依然未收到郵件時,那麼線程將被喚醒並返回-RT_ETIMEOUT。如果郵箱中存在郵件,那麼接收線程將複製郵箱中的 4 個字節郵件到接收線程中。如果接收成功,會喚起等待發送的線程。
工程文件
- rtt2a9_mailbox
線程2發送郵件,線程1等待按鍵事件後接收郵件消息。
消息隊列
消息隊列是另一種常用的線程間通訊方式,是郵箱的擴展。可以應用在多種場合:線程間的消息交換、使用串口接收不定長數據等。
- 相比於郵箱,消息隊列能發送不固定長度的消息,但沒有發送線程的掛起列表。
- 支持發送緊急消息,緊急消息會直接加到對頭。
消息隊列控制塊
struct rt_messagequeue
{
struct rt_ipc_object parent;
void* msg_pool; /* 指向存放消息的緩衝區的指針 */
rt_uint16_t msg_size; /* 每個消息的長度 */
rt_uint16_t max_msgs; /* 最大能夠容納的消息數 */
rt_uint16_t entry; /* 隊列中已有的消息數 */
void* msg_queue_head; /* 消息鏈表頭 */
void* msg_queue_tail; /* 消息鏈表尾 */
void* msg_queue_free; /* 空閒消息鏈表 */
};
typedef struct rt_messagequeue* rt_mq_t;
創建消息隊列
rt_mq_t rt_mq_create(const char *name,
rt_size_t msg_size,
rt_size_t max_msgs,
rt_uint8_t flag);
.msg_size
要注意對齊。創建後,整個鏈表都是空閒塊,.msg_queue_free
指向表頭,.msg_queue_head
和.msg_queue_tail
指向RT_NULL
。
發送消息
rt_err_t rt_mq_send(rt_mq_t mq, void *buffer, rt_size_t size);
從空閒鏈表中摘下.msg_queue_free
指向的空閒塊,加到消息隊列的尾部。如果消息隊列爲空,則更新.msg_queue_head
和.msg_queue_tail
。發送緊急消息時,把消息直接加到對頭。
接收消息
rt_err_t rt_mq_recv(rt_mq_t mq,
void *buffer,
rt_size_t size,
rt_int32_t timeout);
通過.entry
判斷隊列中是否有消息,沒有,則掛起或直接返回。有,則從對頭取出消息,拷貝數據後將內存塊掛到空閒隊列。
工程文件
線程2發送消息,消息中帶一個信號量,用於線程1接收到消息後通知線程2繼續發送消息。