RT-Thread快速入门-互斥量

首发,公众号【一起学嵌入式】

引言

互斥量,即互斥信号量(Mutex,Mutual Exclusion 的缩写)。互斥量的主要作用是对资源实现互斥访问。二值信号量也可以实现对资源的互斥访问,那么为何要引入互斥量呢?互斥量和信号量有什么不同呢?

这其中涉及到两个重要的知识点:

  • 优先级翻转
  • 优先级继承

理解了这两点内容,互斥量也就基本掌握了。

理解互斥量

互斥量是一种保护共享资源的方法,当一个线程拥有互斥量时,可以保护共享资源不被其他线程破坏。

一个线程持有互斥量时,其他线程不能再持有它,持有该互斥量的线程也能够再次获得这个互斥量,而不被挂起,即互斥量可以递归持有。对于信号量,不支持递归获取,若递归获取会形成死锁。

互斥量可以防止线程优先级翻转,二值信号量不支持。那么什么是优先级翻转呢?

优先级翻转问题

优先级翻转通俗解释:当一个高优先级线程试图通过信号量机制访问共享资源时,如果该信号量已被一个低优先级线程持有,而这个低优先级线程在运行过程中可能又被其它一些中等优先级的线程抢占,因此造成高优先级线程被许多具有较低优先级的线程阻塞。

举例说明如下图:

在这里插入图片描述

三个线程,优先级由高到低分别为:Thread1 > Thread2 > Thread3。线程 Thread1 运行过程中需要访问某个共享资源,发现 Thread3 正在访问,Thread1 进入挂起状态,等待 Thread3 释放共享资源。

在 Thread3 执行过程中,Thread2 就绪,抢占了 Thread3 的运行。等 Thread2 执行完毕后,Thread1 接着执行,释放共享资源后,Thread1 得以继续运行。

在这种情况下,出现了优先级翻转的问题:线程 Thread2 优先 Thread1 执行完毕,即 Thread1 需要等待 Thread2 执行完毕后才有机会运行,这与基于优先级的抢占式调度正好反了。

因此,RTOS 引入了互斥量,用于避免二值信号量使用过程产生的优先级翻转问题。

互斥量之所以能够防止优先级翻转问题的发生,是因为在实现过程中采用了优先级继承算法。

优先级继承

所谓的优先级继承是指,提高某个占有某种资源的低优先级线程的优先级,使之与所有等待该资源的线程中优先级最高的那个线程的优先级相等,然后执行,而当这个低优先级线程释放该资源时,优先级重新回到初始设定。

因此,继承优先级的线程避免了系统资源被任何中间优先级的线程抢占。

在上例中,最低优先级线程 Thread3 在拥有互斥量过程中,会临时将优先级提高到与 Thread1 的优先级相同,即使线程 Thread2 达到就绪状态,也不能够立即执行,需要等待 Thread1 执行完毕,才具备运行条件。

注意,在获得互斥量后,应该尽快释放,并在持有互斥量的过程中,不得再更改持有互斥量线程的优先级。

互斥量控制块

RT-Thread 管理互斥量的数据结构为互斥量控制块,由结构体 struct 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;           /* 当前拥有互斥量的线程 */
};
/* rt_mutext_t 为指向互斥量结构体的指针类型 */
typedef struct rt_mutex* rt_mutex_t;

另外 rt_mutex_t 表示的是互斥量的句柄,也就是指向互斥量控制块的指针。

从面向对象的角度来看,rt_mutex 对象是从 rt_ipc_object 派生而来,由 IPC 容器管理。

互斥量的操作

在 RT-Thread 中,对一个互斥量的操作包括:

  • 创建/初始化互斥量
  • 获取互斥量
  • 释放互斥量
  • 删除/脱离互斥量

在这里插入图片描述

其中常用的操作无非就是:创建互斥量、获取互斥量、释放互斥量。

注意:互斥量不能在中断服务程序中使用。

1. 创建互斥量

RT-Thread 中动态创建互斥量的函数接口如下:

rt_mutex_t rt_mutex_create (const char* name, rt_uint8_t flag)

调用此函数创建一个互斥量时,内核会自动创建一个互斥量控制块,并从内核对象管理器中分配一个 mutex 对象,然后对其初始化。

参数 name 为互斥量的名字;flag 用来设置等待互斥量的线程队列排序方式。

互斥量创建成功,函数返回互斥量句柄;创建失败,则返回 RT_NULL

参数 Flag 的取值有两种:

  • RT_IPC_FLAG_PRIO,多个等待互斥量的线程按照优先级高低进行排序。
  • RT_IPC_FLAG_FIFO,多个等待互斥量的线程按照先进先出的方式进行排序。

静态方式创建互斥量需要两步:(1)定义一个互斥量控制块结构体变量(2)调用函数对其初始化。

初始化互斥量的函数接口如下:

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

该函数对参数 mutex 指定的互斥量控制块进行初始化。另外两个参数 nameflag 与动态创建函数相同。

2. 获取互斥量

RT-thread 提供的获取互斥量的函数接口如下,线程通过调用此函数来获取某个互斥量。

rt_err_t rt_mutex_take (rt_mutex_t mutex, rt_int32_t time)

参数 mutex 为互斥量句柄;time 表示等待超时时间,单位为系统节拍。

如果互斥量可用,那么申请互斥量的线程将会成功获取,线程就有了对互斥量的所有权。若该线程继续获取互斥量,则互斥量的持有计数加 1,当先线程不会挂起等待(即支持递归获取互斥量)。

如果互斥量已经被其他线程占用,则当前线程会进入挂起状态,等待其他线程释放该互斥量或者等待超时时间达到。

rt_mutex_take() 返回 RT_EOK 表示获取成功;返回 -RT_ETIMEOUT 表示获取超时;-RT_ERROR 获取失败。

3. 释放互斥量

当线程用完互斥资源后,应该尽快释放它占据的互斥量,使得其他线程能够及时获取。

释放互斥量的函数接口为:

rt_err_t rt_mutex_release(rt_mutex_t mutex)

只有已经拥有互斥量的线程才能释放它,每释放一次该互斥量,它的持有计数就减 1。当该互斥量的持有计数为零时(即持有线程已经释放所有的持有操作),它变为可用,等待在该互斥量上的线程将被唤醒。

如果线程优先级被互斥量提升,那么当释放互斥量后,线程恢复为最初的优先级。

实例演示

多说无益,整个示例来看看。

创建两个线程,线程 1 对两个数值变量加 1;线程 2 也对两个数分别加 1,使用互斥量保证线程改变 2 个数值的操作不被打断。代码如下:

#include <rtthread.h>

#define THREAD_PRIORITY 8
#define THREAD_TIMESLICE 5

/* 指向互斥量的指针 */
static rt_mutex_t dynamic_mutex = RT_NULL;
/* 全局变量 */
static rt_uint8_t number1,number2 = 0;

static void rt_thread1_entry(void *parameter)
{
	while(1)
	{
		/* 线程1获取到互斥量后,先后对number1、number2进行加 1操作,然后释放互斥量 */
		rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER);
		number1++;		
		number2++;
		rt_mutex_release(dynamic_mutex);
		rt_thread_delay(5);
	}
}

static void rt_thread2_entry(void *parameter)
{
	while(1)
	{
		/* 线程2获取到互斥量后,检查number1、number2的值是否相同,
			相同则表示 mutex 起到了锁的作用 */
		rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER);
		if(number1 != number2)
		{
			rt_kprintf("not protect.number1 = %d, mumber2 = %d \n",number1 ,number2);
		}
		else
		{
			rt_kprintf("mutex protect ,number1 = mumber2 is %d\n",number1);
		}
		number1++;
		number2++;
		rt_mutex_release(dynamic_mutex);

		if(number1 >= 10)
		{
			return;
		}
	}
}

int main()
{
	/* 线程控制块指针 */
	rt_thread_t thread1 = RT_NULL;
	rt_thread_t thread2 = RT_NULL;

	/* 创建一个动态互斥量 */
	dynamic_mutex = rt_mutex_create("dmutex", RT_IPC_FLAG_FIFO);
	if (dynamic_mutex == RT_NULL)
	{
		rt_kprintf("create dynamic mutex failed.\n");
		return -1;
	}

	/* 动态创建线程1 */
	thread1 = rt_thread_create("thread1", rt_thread1_entry, RT_NULL,
					1024, THREAD_PRIORITY, THREAD_TIMESLICE);
	
	if(thread1 != RT_NULL)
	{
		/* 启动线程 */
		rt_thread_startup(thread1);
	}

	/* 动态创建线程2 */
	thread2 = rt_thread_create("thread2", rt_thread2_entry, RT_NULL,
					1024, THREAD_PRIORITY-1, THREAD_TIMESLICE);
	if(thread2 != RT_NULL)
	{
		/* 启动线程 */
		rt_thread_startup(thread2);
	}	
}

线程 1 和线程 2 均使用互斥量保护对两个 numer 的操作,程序运行结果如下

在这里插入图片描述

不常用函数接口

对于互斥量操作,还剩删除函数没有介绍。

删除动态创建的互斥量操作函数为:

rt_err_t rt_mutex_delete (rt_mutex_t mutex);

当不再使用互斥量时,通过删除互斥量可以释放系统资源。删除互斥量时,等待互斥量的线程都会被唤醒,互斥量占用的内存空间被释放。

删除静态方式创建的互斥量操作函数为:

rt_err_t rt_mutex_detach (rt_mutex_t mutex);

该函数的作用是,把互斥量对象从内核管理器中脱离。内核唤醒所有挂起在该互斥量上的线程。

OK,今天先到这,下次继续。加油~


关注公众【一起学嵌入式】,回复 “rt-thread”,获取学习资源

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
RT-Thread作品秀】RedClock 作者:iysheng 概述 我开发RedClock这个作品的背景主要有两个: 1.我想开发一下墨水屏,想体验下新奇的事物; 2.想在传统wifi时钟的基础上添加一个番茄时钟的功能,尝试提高工作效率,让其不单单具备提供时间和天气的功能 目前RedClock实现的功能,可以概括为4条: 1.通过墨水屏同步显示时间、天气图标(时间信息通过ntp校时,天气信息通过js语言从和风天气获取) 2.一键切换番茄时钟模式,番茄时钟的默认周期是25分钟,到实践通过屏幕提示 3.通过外接氣壓传感器lps22hh获取温度和气压信息并通過墨水屏幕 4.時間、天氣、温度和气压信息支持通过网络访问ART-Pi的设备IP同步显示 开发环境 硬件:ART-Pi、LPS22HH、SSD1619 RT-Thread版本:V4.0.3 开发工具及版本: 编译工具:arm-none-eabi-gcc、arm-none-eabi-binutils 编辑工具:vim 烧录、调试工具:openocd 0.10+ dirty(打过补丁才可以烧录程序到ART-Pi的外部flash)、kermit(串口工具)、telnet(调试工具)、gdb 硬件开发工具:Kicad(RedClock扩展板开发工具) RT-Thread使用情况概述 使用到的内核模块部分: 互斥锁、信号、mempool、device 组件部分: cJSON、EasyFlash、webnet 软件包部分: lps22hb(传感器) 其他: 使用到的总线接口:I2C(传感器)、SPI(墨水屏) 硬件框架 软件框架说明 软件的实现设计到两个层次,设备驱动层(墨水屏驱动层开发),APP层(天气、传感器和时间的更新显示分别在不同的线程完成,线程间的数据交换通过函数完成,全局变均为static类型,依据高内聚、低耦合分层设计理念开发) 软件模块说明 本软件主要的软件设计理念是创建多个线程,根据功能划分各个线程实现的功能: 在factory工程的基础上,添加了red_monitor_thread这个线程,完成传感器数据周期一分钟获取一次、以及更新tomato显示相关的内容。还有一个redclock_lcd_thread线程周期性的刷新屏幕,目前从屏幕寿命考虑,设置为3分钟刷新一次。还有一个线程完成对按键的扫描(目前仅仅支持番茄时钟快速切换的按键)。 考虑到为了实现一次配网,重启后自动联网,开启了easyflash保存wifi帐号和密码。联网为了消除跨域访问设备的问题,替换了原始ART-Pi的index.html文件为自己的文件,这样将对应的js和css部署在自己电脑这端完成开发和调试,借助webnet通过CGI的方式完成时间、温度、气压的上报(支持同步通过浏览器访问ART-Pi查看)和天气的下发(天气通过js连接和风天气服务获取)。 墨水屏的开发,通过注册了一个device完成,通过SPI接口访问。更新墨水瓶内容相关的动作,放在了四个函数中完成(分别完成天气图标更新、时间更新]、传感器数据更新、番茄时钟状态更新), 这些函数指挥更新墨水屏显示的内容,不会刷新墨水屏显示,刷新墨水屏显示的时候,更新才会有效。 演示效果 演示视频: 圖片展示: 比赛感悟 比赛过程中,我使用Kicad画了两版PCB,并作PCB然后焊接,整个开发过程大部分都是在Linux下完成的,初了开发墨水屏显示界面的时候使用了磨刀这个原型开发工具以及Image2Lcd整个软件将图片转换为C语言。 感觉自己收获最多的地方有两个方面,第一个方面当然是软件了,印象最深刻的是通过在openocd的代码仓库察看不同的branch和patch,给openocd打补丁然后修改对应的配置文件,支持直接烧录程序到ART-Pi的外部flash。通过这个工作,让我对openocd的使用有了更多的了解(通过访问openocd的网站以及看对应的manual), 这个是我最激动的。其他的软件方面就是对easyflash和webnet这两个组件有了学习,通过阅读对应的代码,学到了在32上进行KV类型的数值保存的新方法,之前我接触32保存配置数据使用的方法是从内部flash的尾部划分64KB虚拟为快设备,然后来格式化为elm文件系统,然后以cJSON格式保存,现在看来浪费了很多空间,easyflash也是我以后工作中替换之前这种方案新的可取的方法。通过对Webnet的学习,我真正接触到了通过http服务,远程通过浏览器和32进行数据通讯的方法。围绕webnet, 我还真正开发了一波js和css代码,开发js代码的时候,学会了通过jquery实现post和get请求,然后ART-Pi这端通过CGI完成HTTP通讯。 硬件方面,我感觉很开心的是自己
RT-Thread诞生于2006年,是一款以开源、中立、社区化发展起来的物联网操作系统。 RT-Thread主要采用 C 语言编写,浅显易懂,且具有方便移植的特性(可快速移植到多种主流 MCU 及模组芯片上)。RT-Thread把面向对象的设计方法应用到实时系统设计中,使得代码风格优雅、架构清晰、系统模块化并且可裁剪性非常好。 RT-Thread有完整版和Nano版,对于资源受限的微控制器(MCU)系统,可通过简单易用的工具,裁剪出仅需要 3KB Flash、1.2KB RAM 内存资源的 NANO 内核版本;而相对资源丰富的物联网设备,可使用RT-Thread完整版,通过在线的软件包管理工具,配合系统配置工具实现直观快速的模块化裁剪,并且可以无缝地导入丰富的软件功能包,实现类似 Android 的图形界面及触摸滑动效果、智能语音交互效果等复杂功能。 RT-Thread架构 RT-Thread是一个集实时操作系统(RTOS)内核、中间件组件的物联网操作系统,架构如下: 内核层:RT-Thread内核,是 RT-Thread的核心部分,包括了内核系统中对象的实现,例如多线程及其调度、信号、邮箱、消息队列、内存管理、定时器等;libcpu/BSP(芯片移植相关文件 / 板级支持包)与硬件密切相关,由外设驱动和 CPU 移植构成。 组件与服务层:组件是基于 RT-Thread内核之上的上层软件,例如虚拟文件系统、FinSH命令行界面、网络框架、设备框架等。采用模块化设计,做到组件内部高内聚,组件之间低耦合。 RT-Thread软件包:运行于 RT-Thread物联网操作系统平台上,面向不同应用领域的通用软件组件,由描述信息、源代码或库文件组成。RT-Thread提供了开放的软件包平台,这里存放了官方提供或开发者提供的软件包,该平台为开发者提供了众多可重用软件包的选择,这也是 RT-Thread生态的重要组成部分。软件包生态对于一个操作系统的选择至关重要,因为这些软件包具有很强的可重用性,模块化程度很高,极大的方便应用开发者在最短时间内,打造出自己想要的系统。RT-Thread已经支持的软件包数已经达到 180+。 RT-Thread的特点: 资源占用极低,超低功耗设计,最小内核(Nano版本)仅需1.2KB RAM,3KB Flash。 组件丰富,繁荣发展的软件包生态 。 简单易用 ,优雅的代码风格,易于阅读、掌握。 高度可伸缩,优质的可伸缩的软件架构,松耦合,模块化,易于裁剪和扩展。 强大,支持高性能应用。 跨平台、芯片支持广泛。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zsky_01

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值