RT-Thread学习笔记——互斥量

前言

前面学习了RT-Thread的信号量,但信号量在一些场合使用会存在优先级翻转问题,接下来我们学习互斥量,在 RT-Thread 操作系统中,互斥量可以解决优先级翻转问题,实现的是优先级继承算法。互斥量和信号量不同的是:拥有互斥量的线程拥有互斥量的所有权,互斥量支持递归访问且能防止线程优先级翻转;并且互斥量只能由持有线程释放,而信号量则可以由任何线程释放。这里主要讲RT-Thread互斥量的工作机制、相关函数,在后面使用潘多拉开发板进行实验(STM32L475VET6)。

 一、互斥量工作机制

1、在 RT-Thread 操作系统中,互斥量可以解决优先级翻转问题,实现的是优先级继承算法。优先级继承是通过在线程 A 尝试获取共享资源而被挂起的期间内,将线程 C 的优先级提升到线程 A 的优先级别,从而解决优先级翻转引起的问题。这样能够防止 C(间接地防止 A)被 B 抢占,如下图所示。优先级继承是指,提高某个占有某种资源的低优先级线程的优先级,使之与所有等待该资源的线程中优先级最高的那个线程的优先级相等,然后执行,而当这个低优先级线程释放该资源时,优先级重新回到初始设定。因此,继承优先级的线程避免了系统资源被任何中间优先级的线程抢占。

RT-Thread学习笔记——互斥量_第1张图片 优先级继承 (M 为互斥量 )——来源RT-Thread编程指南

 

 

二、互斥量的相关函数

1、创建动态互斥量函数

创建一个互斥量时,内核首先创建一个互斥量控制块,然后完成对该控制块的初始化工作。

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

(1)入口参数:

 name:互斥量的名称。
 flag:互斥量标志,它可以取如下数值:RT_IPC_FLAG_FIFO 或RT_IPC_FLAG_PRIO。为 RT_IPC_FLAG_PRIO时,表示在多个线程等待资源时,将由优先级高的线程优先获得资源。为 RT_IPC_FLAG_FIFO时,表示在多个线程等待资源时,将按照先来先得的顺序获得资源。

(2)返回值:

互斥量句柄:创建成功。
RT_NULL:创建失败。

2、删除动态互斥量函数

当不再使用动态互斥量时,通过删除动态互斥量以释放系统资源。当删除一个动态互斥量时,所有等待此互斥量的线程都将被唤醒,等待线程获得的返回值是 RT_ERROR,然后系统将该互斥量从内核对象管理器链表中删除并释放互斥量占用的内存空间。

rt_err_t rt_mutex_delete(rt_mutex_t mutex);

(1)入口参数:

 mutex:要删除的动态互斥量对象的句柄。

(2)返回值:

 RT_EOK:删除成功。

3、创建静态互斥量函数

这里所说的创建静态互斥量也就是《RT-Thread编程指南》里面的初始化互斥量,静态互斥量对象的内存是在系统编译时由编译器分配的,一般放于读写数据段或未初始化数据段中。

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

(1)入口参数:

 mutex:互斥量对象的句柄,它由用户提供,并指向互斥量对象的内存块。
 name:互斥量的名称。
 flag:互斥量标志,它可以取如下数值:RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO。

(2)返回值:
 RT_EOK:创建成功。

4、删除静态互斥量函数

这里所说的删除静态互斥量也就是《RT-Thread编程指南》里面所讲的脱离互斥量,脱离互斥量将把互斥量对象从内核对象管理器中脱离。使用该函数接口后,内核先唤醒所有挂在该互斥量上的线程(线程的返回值是RT_ERROR),然后系统将该互斥量从内核对象管理器中脱离。

rt_err_t rt_mutex_detach (rt_mutex_t mutex);

(1)入口参数:

 mutex:要删除的静态互斥量对象的句柄。

(2)返回值:

 RT_EOK:删除成功。

5、获取互斥量函数

线程获取了互斥量,那么线程就有了对该互斥量的所有权,即某一个时刻一个互斥量只能被一个线程持有。如果互斥量没有被其他线程控制,那么申请该互斥量的线程将成功获得该互斥量。如果互斥量已经被当前线程线程控制,则该互斥量的持有计数加 1,当前线程也不会挂起等待。如果互斥量已经被其他线程占有,则当前线程在该互斥量上挂起等待,直到其他线程释放它或者等待时间超过指定的超时时间。

rt_err_t rt_mutex_take (rt_mutex_t mutex, rt_int32_t time);

(1)入口参数:

 mutex:互斥量对象的句柄。
 time:指定等待的时间。

(2)返回值:

 RT_EOK:成功获得互斥量。
 RT_ETIMEOUT:超时。
 RT_ERROR:获取失败。

6、释放互斥量函数

当线程完成互斥资源的访问后,应尽快释放它占据的互斥量,使得其他线程能及时获取该互斥量。使用该函数接口时,只有已经拥有互斥量控制权的线程才能释放它,每释放一次该互斥量,它的持有计数就减 1。当该互斥量的持有计数为零时(即持有线程已经释放所有的持有操作),它变为可用,等待在该信号量上的线程将被唤醒。如果线程的运行优先级被互斥量提升,那么当互斥量被释放后,线程恢复为持有互斥量前的优先级。

rt_err_t rt_mutex_release(rt_mutex_t mutex);

(1)入口参数:

 mutex:互斥量对象的句柄。

(2)返回值:

 RT_EOK :成功。

 

三、基于STM32的互斥量示例

前面说了很多互斥量的理论知识,光说不练都是假把式,那么接下来我们就进行实际的操作。以下将列举两个示例,一个来演示互斥锁功能,另外一个演示防止优先级翻转特性,采用RTT&正点原子联合出品的潘多拉开发板进行实验。

1、互斥锁示例

在演示互斥锁功能,将定义一个全局变量用作公共资源,创建一个互斥量和两个线程,其中一个线程用于改变全局变量的值,另一个线程用于打印出改变后的全局变量值,互斥量用于防止变量值还没改变就被打印了。

(1)实现代码

/* 线程句柄 */
static rt_thread_t change_value_thread = RT_NULL;
static rt_thread_t show_value_thread = RT_NULL;

/* 互斥量句柄 */
static rt_mutex_t mutex = RT_NULL;

/* 全局变量,公共资源 */
char g_value = 'A';

/**************************************************************
函数名称 : change_value_thread_entry
函数功能 : 修改公共资源线程入口函数
输入参数 : parameter:入口参数
返回值  	 : 无
备注		 : 无
**************************************************************/
void change_value_thread_entry(void *parameter)
{
	while(1) 
	{
		rt_mutex_take(mutex, RT_WAITING_FOREVER);/* 获取互斥量,一直等直到获取到 */

		if(g_value < 'Z')
		{
			g_value++;
		}
		else
		{
			g_value = 'A';
		}

		rt_mutex_release(mutex); /* 释放互斥量 */
		
		rt_thread_mdelay(1);
		rt_thread_yield(); /* 放弃剩余时间片,进行一次线程切换 */
	}
}

/**************************************************************
函数名称 : show_value_thread_entry
函数功能 : 打印g_value
输入参数 : parameter:入口参数
返回值  	 : 无
备注		 : 无
**************************************************************/
void show_value_thread_entry(void *parameter)
{
	while(1) 
	{
		rt_mutex_take(mutex, RT_WAITING_FOREVER);/* 获取互斥量,一直等直到获取到 */

		rt_kprintf("g_value:%c\r\n", g_value);
		LED_R(0);
		rt_thread_mdelay(2000);
		LED_R(1);
		rt_thread_mdelay(2000);
		
		rt_mutex_release(mutex); /* 释放互斥量 */
	}
}


void rtthread_mutex_test(void)
{
	/* 创建一个互斥量,先进先出模式*/
	mutex = rt_mutex_create("mutex_test", RT_IPC_FLAG_PRIO);

	if(RT_NULL != mutex)
	{
		rt_kprintf("create mutex successful\r\n");
	}
	else
	{
		rt_kprintf("create mutex failed\r\n");
		return;
	}
        
        /* 创建打印显示公共资源g_value线程 */
	show_value_thread = rt_thread_create("show_value_thread",
						show_value_thread_entry,
						RT_NULL,
						512,
						3,
						20
						);
	if(RT_NULL != show_value_thread)/* 创建成功,启动线程 */
	{
		rt_thread_startup(show_value_thread);;
	}
	else
	{
		rt_kprintf("create show_value_thread failed\r\n");
		return;
	}

	/* 创建改变公共资源g_value线程 */
	change_value_thread = rt_thread_create("change_value_thread",
						change_value_thread_entry,
						RT_NULL,
						512,
						4,
						20
						);
	if(RT_NULL != change_value_thread)/* 创建成功,启动线程 */
	{
		rt_thread_startup(change_value_thread);
	}
	else
	{
		rt_kprintf("create change_value_thread failed\r\n");
		return;
	}
        
}

(2)观察FisSH

RT-Thread学习笔记——互斥量_第2张图片

(3)输入list_mutex,可以看到当前互斥量持有者

2、防止优先级翻转示例

将创建 3 个动态线程以检查持有互斥量时,持有的线程优先级是否被调整到等待线程优先级中的最高优先级,此示例摘自《RT-Thread编程指南》。

(1)实现代码

#include "main.h"
#include "board.h"
#include "rtthread.h"
#include "data_typedef.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 mutex = RT_NULL;

#define THREAD_PRIORITY 10
#define THREAD_STACK_SIZE 512
#define THREAD_TIMESLICE 5


int pri_inversion(void);

int main(void)
{
	pri_inversion();
	
	return 0;
}

/* 线 程 1 入 口 */
static void thread1_entry(void *parameter)
{
	/* 先 让 低 优 先 级 线 程 运 行 */
	rt_thread_mdelay(100);
	/* 此 时 thread3 持 有 mutex, 并 且 thread2 等 待 持有 mutex */
	/* 检 查 thread2 与 thread3 的 优 先 级 情 况 */
	if (tid2->current_priority != tid3->current_priority)
	{
		/* 优 先 级 不 相 同, 测 试 失 败 */
		rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority);
		rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority);
		rt_kprintf("test failed.\n");
		return;
	}
	else
	{
		rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority);
		rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority);
		rt_kprintf("test OK.\n");
	}
}


/* 线 程 2 入 口 */
static void thread2_entry(void *parameter)
{
	rt_err_t result;
	
	rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority);
	/* 先 让 低 优 先 级 线 程 运 行 */
	rt_thread_mdelay(50);
	/*
	* 试 图 持 有 互 斥 锁, 此 时 thread3 持 有, 应 把 thread3 的 优 先 级 提 升
	* 到 thread2 相 同 的 优 先 级
	*/
	result = rt_mutex_take(mutex, RT_WAITING_FOREVER);
	if (result == RT_EOK)
	{
		/* 释 放 互 斥 锁 */
		rt_mutex_release(mutex);
	}
}

/* 线 程 3 入 口 */
static void thread3_entry(void *parameter)
{
	rt_tick_t tick;
	rt_err_t result;
	
	rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority);
	result = rt_mutex_take(mutex, RT_WAITING_FOREVER);
	if (result != RT_EOK)
	{
		rt_kprintf("thread3 take a mutex, failed.\n");
	}
	
	/* 做 一 个 长 时 间 的 循 环,500ms */
	tick = rt_tick_get();
	while (rt_tick_get() - tick < (RT_TICK_PER_SECOND / 2)) ;
	rt_mutex_release(mutex);
}

int pri_inversion(void)
{
	/* 创 建 互 斥 锁 */
	mutex = rt_mutex_create("mutex", RT_IPC_FLAG_FIFO);
	if (mutex == RT_NULL)
	{
		rt_kprintf("create dynamic mutex failed.\n");
		return -1;
	}

	/* 创 建 线 程 1 */
	tid1 = rt_thread_create("thread1",
				thread1_entry,
				RT_NULL,
				THREAD_STACK_SIZE,
				THREAD_PRIORITY - 1, THREAD_TIMESLICE
				);
	
	if (tid1 != RT_NULL)
		rt_thread_startup(tid1);
	/* 创 建 线 程 2 */
	tid2 = rt_thread_create("thread2",
			        thread2_entry,
			        RT_NULL,
				THREAD_STACK_SIZE,
				THREAD_PRIORITY, THREAD_TIMESLICE
				);
	if (tid2 != RT_NULL)
		rt_thread_startup(tid2);
	/* 创 建 线 程 3 */
	tid3 = rt_thread_create("thread3",
				thread3_entry,
				RT_NULL,
				THREAD_STACK_SIZE,
				THREAD_PRIORITY + 1, THREAD_TIMESLICE
				);
	if (tid3 != RT_NULL)
		rt_thread_startup(tid3);
	return 0;
}

(2)观察FinSH

RT-Thread学习笔记——互斥量_第3张图片

 

四、互斥量使用注意事项

1.  两个线程不能对同时持有同一个互斥量。如果某线程对已被持有的互斥量进行获取,则该线程会被挂起,直到持有该互斥量的线程将互斥量释放成功,其他线程才能申请这个互斥量。

2.  互斥量不能在中断服务程序中使用。

3.  RT-Thread 作为实时操作系统需要保证线程调度的实时性,尽量避免线程的长时间阻塞,因此在获得互斥量之后,应该尽快释放互斥量。

4.  持有互斥量的过程中,不得再调用 rt_thread_control()等函数接口更改持有互斥量线程的优先级。

 

五、互斥量和信号量的区别

下面内容来自RT-Thread社区kamutulafu的帖子:互斥量和信号量的区别 

原文链接:https://www.rt-thread.org/qa/forum.php?mod=viewthread&tid=8356&highlight=%E4%BA%92%E6%96%A5%E9%87%8F

1. 互斥量用于线程的互斥,信号量用于线程的同步。这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。

互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源

2. 互斥量值只能为0/1,信号量值可以为非负整数。

也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。

3. 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。

 

参考文献:

1、[野火®]《RT-Thread 内核实现与应用开发实战—基于STM32》

2、《RT-THREAD 编程指南》

 

 

你可能感兴趣的:(RTThread物联网操作系统)