RT-Thread Mutex(研究ノート)

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

ミューテックスの基本概念

相互に排他的なセマフォとしても知られるMutexは、特別なバイナリセマフォです。セマフォとは異なり、ミューテックスの所有権、再帰的アクセス、および優先順位の逆転を防ぐ機能をサポートします。
ミューテックスには、ロック解除またはロックされた2つの状態(2つの状態値)しかありません。スレッドがそれを保持すると、ミューテックスがロックされ、スレッドがその所有権を取得します。代わりに、スレッドがスレッドを解放すると、ミューテックスのロックが解除され、所有権が失われます。スレッドがミューテックスを保持している場合、他のスレッドはそれをロック解除または保持することができず、ミューテックスを保持しているスレッドは中断されることなく再びロックを取得できます。この機能は、一般的なバイナリセマフォとは大きく異なります。セマフォでは、インスタンスがないため、スレッドが再帰的に保持すると、アクティブに中断されます(最終的にはデッドロックが発生します)。

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

ミューテックス優先度継承メカニズム

セマフォの使用によって引き起こされる別の潜在的な問題は、スレッドの優先順位の反転です。いわゆる優先順位の逆転の問題は、優先度の高いスレッドがセマフォメカニズムを介して共有リソースにアクセスしようとしたときに、セマフォが優先度の低いスレッドによってすでに保持されており、優先度の低いスレッドが他のスレッドによって使用されている可能性がある場合です。実行中のプロセス中のスレッド。一部の中優先度のスレッドはプリエンプトされるため、高優先度のスレッドが多くの低優先度のスレッドによってブロックされ、リアルタイムのパフォーマンスを保証することが困難になります。

優先度継承プロトコルとは、特定のリソースを占有する優先度の低いスレッドの優先度を、リソースを待機して実行するすべてのスレッドの中で優先度が最も高いスレッドの優先度と等しくなるように増やすことを意味します。優先度の低いスレッドがリソースを解放すると、優先度は初期設定に戻ります。したがって、優先順位が継承されたスレッドは、中間優先順位のスレッドによるシステムリソースのプリエンプションを回避します。

•警告:ミューテックスを取得したらできるだけ早く解放し、ミューテックスを保持しているスレッドの優先順位を変更しないでください。

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

Mutexアプリケーションシナリオ

ミューテックスは以下に適用されます:

  • スレッドがミューテックスを複数回取得する可能性がある状況。これにより、同じスレッドが再帰的に複数回保持することによって引き起こされるデッドロックを回避できます。
  • 優先順位の逆転を引き起こす可能性のある状況。

典型的な例:シリアル通信中、ハードウェアリソースは1つしかないため、2つのスレッドを同時に送信する必要がある場合は、相互排他ロックを追加する必要があります。

注:ミューテックスは、割り込みサービス機能では使用できません。

ミューテックスのしくみ

ミューテックスはキーに相当します。2つのスレッドが同じリソースを取得する場合は、最初に「キー」を取得する必要があります。現在のスレッドが「キー」を解放した後、次のスレッドは引き続きリソースにアクセスできます。

画像ソース:「RTスレッドカーネルの実装とアプリケーション開発の実践」

ここに画像の説明を挿入

Mutexコントロールブロック

struct rt_mutex
{
    
    
    struct rt_ipc_object parent;                        /**< 继承自ipc_object类 */

    rt_uint16_t          value;                         /**< 互斥量的值 */

    rt_uint8_t           original_priority;             /**< 持有线程的原始优先级 */
    rt_uint8_t           hold;                          /**< 持有线程的持有次数 */

    struct rt_thread    *owner;                         /**< 当前拥有互斥量的线程 */
};

Mutex関数インターフェースの概要

mutex rt_mutex_create()を作成します

rt_mutex_t rt_mutex_create(const char * name、rt_uint8_tフラグ);

rt_mutex_create関数を呼び出して、名前で指定された名前のミューテックスを作成できます。作成されたミューテックスは、指定されたフラグが異なるため、意味が異なります。PRIO優先度フラグを使用して作成されたIPCオブジェクトの場合、複数のスレッドがリソースを待機すると、優先度の高いスレッドがリソースの取得を優先します。FIFO先入れ先出しフラグを使用して作成されたIPCオブジェクトは、複数のスレッドがリソースを待機しているときに、先着順でリソースを取得します。

パラメータ 説明
名前 ミューテックス名
国旗 ミューテックスフラグ

mutex rt_mutex_delete()を削除します

rt_err_t rt_mutex_delete(rt_mutex_t mutex);

ミューテックスが削除されると、このミューテックスを待機しているすべてのスレッドが起動され、待機中のスレッドによって取得される戻り値は-RT_ERRORです。次に、システムはカーネルオブジェクトマネージャのリンクリストからミューテックスを削除し、ミューテックスが占有していたメモリスペースを解放します。

パラメータ 説明
ミューテックス ミューテックスオブジェクトへのハンドル

mutex rt_mutex_init()を初期化します

rt_err_t rt_mutex_init(rt_mutex_t mutex、const char * name、rt_uint8_tフラグ);

この関数インターフェースを使用する場合は、ミューテックスオブジェクトのハンドル(つまり、ミューテックス制御ブロックへのポインター)、ミューテックスの名前、およびミューテックスのフラグを指定する必要があります。ミューテックスフラグは、上記のミューテックスの作成機能で説明したフラグを使用できます。

パラメータ 説明
ミューテックス ミューテックスオブジェクトへのハンドル
名前 ミューテックス名
国旗 ミューテックスフラグ

Mutexリリースrt_mutex_release()

rt_err_t rt_mutex_release(rt_mutex_t mutex);

この関数インターフェースを使用する場合、ミューテックスをすでに制御しているスレッドのみが解放できます。ミューテックスが解放されるたびに、その保持カウントが1ずつ減少します。ミューテックスのホールドカウントがゼロの場合(つまり、ホールドスレッドがすべてのホールド操作を解放した場合)、ミューテックスが使用可能になり、セマフォを待機しているスレッドがウェイクアップされます。

パラメータ 説明
ミューテックス ミューテックスオブジェクトへのハンドル

ミューテックス取得rt_mutex_take()

rt_err_t rt_mutex_take(rt_mutex_t mutex、rt_int32_t時間);

ミューテックスが別のスレッドによって制御されていない場合、ミューテックスを要求したスレッドはミューテックスを正常に取得します。ミューテックスが現在のスレッドによってすでに制御されている場合、ミューテックスの保持カウントは1ずつ増加し、現在のスレッドは待機を一時停止しません。ミューテックスがすでに別のスレッドによって占有されている場合、現在のスレッドは、他のスレッドがミューテックスを解放するか、指定されたタイムアウトより長く待機するまで、ミューテックスの待機を一時停止します。

パラメータ 説明
ミューテックス ミューテックスオブジェクトへのハンドル
時間 OSTickで指定された待機時間

Mutexの使用に関する考慮事項

予防:

  1. 2つのスレッドが同時に同じミューテックスを保持することはできません。
  2. ミューテックスは、割り込みサービスルーチンでは使用できません。
  3. RT-Threadのリアルタイムのパフォーマンスを確保するために、ミューテックスが取得され、長期間解放されないようにする必要があります。
  4. rt_thread_control()(スレッド優先度の変更)などの関数は、ミューテックスを保持している間はスレッドで呼び出すことができません。

ミューテックス実験

ミューテックスを使用するにはrtconfig.h、で関連する構成を開く必要があります。

ここに画像の説明を挿入
この実験は、中国の公式文書を参照しており、3つの動的スレッドを作成します。優先度の低いスレッドがミューテックスを取得した後、優先度が待機中のスレッドの優先度の中で最も高い優先度に調整されているかどうかを判断します。
スレッド1、2、3の優先度はそれぞれ高、中、低です。スレッド3は最初にミューテックスを保持し、次にスレッド2にそれを取得させます。このとき、スレッド2はスレッドと同じ優先度に調整する必要があります。 2.このチェックプロセスはスレッド1で行われます。

  • 注:この実験スレッド3は、ミューテックスを取得してから長い遅延が発生するため、実際の使用ではこの使用を避ける必要があります。
#include "board.h"
#include "rtthread.h"

// 定义线程控制块指针
static rt_thread_t tid1 = RT_NULL;
static rt_thread_t tid2 = RT_NULL;
static rt_thread_t tid3 = RT_NULL;

// 定义互斥量控制块
static rt_mutex_t test_mux = RT_NULL;


/******************************************************************************
* @ 函数名  : thread1_entry
* @ 功  能  : 线程1入口
* @ 参  数  : parameter 外部传入的参数
* @ 返回值  : 无
******************************************************************************/
static void thread1_entry(void *parameter)
{
    
    

	// 让低优先级线程先运行
	rt_thread_delay(10);
	
	// 此时线程3持有互斥量,并线程2等待互斥量
	
	// 检查线程2与线程3的优先级情况
	if(tid2->current_priority != tid3->current_priority)
	{
    
    
		// 优先级不相同,测试失败
		rt_kprintf("线程3优先级未变化,测试失败!\n");
	}
	else
		rt_kprintf("线程3优先级变为%d,测试成功!\n");
		
	while(1)
	{
    
    
		rt_kprintf("线程2优先级:%d\n", tid2->current_priority);
		rt_kprintf("线程3优先级:%d\n\n", tid3->current_priority);
		
		rt_thread_delay(1000);
	}
		
}
	
/******************************************************************************
* @ 函数名  : thread2_entry
* @ 功  能  : 线程2入口
* @ 参  数  : parameter 外部传入的参数
* @ 返回值  : 无
******************************************************************************/
static void thread2_entry(void *parameter)
{
    
    
	rt_err_t result;
	
	// 让低优先级线程3先运行
	rt_thread_delay(5);
	
	while(1)
	{
    
    
		// 试图获取互斥锁,此时线程3持有互斥锁,应把线程3优先级提升为线程2优先级
		result = rt_mutex_take(test_mux, RT_WAITING_FOREVER);
		
		if(result == RT_EOK)
		{
    
    
			rt_kprintf("线程2获取到互斥锁,且已释放\n");
			// 释放互斥锁
			rt_mutex_release(test_mux);
			rt_thread_delay(800);
		}
	}
}
		
/******************************************************************************
* @ 函数名  : thread3_entry
* @ 功  能  : 线程3入口
* @ 参  数  : parameter 外部传入的参数
* @ 返回值  : 无
******************************************************************************/
static void thread3_entry(void *parameter)
{
    
    
	rt_err_t result;
	
	while(1)
	{
    
    
		// 获取互斥锁(测试连续获取两次)
		result = rt_mutex_take(test_mux, RT_WAITING_FOREVER);
		result = rt_mutex_take(test_mux, RT_WAITING_FOREVER);
		
		if(result == RT_EOK)
		{
    
    
			rt_kprintf("线程3获取到互斥锁\n");
		}
		
		// 做一个长时间的挂起(实际使用时非常不建议这样用)
		rt_thread_delay(1500);
		
		rt_kprintf("线程3释放互斥锁\n");
		
		// 释放互斥锁
		rt_mutex_release(test_mux);
		rt_mutex_release(test_mux);
	}
}
		

int main(void)
{
    
    
	// 硬件初始化和RTT的初始化已经在component.c中的rtthread_startup()完成
	
	// 创建一个互斥量
	test_mux =                                    // 互斥量控制块指针
	rt_mutex_create("test_mux",                   // 互斥量名字                       // 互斥量初始值
	                RT_IPC_FLAG_FIFO);            // FIFO队列模式(先进先出)
	
	if(test_mux != RT_NULL)
		rt_kprintf("互斥量创建成功!\n");

	// 创建线程1
	tid1 =                                        // 线程控制块指针
	rt_thread_create("tid1",                      // 线程名字
	                thread1_entry,                // 线程入口函数
	                RT_NULL,                      // 入口函数参数
	                255,                          // 线程栈大小
				    2,                            // 线程优先级
					10);                          // 线程时间片
	
	
	// 开启线程调度
	if(tid1 != RT_NULL)
		rt_thread_startup(tid1);
	else
		return -1;
	
	
	// 创建线程2
	tid2 =                                        // 线程控制块指针
	rt_thread_create("tid2",                      // 线程名字
	                thread2_entry,                // 线程入口函数
	                RT_NULL,                      // 入口函数参数
	                255,                          // 线程栈大小
				    3,                            // 线程优先级
					10);                          // 线程时间片
	
	
	// 开启线程调度
	if(tid2 != RT_NULL)
		rt_thread_startup(tid2);
	else
		return -1;
	
	// 创建线程3
	tid3 =                                        // 线程控制块指针
	rt_thread_create("tid3",                      // 线程名字
	                thread3_entry,                // 线程入口函数
	                RT_NULL,                      // 入口函数参数
	                255,                          // 线程栈大小
				    4,                            // 线程优先级
					10);                          // 线程时间片
	
	
	// 开启线程调度
	if(tid3 != RT_NULL)
		rt_thread_startup(tid3);
	else
		return -1;
							
}


実験現象

優先度が最も低いスレッド3がミューテックスを保持し、スレッド2もミューテックスの取得を開始すると、スレッド3の優先度がスレッド2の優先度に引き上げられます。

スレッド2がミューテックスを解放し、遅延機能を使用してサスペンドすると、スレッド3はミューテックスを再度取得しますが、スレッド2はミューテックス待機キューにないため、スレッド3の優先度は変更されません。

スレッド2の遅延が終了すると、ミューテックスの取得を続行しますが、このとき、スレッド3の優先度が再びスレッド2の優先度に引き上げられます。

ここに画像の説明を挿入

おすすめ

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