RT-Threadメッセージキュー(研究ノート)

この記事では、[WildfireEmbedFire]「RTスレッドカーネルの実装とアプリケーション開発-STM32に基づく」について説明します。これは個人的な学習ノートとしてのみ使用されます。詳細な内容と手順については、元のテキストを確認してください(Wildfire Data Download Centerからダウンロードできます)。

メッセージキューの基本概念

メッセージキューは、スレッド間の一般的な通信方法であり、スレッドから可変長のメッセージを受信したり、サービスルーチンを中断したり、メッセージを独自のメモリスペースにキャッシュしたりできます。他のスレッドもメッセージキューから対応するメッセージを読み取ることができ、メッセージキューが空の場合、読み取りスレッドを一時停止できます。新しいメッセージが到着すると、中断されたスレッドがウェイクアップされてメッセージを受信して​​処理します。メッセージキューは非同期通信方式です。

-RT-Thread公式中国語マニュアル

メッセージキューサービスを使用すると、スレッドまたは割り込みサービスルーチンは、1つ以上のメッセージをメッセージキューに入れることができます。同様に、1つ以上のスレッドがメッセージキューからメッセージを取得できます。複数のメッセージがメッセージキューに送信される場合、通常、最初にメッセージキューに入るメッセージは最初にスレッドに渡されます。つまり、スレッドは最初にメッセージキューに入るメッセージ、つまり先入れ先出しを取得します。 -アウト原則(FIFO)。同時に、RT-Threadのメッセージキューは優先度をサポートします。つまり、メッセージを待機しているすべてのスレッドの中で、優先度が最も高いスレッドが最初にメッセージを取得します。

RT-Threadでは、キューデータ構造を使用してスレッドの非同期通信を実現します。これには次の特徴があります。

  • メッセージは、先入れ先出しキューイングと優先キューイングをサポートし、非同期の読み取りおよび書き込み作業をサポートします。
  • 読み取りキューはタイムアウトメカニズムをサポートします。
  • 緊急メッセージの送信をサポートします。緊急メッセージはキューの先頭に送信されます。
  • さまざまな長さ(最大キューノード値まで)の任意のタイプのメッセージを許可できます。
  • スレッドは、任意のメッセージキューからメッセージを送受信できます。
  • 複数のスレッドが同じメッセージキューからメッセージを送受信できます。
  • キューが使い果たされた場合は、キュー操作を削除してメモリ機能を解除し、再利用する必要があります。

- オリジナル

メッセージキューのしくみ

メッセージキューの作業図に示されているように、メッセージキューサービスを介して、スレッドまたは割り込みサービスルーチンは1つ以上のメッセージをメッセージキューに入れることができます。同様に、1つ以上のスレッドがメッセージキューからメッセージを取得できます。複数のメッセージがメッセージキューに送信される場合、通常、最初にメッセージキューに入るメッセージは最初にスレッドに渡される必要があります。つまり、スレッドは最初にメッセージキューに入るメッセージ、つまり先入れ先出しを取得します。先入れ先出し法(FIFO)。

-RT-Thread公式中国語マニュアル

画像ソース:RT-Threadの公式中国語マニュアル

ここに画像の説明を挿入

メッセージキューのブロックメカニズム

メッセージキューはスレッドに属していないため、複数のスレッドがメッセージキューを操作する場合は、各スレッドをキューの読み取りと書き込みから保護する必要がありますが、オペレーティングシステムにはすでにこの保護操作があります。これはブロッキングと呼ばれます。メッセージキューのメカニズム。

メッセージキューの読み取りと書き込みを行う各関数には、独自のブロックメカニズムがあります。スレッドが空のキューを読み取る場合、次の3つのオプションがあります。

  1. メッセージを待たずに、直接読むことをスキップしてください。
  2. 一定時間待ってから準備完了状態に入り、タイムアウト後にブロッキング状態に入ります。
  3. 待ち続けて、直接ブロッキング状態に入ります。

アイドルメッセージリストに利用可能なメッセージブロックがない場合、キューがいっぱいになります。このとき、送信者(スレッドまたは割り込み)はエラーコードを受け取り、実行を継続します。メッセージはブロック特性なしで送信されます。

メッセージキューのアプリケーションシナリオ

メッセージキューは、スレッド間のメッセージ交換や、割り込みサービス機能でのスレッドへのメッセージの送信など、可変長のメッセージが送信される状況で使用できます(割り込みサービスルーチンはメッセージを受信できません)。

メッセージキュー制御ブロック

メッセージキュー制御ブロックには、キュープールサイズ、キューメッセージサイズ、リンクリストポインタなど、メッセージキューの詳細情報が含まれています。

struct rt_messagequeue
{
    
    
    struct rt_ipc_object parent;                        /**< inherit from 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;         /**< sender thread suspended on this message queue */
};

メッセージキュー関連のインターフェイス

いくつかの一般的に使用されるインターフェースのみが導入されています

メッセージキューの作成rt_mq_create()

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 メッセージキュー内のメッセージの最大長
max_msgs メッセージキューの最大容量(メッセージ数)
国旗 メッセージキューで使用される待機方法

メッセージキューを初期化しますrt_mq_init()

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フラグ);

メッセージキューの作成に似ていますが、メッセージキュー機能を初期化するためのメモリは、システムによって動的に割り当てられるのではなく、静的に定義されます。

パラメータ 説明
mq 静的メッセージキューオブジェクトへのハンドル
名前 メッセージキューの名前
msgpool メッセージを保存するためのバッファ
msg_size メッセージキュー内のメッセージの最大長
pool_size メッセージを保存するバッファのサイズ
国旗 メッセージキューで使用される待機方法

メッセージ送信関数rt_mq_send()

rt_err_t rt_mq_send(rt_mq_t mq、void *バッファー、rt_size_tサイズ);

メッセージを送信するとき、メッセージキューオブジェクトは最初にアイドルメッセージリンクリストからアイドルメッセージブロックをフェッチし、スレッドまたは割り込みサービスプログラムによって送信されたメッセージコンテンツをアイドルメッセージブロックにコピーし、最後にメッセージブロックをハングさせます。メッセージキューの。(通常のメッセージを送信した後、アイドルメッセージリストのキューの先頭にあるメッセージは、メッセージキューの末尾に転送されます)

パラメータ 説明
mq メッセージキューオブジェクトへのハンドル
バッファ メッセージの内容
サイズ メッセージサイズ

メッセージ受信関数rt_mq_recv()

rt_err_t・rt_mq_recv(rt_mq_t mq、void *バッファー、rt_size_tサイズ、rt_int32_tタイムアウト);

メッセージは、メッセージキューが空でない場合にのみ受信できます。そうでない場合、メッセージの受信時にスレッドが中断され、待機時間はtimeoutパラメータによって決定されます。

パラメータ 説明
mq メッセージキューオブジェクトへのハンドル
バッファ メッセージの内容
サイズ メッセージサイズ
タイムアウト 指定されたタイムアウト

キューの削除rt_mq_delete()

rt_err_t rt_mq_delete(rt_mq_t mq)

メッセージキューを削除するときに、スレッドがメッセージキュー待機キューで中断されている場合、カーネルは最初にメッセージ待機キューにぶら下がっているすべてのスレッドをウェイクアップし、次にメッセージキューによって使用されているメモリを解放し、最後にメッセージキューオブジェクトを削除します。

パラメータ 説明
mq メッセージキューオブジェクトへのハンドル

メッセージキュー実験

rtconfig.hメッセージキューを使用するには、でこの機能を有効にする必要があります。

ここに画像の説明を挿入

man()この実験は、関数のみを含む実験コードに対応する元のテキストを参照しており、周辺機器の初期化に関連するコードは以下に示されていません。

前のセクションの実験とは異なり、前の実験のボタンスレッドの優先度はLEDスレッドの優先度よりも高くする必要があります(そうしないと、ボタンの応答速度が影響を受けます)が、この実験の受信スレッドrecv_threadは基本的に一時停止状態であるため、send_thread(ボタン)スレッドはrend_threadと同じ優先度(またはさらに低い優先度)を持つことができます。

#include "board.h"
#include "rtthread.h"

// 定义线程控制块指针
static rt_thread_t recv_thread = RT_NULL;
static rt_thread_t send_thread = RT_NULL;

// 定义消息队列控制块
static rt_mq_t test_mq = RT_NULL;


/******************************************************************************
* @ 函数名  : recv_thread_entry
* @ 功  能  : 消息接收线程入口函数
* @ 参  数  : parameter 外部传入的参数
* @ 返回值  : 无
******************************************************************************/
static void recv_thread_entry(void *parameter)
{
    
    
	rt_err_t uwRet = RT_EOK;
	uint32_t recv_queue; // 接收数据保存的位置
	while(1)
	{
    
    
		// 队列接收,等待方式为一直阻塞等待
		uwRet = rt_mq_recv(test_mq, &recv_queue, sizeof(recv_queue), 
				RT_WAITING_FOREVER); 
		if(RT_EOK == uwRet)
		{
    
    
			rt_kprintf("recv_thread 接收到的数据为%d.\n", recv_queue);
		}
		else
		{
    
    
			rt_kprintf("recv_thread 接收数据出错!\n");
		}
		rt_thread_delay(200);
	}
}

/******************************************************************************
* @ 函数名  : send_thread_entry
* @ 功  能  : 消息发送线程入口函数
* @ 参  数  : parameter 外部传入的参数
* @ 返回值  : 无
******************************************************************************/
static void send_thread_entry(void *parameter)
{
    
    
	rt_err_t uwRet = RT_EOK;
	uint32_t send_data1 = 1;
	uint32_t send_data2 = 2;
	
	while(1)
	{
    
    
		// KEY0 被按下
		if(Key_Scan(KEY0_GPIO_PORT, KEY0_GPIO_PIN) == KEY_ON)
		{
    
    
			// 将data1发送到消息队列
			uwRet = rt_mq_send(test_mq, &send_data1, sizeof(send_data1)); 
			
			if(uwRet != RT_EOK)
			{
    
    
				rt_kprintf("send_thread 发送数据失败--data1.\n");
			}
		}
		
		// WK_UP 被按下
		if(Key_Scan(WK_UP_GPIO_PORT, WK_UP_GPIO_PIN) == KEY_ON)
		{
    
    
			{
    
    
			// 将data2发送到消息队列
			uwRet = rt_mq_send(test_mq, &send_data2, sizeof(send_data2)); 
				
			if(uwRet != RT_EOK)
			{
    
    
				rt_kprintf("send_thread 发送数据失败--data2.\n");
			}
		}
		
		}
		rt_thread_delay(20);
	}
}

int main(void)
{
    
    
	// 硬件初始化和RTT的初始化已经在component.c中的rtthread_startup()完成
	
	// 创建一个队列
	test_mq =                                     // 消息队列控制块指针
	rt_mq_create("test_mq",                       // 消息队列名字
	                50,                           // 消息队列最大长度(单条消息)
	                20,                           // 消息队列最大容量(消息数量)
	                RT_IPC_FLAG_FIFO);            // FIFO队列模式(先进先出)
	
	if(test_mq != RT_NULL)
		rt_kprintf("消息队列创建成功!\n");

	// 创建一个动态线程
	recv_thread =                                 // 线程控制块指针
	rt_thread_create("recv",                      // 线程名字
	                recv_thread_entry,            // 线程入口函数
	                RT_NULL,                      // 入口函数参数
	                255,                          // 线程栈大小
				    5,                            // 线程优先级
					10);                          // 线程时间片
	
	
	// 开启线程调度
	if(recv_thread != RT_NULL)
		rt_thread_startup(recv_thread);
	else
		return -1;
							
	// 创建一个动态线程
	send_thread =                                 // 线程控制块指针
	rt_thread_create("send",                      // 线程名字
	                send_thread_entry,            // 线程入口函数
	                RT_NULL,                      // 入口函数参数
	                255,                          // 线程栈大小
				    5,                            // 线程优先级
					10);                          // 线程时间片
	// 开启线程调度
	if(send_thread != RT_NULL)
		rt_thread_startup(send_thread);
	else
		return -1;
}

実験現象

ボタンKEY0を押すと、send_threadスレッドがdata1を送信し、次にrecv_threadスレッドが受信データを出力します。ボタンWK_UPを押すと、send_threadスレッドがdata2を送信し、recv_threadスレッドが受信データを出力します。

ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/weixin_43772810/article/details/123689088