RT-Thread学习笔记2-互斥量与信号量
1. 临界区保护
临界区是仅允许一个线程访问的共享资源。它可以是一个具体的硬件设备,也可以是一个变量、一个缓冲区。多个线程必须互斥的对他们进行访问
1.1 方法一:关闭系统调度保护临界区
禁止调度
/* 调度器商锁,上锁后不再切换到其他线程,仅响应中断 */
rt_enter_critical();
/* 临界操作 */
rt_exit_critical();
关闭中断:因为所有的线程的调度都是建立在中断的基础上的,所以当关闭中断后系统将不能再进行调度,线程自身也自然不会被其他线程抢占了
rt_base_t level;
/* 关闭中断 */
level = rt_hw_interrupt_disable();
/* 临界操作 */
rt_hw_interrupt_enable(level);
1.2 方法二:互斥特性保护临界区
信号量、互斥量
2. 信号量
嵌入式系统运行的代码主要包括线程和ISR,在它们的运行过程中,它们的运行步骤有时需要同步(按照预定的先后次序运行),有时访问的资源需要互斥(一个时刻只允许一个线程访问资源),有时也需要比本次交换数据。这些机制成为进程间通信IPC。RT-Thread中的IPC机制包括信号量、互斥量、事件、邮箱、消息队列。通过IPC,可以协调多个线程(包括ISR)默契的工作。信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的。每个信号量对象都有一个信号量值和一个线程等待队列。信号量的值对应信号量对象的实例数目(资源数目),如果信号量值N,则表示有N个信号量实例(资源)可被使用。当值为0时,再请求该信号量的的线程,就会被挂起在该信号量的等待队列上。
2.1 信号量的定义
struct rt_semaphore
{
struct rt_ipc_object parent; /* IPC对象继承而来 */
rt_uint16_t value; /* 信号量的值 */
}
静态信号量:struct rt_semaphore static_sem
动态信号量:rt_sem_t dynamic_sem
typedef struct rt_semaphore *rt_sem_t;
2.2 信号量的操作
- 初始化与脱离
静态信号量:
rt_err_t rt_sem_init(rt_sem_t sem, const char *name, rt_uint32_t value, rt_uint8_t flag)
rt_err_t rt_sem_detach(rt_sem_t sem) //将不用的静态信号量从系统中脱离
- 创建与删除
判断一下返回值是不是RT_NULL
动态信号量:
rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag) //等待队列中的线程,当有资源的时候: RT_IPC_FLAG_FIFO先来先服务,先后顺序排列 RT_IPC_FLAG_PRIO按照线程优先级排列
rt_err_t rt_sem_delete(rt_sem_t sem)//释放系统资源
- 获取信号量
rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time) //RT_WAITING_FOREVER = -1, 以系统滴答时钟为单位。即100HZ,等待10ms的倍数。如果超时则返回-RT_ETIMEOUT.切忌该函数不可在中断中调用,因为它会导致线程被挂起。只能在线程调用
rt_err_t rt_sem_trytake(rt_sem_t sem) //时间参数为0,一秒钟都不等待
- 释放信号量
rt_err_t rt_sem_release(rt_sem_t sem) // 既可以在线程,也可以在中断中调用。因为它不会导致线程被挂起
3. 生产者、消费者问题
两个线程,一个生产者线程和一个消费者线程,两个线程共享一个初始为空、固定大小为n的缓存区。生产者的工作是生产一段数据,只有缓冲区没满时,生产者才能把消息放入到缓冲区,否则必须等待,如此反复。只有缓冲区非空时,消费者才能从中取出数据,一次消费一段数据,否则必须等待。问题的核心是:
- 保证不让生产者在缓存还是满的时候仍然要向内写数据
- 不让消费者试图从空的缓存中取出数据
解决生产者消费者问题实际上是要解决线程间互斥关系和同步关系问题。由于缓冲区是临界资源,一个时刻只允许一个生产者放入消息,或者一个消费者从中取出消息。这里需要解决一个互斥访问的问题。同时生产者和消费者又是一个相互协作的关系,只有生产者生产之后,消费者才能消费,所以还需要解决一个同步的问题

/* 生成者线程入口 */
void producer_thread_entry(void* parameter)
{
int cnt = 0;
/* 运行100次 */
while( cnt < 100)
{
/* 获取一个空位 */
rt_sem_take(&sem_empty, RT_WAITING_FOREVER);
/* 修改array内容,上锁 */
rt_sem_take(&sem_lock, RT_WAITING_FOREVER);
array[set%MAXSEM] = cnt + 1;
rt_kprintf("the producer generates a number: %d\n", array[set%MAXSEM]);
set++;
rt_sem_release(&sem_lock);
/* 发布一个满位 */
rt_sem_release(&sem_full);
cnt++;
/* 暂停一段时间 */
rt_thread_delay(50);
}
rt_kprintf("the producer exit!\n");
}
/* 消费者线程入口 */
void consumer_thread_entry(void* parameter)
{
rt_uint32_t no;
rt_uint32_t sum;
/* 第n个线程,由入口参数传进来 */
no = (rt_uint32_t)parameter;
sum = 0;
while(1)
{
/* 获取一个满位 */
rt_sem_take(&sem_full, RT_WAITING_FOREVER);
/* 临界区,上锁进行操作 */
rt_sem_take(&sem_lock, RT_WAITING_FOREVER);
sum += array[get%MAXSEM];
rt_kprintf("the consumer[%d] get a number: %d\n", no, array[get%MAXSEM] );
get++;
rt_sem_release(&sem_lock);
/* 释放一个空位 */
rt_sem_release(&sem_empty);
/* 生产者生产到100个数目,停止,消费者线程相应停止 */
if (get == 100) break;
/* 暂停一小会时间 */
rt_thread_delay(10);
}
rt_kprintf("the consumer[%d] sum is %d \n ", no, sum);
rt_kprintf("the consumer[%d] exit!\n");
}
int semaphore_producer_consumer_init()
{
/* 初始化3个信号量 */
rt_sem_init(&sem_lock , "lock", 1, RT_IPC_FLAG_FIFO);
rt_sem_init(&sem_empty, "empty", MAXSEM, RT_IPC_FLAG_FIFO);
rt_sem_init(&sem_full , "full", 0, RT_IPC_FLAG_FIFO);
/* 创建线程1 */
producer_tid = rt_thread_create("producer",
producer_thread_entry, RT_NULL, /* 线程入口是producer_thread_entry, 入口参数是RT_NULL */
THREAD_STACK_SIZE, THREAD_PRIORITY - 1, THREAD_TIMESLICE);
if (producer_tid != RT_NULL)
rt_thread_startup(producer_tid);
else
tc_stat(TC_STAT_END | TC_STAT_FAILED);
/* 创建线程2 */
consumer_tid = rt_thread_create("consumer",
consumer_thread_entry, RT_NULL, /* 线程入口是consumer_thread_entry, 入口参数是RT_NULL */
THREAD_STACK_SIZE, THREAD_PRIORITY + 1, THREAD_TIMESLICE);
if (consumer_tid != RT_NULL)
rt_thread_startup(consumer_tid);
else
tc_stat(TC_STAT_END | TC_STAT_FAILED);
return 0;
}
4. 互斥量
互斥量控制块是操作系统用于管理互斥量的一个数据结构。
4.1 互斥量控制块
struct rt_mutex
{
struct rt_ipc_object parent; /* IPC对象继承而来 */
rt_uint16_t value; /* 只有LOCK和UNLOCK两种值 */
rt_uint8_t original_priority; /* 上一次获得该锁的线程的优先级 */
rt_uint8_t hold; /* 该线程获取了多少次该互斥锁 */
struct rt_thread *owner; /* 当前拥有该锁的线程句柄 */
}
静态互斥量:struct rt_mutex static_mutex;
动态互斥量:rt_mutex_t dynamic_mutex;
4.2 互斥量的操作
- 初始化与脱离
静态互斥量
rt_err_t rt_mutex_init(rt_mutex_t mutex, const char *name, rt_uint8_t flag); //RT_IPC_FIFO RT_IPC_FLAG_PRIO
rt_err_t rt_mutex_detach(rt_mutex_t mutex);
- 创建与删除
动态互斥量
rt_mutex rt_mutex_create(const char *name, rt_uint8_t flag);
rt_err_t rt_mutex_delete(rt_mutex_t mutex);
- 获取互斥量
只能在线程中调用,且同一个线程能够take多次同一个互斥量,其成员hold+1
rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time) // RT_WAITING_FOREVER = -1
- 释放互斥量
只能在线程中调用,不能在中断中调用。必须同一个线程获取的同一个互斥量,才能在该线程释放该互斥量
rt_err_t rt_mutex_release(rt_mutex_t mutex)
4.3 互斥量和信号量的差别
- 信号量可以由任何线程(以及中断)释放,它用于同步的时候就像交通灯,线程只有在获得许可的时候,才能运行,强调的是运行步骤;互斥量只能由持有它的线程释放,即只有锁上它的哪个线程,才有钥匙打开它,强调的是许可和权限
- 使用信号量可能导致优先级反转,互斥量可通过优先级集成的方法解决优先级反转问题
5. 线程优先级翻转
当一个高优先级线程试图通过某种互斥IPC对象机制访问共享资源时,如果该IPC对象已经被一个低优先级的线程所持有,而且这个低优先级线程运行过程中可能又被其他一些中等优先级的线程抢占,因此造成高优先级线程被许多具有较低优先级的线程阻塞的情况。导致高优先级的实时性得不到保证
5.1 优先级继承
在RT-Thread中,通过互斥量的优先级继承算法,可有有效解决优先级翻转问题。优先级继承是指提高某个占有某种共享资源的低优先级线程优先级,使之与所有等待该资源的线程中优先级最高的那个线程的优先级相等,从而得到更快地执行然后释放共享资源,当这个低优先级线程释放该资源时,优先级重新回到初始设定值。继承优先级的线程,避免了系统共享资源被任何中间优先级的线程抢占
优先级翻转线向提醒编程人员对共享资源进行互斥访问的代码段应尽量短。让低优先级线程尽快完成工作,释放共享资源
参考文献
本文作者: CrazyCatJack
本文链接: https://www.cnblogs.com/CrazyCatJack/p/14408842.html
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
关注博主:如果您觉得该文章对您有帮助,可以点击文章右下角推荐一下,您的支持将成为我最大的动力!
RT-Thread学习笔记2-互斥量与信号量的更多相关文章
- 1.linux系统基础笔记(互斥量、信号量)
操作系统是很多人每天必须打交道的东西,因为在你打开电脑的一刹那,随着bios自检结束,你的windows系统已经开始运行了.如果问大家操作系统是什么?可能有的人会说操作系统就是windows,就是那些 ...
- 【C#编程基础学习笔记】6---变量的命名
2013/7/24 技术qq交流群:JavaDream:251572072 教程下载,在线交流:创梦IT社区:www.credream.com [C#编程基础学习笔记]6---变量的命名 ----- ...
- linux 线程的同步 一 (互斥量和信号量)
互斥量(Mutex) 互斥量表现互斥现象的数据结构,也被当作二元信号灯.一个互斥基本上是一个多任务敏感的二元信号,它能用作同步多任务的行为,它常用作保护从中断来的临界段代码并且在共享同步使用的资源. ...
- C#线程学习笔记六:线程同步--信号量和互斥体
本笔记摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/23/Mutex_And_Semaphore.html,记录一下学习过程以备后续查用. ...
- 【转】【Linux】 临界区,互斥量,信号量,事件的区别
原文地址:http://blog.itpub.net/10697500/viewspace-612045/ Linux中 四种进程或线程同步互斥的控制方法1.临界区:通过对多线程的串行化来访问公共资源 ...
- CPP-基础:windows api 多线程---互斥量、信号量、临界值、事件区别
http://blog.csdn.net/wangsifu2009/article/details/6728155 四种进程或线程同步互斥的控制方法:1.临界区:通过对多线程的串行化来访问公共资源或一 ...
- thread学习笔记--BackgroundWorker 类
背景: 在 WinForms 中,有时要执行耗时的操作,比如统计某个磁盘分区的文件夹或者文件数目,如果分区很大或者文件过多的话,处理不好就会造成“假死”的情况,或者报“线程间操作无效”的异常,或者在该 ...
- Boost Thread学习笔记三
下面先对condition_impl进行简要分析.condition_impl在其构造函数中会创建两个Semaphore(信号量):m_gate.m_queue,及一个Mutex(互斥体,跟boost ...
- Boost Thread学习笔记二
除了thread,boost种:boost::mutexboost::try_mutexboost::timed_mutexboost::recursive_mutexboost::recursive ...
随机推荐
- Bitter.Core系列三:Bitter ORM NETCORE ORM 全网最粗暴简单易用高性能的 NETCore ORM 之 示例模型创建
在具体数据库操作之前,我们先准备好四张表以及相对应数据库操作模型: 学生表,年级表,班级表,学分表.示例数据库表,如下代码(MSSQL 为例) --学生表 CREATE TABLE t_student ...
- The Garbage Collection Handbook
The Garbage Collection Handbook The Garbage Collection Handbook http://gchandbook.org/editions.html ...
- 字节跳动在 Go 网络库上的实践
https://mp.weixin.qq.com/s/wSaJYg-HqnYY4SdLA2Zzaw RPC 框架作为研发体系中重要的一环,承载了几乎所有的服务流量.本文将简单介绍字节跳动自研网络库 n ...
- 两个报文是如何进行 TCP 分组传输
16 | 如何理解TCP的"流"? https://time.geekbang.org/column/article/132443 TCP 是一种流式协议在前面的章节中,我们讲的都 ...
- 使用Linux服务器来通过网络安装和激活Windows 7 —— 一些基本原理
使用Linux服务器来通过网络安装和激活Windows 7 -- 一些基本原理 https://www.pufengdu.org/blog/?p=372
- 分布式跟踪的一个流行标准是OpenTracing API,该标准的一个流行实现是Jaeger项目。
https://github.com/jaegertracing/jaeger https://mp.weixin.qq.com/s/-Tn2AgyHoq8pwMun8JHcGQ Jaeger的深入分 ...
- LOJ10067
LOJ10067 构造完全图 给你一棵树 T,找出 T 能扩展出的边权和最小的完全图 G. 第一行 N 表示树 T 的点数: 保证输入数据构成一棵树. 输出仅一个数,表示最小的完全图 G 的边权和. ...
- URL重定向漏洞解析
参考文章 悟空云课堂 | 第二期:URL重定向(跳转)漏洞 CWE-601: URL Redirection to Untrusted Site ('Open Redirect') 分享几个绕过URL ...
- ssh登陆时,参数直接加入密码
参考: [随笔]ssh登录时如何直接在参数中加入登录密码 安装 sshpass
- 33.vsftpd服务程序--本地用户模式
1.针对本地用户模式的权限参数以及作用如下 [root@localhost ~]# vim /etc/vsftpd/vsftpd.conf 1 anonymous_enable=NO 2 local_ ...