前言

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

一、互斥量工作机制

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

优先级继承 (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

(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 5int 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

四、互斥量使用注意事项

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 编程指南》

RT-Thread学习笔记——互斥量相关推荐

  1. C++11学习笔记-----互斥量以及条件变量的使用

    在多线程环境中,当多个线程同时访问共享资源时,由于操作系统CPU调度的缘故,经常会出现一个线程执行到一半突然切换到另一个线程的情况.以多个线程同时对一个共享变量做加法运算为例,自增的汇编指令大致如下, ...

  2. 5.FreeRTOS学习笔记- 互斥量

    基本概念 互斥量又称互斥信号量(本质是信号量),是一种特殊的二值信号量 互斥量 支持互斥量所有权.递归访问以及防止优先级翻转的特性,用于实现对临界资源(如显示器.打印机)的独占式访问. 任意时刻互斥量 ...

  3. FreeRTOS个人笔记-互斥量

    根据个人的学习方向,学习FreeRTOS.由于野火小哥把FreeRTOS讲得比较含蓄,打算在本专栏尽量细化一点.作为个人笔记,仅供参考或查阅. 配套资料:FreeRTOS内核实现与应用开发实战指南.野 ...

  4. freertos 创建互斥量_STM32CubeMX+FreeRTOS学习[6] 互斥量(Lu)

    FreeRTOS 学习之六:互斥量 前提:默认已经装好 MDK V5 和 STM32CubeMX ,并安装了 STM32F1xx 系列的支持包. 硬件平台: STM32F1xx 系列. 目的:学习互斥 ...

  5. FreeRTOS学习笔记——互斥型信号量

    来自:http://blog.csdn.net/xukai871105/article/details/43456985 0.前言 在嵌入式操作系统中互斥型信号量是任务间资源保护的重要手段.下面结合一 ...

  6. 常量与变量的区别(详细说明)(学习笔记3--变量与常量)

    前言: 如果你正在学习C语言而又不知道从何处开始学,你可以跟着我一起学习C语言,在寒假期间我每天都会发一篇博客,里面有各种C语言的知识点,如果你想学习下去,想进步,就来每天跟着我一起打卡吧,期待我们能 ...

  7. 进程通信学习笔记(互斥锁和条件变量)

    1.互斥锁:上锁和解锁 Posix互斥锁作为数据类型pthread_mutex_t的变量声明.如果互斥锁变量是静态分配的,那么可以把它初始化成常值PTHREAD_MUTEX_INITIALIZER.如 ...

  8. (原创)c#学习笔记05--变量的更多内容01--类型转换01--隐式转换

    第五章 变量的更多内容 本章内容: ●  如何在类型之间进行隐式和显式转换 ●  如何创建和使用枚举类型 ●  如何创建和使用结构类型 ●  如何创建和使用数组 ●  如何处理字符串值 首先要讨论的主 ...

  9. 操作系统学习笔记--互斥(共享资源)

    用信号量实现互斥.互斥是并发进程之间由于共享资源而形成的间接制约关系. Var mutex : semaphore := 1; //初值为1 Begin Parbegin(由部分开始) Process ...

最新文章

  1. 有序数组二分查找java_详解Java数据结构和算法(有序数组和二分查找)
  2. MapReduce学习总结之Combiner、Partitioner、Jobhistory
  3. UA MATH523A 实分析1 集合论基础6 一些点集拓扑基本概念
  4. 线性代数 第三章 矩阵的初等变换与线性方程组
  5. 高铁是如何跑起来?列车头顶高压线为什么磨不坏?
  6. 第5章 C++内存模型和原子类型操作
  7. 洛谷P4074 [WC2013]糖果公园(莫队)
  8. 约瑟夫环问题 poj 1012 poj 2244
  9. 计算机黑板报主题ps,Photoshop︱用PS创建一个黑板报
  10. 最新行政区划代码下载
  11. 网页上腾讯视频下载mp4格式到本地
  12. 第01课:敏捷教练和 ScrumMaster 基本功四部曲(iPad 版)
  13. Python图像库PIL的类Image及其方法介绍
  14. 【vim环境配置】详细实录
  15. c++:简单的运算符重载
  16. 漫步数学分析三十九——隐函数定理
  17. 康耐视VisionPro
  18. 【BZOJ4605】崂山白花蛇草水 权值线段树+kd-tree
  19. mysql忘记密码找回root密码
  20. PHP 使用parse_ini_file读取文本文档遇到中文乱码问题

热门文章

  1. 一个 Duang~ 的CSS3动画
  2. java短信接口_短信接口-- java
  3. SylixOS进化简史
  4. 【转载】巴菲特:比能力更重要的是靠谱
  5. 如何通俗易懂地阐述机器学习?
  6. Python调用HEG批量转换hdf影像为tiff
  7. 关于安装公司监控软件后导致Mac电脑需要每天重启4次才能连网的解决办法
  8. Android10.0 startService启动过程
  9. mysql写周报_mysql 数据分析如何实现日报、周报、月报和年报?
  10. CC00024.CloudOpenStack——|OpenStack组件.V02|——|OpenStack-Cinder块存储|配置block.V2|