RT-Thread Mutex (Study Notes)

This article refers to [Wildfire EmbedFire] "RT-Thread Kernel Implementation and Application Development - Based on STM32", which is only used as a personal study note. For more detailed content and steps, please check the original text (you can download it from the Wildfire Data Download Center)

Basic Concepts of Mutexes

Mutex, also known as mutually exclusive semaphore, is a special binary semaphore. Unlike semaphores, it supports mutex ownership, recursive access, and features that prevent priority inversion.
There are only two states of a mutex, unlocked or locked (two state values). When a thread holds it, the mutex is locked, and the thread takes ownership of it. Instead, when the thread releases it, the mutex is unlocked, losing ownership of it. When a thread holds a mutex, other threads will not be able to unlock it or hold it, and the thread holding the mutex will be able to acquire the lock again without being suspended. This feature is very different from the general binary semaphore. In the semaphore, because there is no instance, the thread recursively holds will actively suspend (eventually form a deadlock).

——RT-Thread Official Chinese Manual

Mutex Priority Inheritance Mechanism

Another potential problem caused by using semaphores is thread priority flipping. The so-called priority inversion problem is that when a high-priority thread tries to access a shared resource through the semaphore mechanism, if the semaphore is already held by a low-priority thread, and the low-priority thread may be used by other threads during the running process. Some medium-priority threads are preempted, thus causing high-priority threads to be blocked by many lower-priority threads, making it difficult to guarantee real-time performance.

The priority inheritance protocol means that the priority of a low-priority thread that occupies a certain resource is increased to be equal to the priority of the thread with the highest priority among all threads waiting for the resource, and then executed, and when this When the low-priority thread releases the resource, the priority returns to the initial setting. Thus, threads with inherited priorities avoid preemption of system resources by any intermediate-priority thread.

• Warning: Release the mutex as soon as possible after acquiring it, and do not change the priority of the thread holding the mutex while it is being held.

——RT-Thread Official Chinese Manual

Mutex application scenarios

Mutexes apply to:

  • A situation where a thread may acquire a mutex multiple times. This can avoid the deadlock caused by the same thread holding multiple times recursively;
  • A situation that may cause priority inversion.

Typical example: During serial communication, since there is only one hardware resource, if two threads need to send at the same time, a mutual exclusion lock must be added.

Note: Mutexes cannot be used in interrupt service functions.

How Mutexes Work

A mutex is equivalent to a key. If two threads want to acquire the same resource, they must acquire the "key" first. After the current thread releases the "key", the next thread can continue to access the resource.

Image source: "RT-Thread Kernel Implementation and Application Development Practice"

insert image description here

Mutex Control Block

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;                         /**< 当前拥有互斥量的线程 */
};

Introduction to Mutex Function Interface

Create mutex rt_mutex_create()

rt_mutex_t rt_mutex_create (const char* name, rt_uint8_t flag);

You can call the rt_mutex_create function to create a mutex whose name is specified by name. The created mutex has different meanings due to the different flags specified: For an IPC object created using the PRIO priority flag, when multiple threads wait for resources, the thread with higher priority will give priority to obtaining resources. The IPC object created using the FIFO first-in-first-out flag will obtain resources in a first-come, first-served order when multiple threads wait for resources.

parameter describe
name mutex name
flag mutex flag

Delete mutex rt_mutex_delete()

rt_err_t rt_mutex_delete (rt_mutex_t mutex);

When a mutex is deleted, all threads waiting for this mutex will be awakened, and the return value obtained by the waiting threads is -RT_ERROR. Then the system deletes the mutex from the kernel object manager linked list and releases the memory space occupied by the mutex.

parameter describe
mutex A handle to the mutex object

Initialize mutex rt_mutex_init()

rt_err_t rt_mutex_init (rt_mutex_t mutex, const char* name, rt_uint8_t flag);

When using this function interface, you need to specify the handle of the mutex object (that is, the pointer to the mutex control block), the name of the mutex and the flag of the mutex. Mutex flags can use the flags mentioned in the Create Mutex function above.

parameter describe
mutex A handle to the mutex object
name mutex name
flag mutex flag

Mutex release rt_mutex_release()

rt_err_t rt_mutex_release(rt_mutex_t mutex);

When using this function interface, only the thread that already has control of the mutex can release it. Each time the mutex is released, its holding count is decremented by 1. When the mutex's hold count is zero (i.e. the holding thread has released all holding operations), it becomes available and threads waiting on the semaphore will be woken up.

parameter describe
mutex A handle to the mutex object

Mutex acquisition rt_mutex_take()

rt_err_t rt_mutex_take (rt_mutex_t mutex, rt_int32_t time);

If the mutex is not controlled by another thread, the thread that requested the mutex will successfully acquire the mutex. If the mutex is already controlled by the current thread, the holding count of the mutex is incremented by 1, and the current thread will not suspend waiting. If the mutex is already occupied by another thread, the current thread suspends waiting on the mutex until the other thread releases it or waits longer than the specified timeout.

parameter describe
mutex A handle to the mutex object
time The specified wait time, in OS Tick

Mutex usage considerations

Precautions:

  1. Two threads cannot hold the same mutex at the same time.
  2. Mutexes cannot be used in interrupt service routines.
  3. In order to ensure the real-time performance of RT-Thread, it should be avoided that the mutex is acquired and not released for a long time.
  4. Functions such as rt_thread_control() (modify thread priority) cannot be called on a thread while it is holding a mutex.

Mutex experiment

To use a mutex, you need rtconfig.hto open the relevant configuration in .

insert image description here
This experiment refers to the official Chinese document. The experiment will create 3 dynamic threads. After the low-priority thread obtains the mutex, it will be judged whether its priority has been adjusted to the highest priority among the waiting thread priorities.
The priorities of threads 1, 2, and 3 are high, medium, and low, respectively. Thread 3 holds the mutex first, and then lets thread 2 acquire it. At this time, thread 2 should be adjusted to the same priority as thread 2. This The checking process takes place in thread 1.

  • Note: This experiment thread 3 has a long delay after acquiring the mutex, and this usage should be avoided in actual use
#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;
							
}


Experimental phenomena

When thread 3 with the lowest priority holds the mutex and thread 2 also starts to acquire the mutex, the priority of thread 3 is raised to the priority of thread 2.

When thread 2 releases the mutex and uses the delay function to suspend, thread 3 acquires the mutex again, but because thread 2 is not in the mutex waiting queue, the priority of thread 3 remains unchanged.

When the thread 2 delay ends, it continues to try to acquire the mutex. At this time, the priority of thread 3 is raised to the priority of thread 2 again.

insert image description here

Guess you like

Origin blog.csdn.net/weixin_43772810/article/details/123786613