您當前的位置:首頁 > 歷史

RT-Thread快速入門-訊息佇列

作者:由 zppsky 發表于 歷史時間:2022-04-11

一起學嵌入式

RT-Thread快速入門-訊息佇列

哈哈,RT-Thread 快速入門系列文章登上官方論壇 “今日聚焦” 了,能夠得到官方認可,屬實受寵若驚。感謝 RT-Thread 的認可,感謝官方提供的交流學習平臺。

繼續努力,持續輸出 RT-Thread 相關的技術文章。加油~

RT-Thread 官方論壇地址為:

https://club。rt-thread。org/index。html

正文開始

上一篇介紹了訊息郵箱,本篇文章介紹執行緒(任務)間通訊的另一種方式——訊息佇列。

訊息佇列在實際專案中應用較多,建議初學者應該熟練掌握。

掌握了 RT-Thread 訊息佇列的原理和操作方法,如果再學習其他款 RTOS,會感覺很輕鬆。

訊息佇列的工作機制

1. 理解訊息佇列

執行緒或中斷服務例程可以將一條或多條訊息放入訊息佇列中。

一個或多個執行緒也可以從訊息佇列中獲得訊息。當有多個訊息傳送到訊息佇列時,通常將先進入訊息佇列的訊息先傳給執行緒,也就是說,執行緒先得到的是最先進入訊息佇列的訊息,即先進先出原則 (FIFO)。

如下圖所示

RT-Thread快速入門-訊息佇列

2. 訊息佇列控制塊

訊息佇列控制塊是 RT-Thread 系統管理訊息佇列的一種資料結構,由結構體

struct rt_messagequeue

表示。另外

rt_mq_t

表示訊息佇列的控制代碼,即指向訊息佇列控制塊的指標。

訊息佇列控制塊的資料結構定義如下:

struct rt_messagequeue

{

struct rt_ipc_object parent; /* 繼承自 ipc_object 類 */

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; /* 空閒訊息連結串列 */

rt_list_t suspend_sender_thread; /* 傳送執行緒的掛起等待佇列 */

};

typedef struct rt_messagequeue *rt_mq_t;

結構體定義中,繼承關係一目瞭然,不再贅述。

rt_messagequeue

物件從

rt_ipc_object

中派生,由 IPC 容器所管理。

訊息佇列的操作函式

RT-Thread 提供了多種管理訊息佇列的介面函式。包括:建立訊息佇列 - 傳送訊息 - 接收訊息 - 刪除訊息佇列。 如下圖所示:

RT-Thread快速入門-訊息佇列

對於初學者來說,掌握其中常用的函式即可。本文重點介紹訊息佇列常用的函式介面。

實際專案中,使用訊息佇列的流程為:建立訊息佇列 - 傳送訊息 - 接收訊息。我們就重點介紹一下對應的操作函式。

1. 建立訊息佇列

在 RT-Thread 中,同其他核心物件一樣。建立訊息佇列也有兩種方式:(1)動態建立(2)靜態初始化。

動態建立

一個訊息佇列的函式介面如下,呼叫此函式時,核心動態建立一個訊息佇列控制塊。然後再分配一塊記憶體空間,用於存放訊息,這塊記憶體的大小為:訊息佇列個數* [訊息大小 + 訊息頭大小]。最後初始化訊息佇列以及訊息佇列控制塊。

rt_mq_t rt_mq_create(const char *name,

rt_size_t msg_size,

rt_size_t max_msgs,

rt_uint8_t flag)

引數

name

為訊息佇列名稱;

msg_size

為佇列中一條訊息的長度,單位為位元組;

max_msgs

為訊息佇列的最大個數;

flag

為訊息佇列的等待方式。

建立成功,返回訊息佇列的控制代碼;建立失敗,則返回

RT_NULL

靜態方式建立訊息佇列

需要兩步:

定義一個訊息佇列控制塊以及一段存放訊息的緩衝區

初始化訊息佇列控制塊

訊息佇列控制塊初始化函式如下:

rt_err_t rt_mq_init(rt_mq_t mq, const char* name,

void *msgpool, rt_size_t msg_size,

rt_size_t pool_size, rt_uint8_t flag);

函式的引數解釋如下表:

引數

描述

初始化訊息佇列函式返回

RT_EOK

建立或初始化完成訊息佇列後,所有訊息塊都掛在空閒訊息連結串列上,訊息佇列為空。

建立訊息佇列的標誌變數取值有兩種:

RT_IPC_FLAG_FIFO

,等待訊息佇列的執行緒按照先進先出的方式進行排列。

RT_IPC_FLAG_PRIO

,等待訊息佇列的執行緒按照優先順序的方式進行排列。

2. 傳送訊息

RT-Thread 提供的傳送訊息介面函式有兩種:一種是無等待超時介面,一種是有等待超時。

執行緒或者中斷服務程式都可以給訊息佇列傳送訊息,傳送訊息的函式介面如下,此函式沒有等待超時引數。

rt_err_t rt_mq_send(rt_mq_t mq, const void *buffer, rt_size_t size)

引數

mq

為訊息佇列物件的控制代碼;

buffer

為存放訊息緩衝區的指標;

size

為訊息大小。

傳送成功,函式返回

RT_EOK

;訊息佇列已滿,返回

-RT_EFULL

傳送的訊息長度大於訊息佇列中訊息塊的最大長度,則返回

-RT_ERROR

等待方式

傳送訊息的函式介面如下,這個函式有等待超時引數:

rt_err_t rt_mq_send_wait(rt_mq_t mq,

const void *buffer,

rt_size_t size,

rt_int32_t timeout)

此函式的引數

timeout

為傳送等待超時時間,單位為系統時鐘節拍。其他引數與

rt_mq_send()

相同。

如果訊息佇列已經滿了,傳送執行緒會根據設定的

timeout

引數等待訊息佇列中因為收取訊息而空出空間。若超時時間到達依然沒有空出空間,則傳送執行緒將會被喚醒並返回錯誤碼。

返回

RT_EOK

表示傳送成功;返回

-RT_ETIMEOUT

表示超時;返回

-RT_ERROR

表示傳送失敗。

注意

:在中斷服務例程中傳送郵件時,應該採用無等待延時的方式傳送,直接使用

rt_mq_send()

或者等待超時設定為 0 的函式

rt_mq_send_wait()

3. 接收訊息

執行緒接收訊息的函式介面如下,

rt_err_t rt_mq_recv(rt_mq_t mq, void *buffer,

rt_size_t size, rt_int32_t timeout)

引數

mq

為訊息佇列物件的控制代碼;

buffer

為訊息內容;

size

為訊息大小;

timeout

為超時時間。

接收訊息時,需要指定訊息佇列的控制代碼,以及一塊用於儲存訊息的緩衝區,接收到的訊息內容將被複制到該緩衝區裡。還需指定等待訊息的超時時間。

當訊息佇列中為空時,接收訊息的執行緒會根據設定的超時時間,掛起在訊息佇列的等待執行緒佇列上,或直接返回。

實戰演練

多說無益,實踐出真知。我們來舉個例子,學習一下如何使用訊息佇列。

動態建立兩個執行緒和一個訊息佇列,一個執行緒往訊息佇列中傳送訊息,一個執行緒從訊息佇列中接收訊息。

程式碼如下:

#include

#define THREAD_PRIORITY 8

#define THREAD_TIMESLICE 5

/* 訊息佇列控制代碼 */

rt_mq_t mq_handle;

/* 執行緒 1 入口 */

static void thread1_entry(void *parameter)

{

char buf = 0;

rt_uint8_t cnt = 0;

while (1)

{

/* 從訊息佇列中收取訊息 */

if (rt_mq_recv(mq_handle, &buf, sizeof(buf), RT_WAITING_FOREVER) == RT_EOK)

{

rt_kprintf(“thread1: recv msg , the content: %c\n”, buf);

if (cnt == 19)

{

break;

}

cnt++;

}

rt_thread_mdelay(1);

}

}

/* 執行緒 2 入口 */

static void thread2_entry(void *parameter)

{

int result;

char buf = ‘A’;

rt_uint8_t cnt = 0;

while (1)

{

rt_kprintf(“thread2: send message - %c\n”, buf);

/* 向訊息佇列傳送訊息 */

result = rt_mq_send(mq_handle, &buf, 1);

if(result != RT_EOK)

{

rt_kprintf(“rt_mq_send ERR\n”);

}

buf++;

cnt++;

if(cnt >= 20)

{

rt_kprintf(“message queue stop send, thread2 quit\n”);

break;

}

/* 延時 50ms */

rt_thread_mdelay(500);

}

}

int main()

{

/* 執行緒控制塊指標 */

rt_thread_t thread1 = RT_NULL;

rt_thread_t thread2 = RT_NULL;

/* 建立一個郵箱 */

mq_handle = rt_mq_create(“mq”, 1, 2048, RT_IPC_FLAG_FIFO);

if (mq_handle == RT_NULL)

{

rt_kprintf(“create msg queue failed。\n”);

return -1;

}

/* 動態建立執行緒1 */

thread1 = rt_thread_create(“thread1”, thread1_entry, RT_NULL,

1024, THREAD_PRIORITY - 1, THREAD_TIMESLICE);

if(thread1 != RT_NULL)

{

/* 啟動執行緒 */

rt_thread_startup(thread1);

}

/* 動態建立執行緒2 */

thread2 = rt_thread_create(“thread2”, thread2_entry, RT_NULL,

1024, THREAD_PRIORITY, THREAD_TIMESLICE);

if(thread2 != RT_NULL)

{

/* 啟動執行緒 */

rt_thread_startup(thread2);

}

}

編譯執行結果如下

RT-Thread快速入門-訊息佇列

該例程演示了訊息佇列如何使用。執行緒 1 從訊息佇列中收取訊息;執行緒 2 定時給訊息佇列傳送訊息,一共傳送了 20 條訊息。

其他操作函式

對於 RT-Thread 訊息佇列操作來說,還有幾個函式沒有介紹。可以簡單瞭解一下。

1. 刪除動態建立的訊息佇列

刪除由

rt_mq_create()

函式建立的訊息佇列,可以呼叫如下函式:

rt_err_t rt_mq_delete(rt_mq_t mq)

呼叫此函式,可以釋放訊息佇列控制塊佔用的記憶體資源以及訊息緩衝區佔用的記憶體。在刪除一個訊息佇列物件時,應該確保該訊息佇列不再被使用。

在刪除前會喚醒所有掛起在該訊息佇列上的執行緒,然後釋放訊息佇列物件佔用的記憶體塊。

2. 脫離靜態建立的訊息佇列

刪除

rt_mq_init()

初始化的訊息佇列,可以用如下函式:

rt_err_t rt_mq_detach(rt_mq_t mq)

呼叫此函式時,首先會喚醒所有掛起在該訊息佇列中,執行緒等待佇列上的執行緒,然後將該訊息佇列從核心物件管理器中脫離。

3.傳送緊急訊息

RT-Thread 中,提供了一種傳送緊急訊息的函式介面,其過程與傳送訊息幾乎一樣。其函式介面如下:

rt_err_t rt_mq_urgent(rt_mq_t mq, void* buffer, rt_size_t size);

在傳送緊急訊息時,從空閒訊息連結串列上取下來的訊息塊不是掛到訊息佇列的隊尾,而是掛到隊首,這樣,接收者就能夠優先接收到緊急訊息,從而及時進行訊息處理。

OK,今天先到這,下次繼續。加油~

公眾號【

一起學嵌入式

】,精彩首先送達

標簽: RT  佇列  訊息  MQ