この記事では、[WildfireEmbedFire]「RTスレッドカーネルの実装とアプリケーション開発-STM32に基づく」について説明します。これは個人的な学習ノートとしてのみ使用されます。詳細な内容と手順については、元のテキストを確認してください(Wildfire Data Download Centerからダウンロードできます)。
記事ディレクトリ
メッセージキューの基本概念
メッセージキューは、スレッド間の一般的な通信方法であり、スレッドから可変長のメッセージを受信したり、サービスルーチンを中断したり、メッセージを独自のメモリスペースにキャッシュしたりできます。他のスレッドもメッセージキューから対応するメッセージを読み取ることができ、メッセージキューが空の場合、読み取りスレッドを一時停止できます。新しいメッセージが到着すると、中断されたスレッドがウェイクアップされてメッセージを受信して処理します。メッセージキューは非同期通信方式です。
-RT-Thread公式中国語マニュアル
メッセージキューサービスを使用すると、スレッドまたは割り込みサービスルーチンは、1つ以上のメッセージをメッセージキューに入れることができます。同様に、1つ以上のスレッドがメッセージキューからメッセージを取得できます。複数のメッセージがメッセージキューに送信される場合、通常、最初にメッセージキューに入るメッセージは最初にスレッドに渡されます。つまり、スレッドは最初にメッセージキューに入るメッセージ、つまり先入れ先出しを取得します。 -アウト原則(FIFO)。同時に、RT-Threadのメッセージキューは優先度をサポートします。つまり、メッセージを待機しているすべてのスレッドの中で、優先度が最も高いスレッドが最初にメッセージを取得します。
RT-Threadでは、キューデータ構造を使用してスレッドの非同期通信を実現します。これには次の特徴があります。
- メッセージは、先入れ先出しキューイングと優先キューイングをサポートし、非同期の読み取りおよび書き込み作業をサポートします。
- 読み取りキューはタイムアウトメカニズムをサポートします。
- 緊急メッセージの送信をサポートします。緊急メッセージはキューの先頭に送信されます。
- さまざまな長さ(最大キューノード値まで)の任意のタイプのメッセージを許可できます。
- スレッドは、任意のメッセージキューからメッセージを送受信できます。
- 複数のスレッドが同じメッセージキューからメッセージを送受信できます。
- キューが使い果たされた場合は、キュー操作を削除してメモリ機能を解除し、再利用する必要があります。
- オリジナル
メッセージキューのしくみ
メッセージキューの作業図に示されているように、メッセージキューサービスを介して、スレッドまたは割り込みサービスルーチンは1つ以上のメッセージをメッセージキューに入れることができます。同様に、1つ以上のスレッドがメッセージキューからメッセージを取得できます。複数のメッセージがメッセージキューに送信される場合、通常、最初にメッセージキューに入るメッセージは最初にスレッドに渡される必要があります。つまり、スレッドは最初にメッセージキューに入るメッセージ、つまり先入れ先出しを取得します。先入れ先出し法(FIFO)。
-RT-Thread公式中国語マニュアル
メッセージキューのブロックメカニズム
メッセージキューはスレッドに属していないため、複数のスレッドがメッセージキューを操作する場合は、各スレッドをキューの読み取りと書き込みから保護する必要がありますが、オペレーティングシステムにはすでにこの保護操作があります。これはブロッキングと呼ばれます。メッセージキューのメカニズム。
メッセージキューの読み取りと書き込みを行う各関数には、独自のブロックメカニズムがあります。スレッドが空のキューを読み取る場合、次の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スレッドが受信データを出力します。