Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
2024-RSOC
【2024-RSOC】DAY3 RTT IPC学习
发布于 2024-07-24 22:51:35 浏览:574
订阅该版
[tocm] # 一、信号量 ## 用于线程之间的同步 ### 概念 信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的。 ### 举例(官网) - 当停车场空的时候,停车场的管理员发现有很多空车位,此时会让外面的车陆续进入停车场获得停车位; - 当停车场的车位满的时候,管理员发现已经没有空车位,将禁止外面的车进入停车场,车辆在外排队等候; - 当停车场内有车离开时,管理员发现有空的车位让出,允许外面的车进入停车场;待空车位填满后,又禁止外部车辆进入。 在此例子中,管理员就相当于信号量,管理员手中空车位的个数就是信号量的值(非负数,动态变化);停车位相当于公共资源(临界区),车辆相当于线程。车辆通过获得管理员的允许取得停车位,就类似于线程通过获得信号量访问公共资源。 ### 存在两种状态 - 等待 - 搁置(如暂时放弃这项工作,先去做其他工作,等叫号到自己再回来) ### 信号量的管理方式 [![pkbp9fg.png](https://s21.ax1x.com/2024/07/24/pkbp9fg.png)](https://imgse.com/i/pkbp9fg) - 多线程等待排队方式: - `RT_IPC_FLAG_FIFO`(先进先出) - `RT_IPC_FLAG_PRIO`(优先级等待) # 二、互斥量 ## 互斥量又叫相互排斥的信号量,是一种特殊的二值信号量。 ### 举例(官网) 互斥量类似于只有一个车位的停车场:当有一辆车进入的时候,将停车场大门锁住,其他车辆在外面等候。当里面的车出来时,将停车场大门打开,下一辆车才可以进入。 ### 与信号量区别 1. **所有权**:拥有互斥量的线程拥有互斥量的所有权,互斥量支持递归访问且能防止线程优先级翻转。 - **线程优先级翻转解释**:当一个高优先级线程试图通过信号量机制访问共享资源时,如果该信号量已被一低优先级线程持有,而这个低优先级线程在运行过程中可能又被其他一些中等优先级的线程抢占,因此造成高优先级线程被许多具有较低优先级的线程阻塞,实时性难以得到保证。 如下图: [![pkbpPpQ.md.png](https://s21.ax1x.com/2024/07/24/pkbpPpQ.md.png)](https://imgse.com/i/pkbpPpQ) 2. **释放方式**:互斥量只能由持有线程释放,而信号量则可以由任何线程释放。 ### 优先级继承协议 互斥量可以解决优先级翻转问题,实现的是优先级继承协议 (Sha, 1990)。 - **具体操作**:将线程 C 的优先级提升到线程 A 的优先级别,防止 C(间接地防止 A)被 B 抢占。 如下图所示: [![pkbpPpQ.png](https://s21.ax1x.com/2024/07/24/pkbpPpQ.png)](https://imgse.com/i/pkbpPpQ) ### 互斥量的管理方式 对一个互斥量的操作包含: - 创建 / 初始化互斥量 - 获取互斥量 - 释放互斥量 - 删除 / 脱离互斥量 互斥量相关接口如下图所示: [![pkbpptS.png](https://s21.ax1x.com/2024/07/24/pkbpptS.png)](https://imgse.com/i/pkbpptS) ### 注意 1. 无论用户选择 `RT_IPC_FLAG_PRIO` 还是 `RT_IPC_FLAG_FIFO`,内核均按照 `RT_IPC_FLAG_PRIO` 处理。 2. 当不再使用互斥量时,通过删除互斥量以释放系统资源,适用于动态创建的互斥量。 # 三、事件集 事件集是线程间同步的一种机制,它允许一个事件集包含多个事件,从而实现一对多、多对多的线程间同步。 ## 举例 ### 1. 单事件同步 - **P1 坐公交去某地**,只有一种公交可以到达目的地,等到此公交即可出发。 ### 2. 任意事件同步 - **P1 坐公交去某地**,有 3 种公交都可以到达目的地,等到其中任意一辆即可出发。 ### 3. 多事件同步 - **P1 约 P2 一起去某地**,P1 必须等到“同伴 P2 到达公交站”与“公交到达公交站”两个条件都满足后才能出发。 在这里,可以将 P1 去某地视为线程,将“公交到达公交站”、“同伴 P2 到达公交站”视为事件的发生。情况①是特定事件唤醒线程;情况②是任意单个事件唤醒线程;情况③是多个事件同时发生才唤醒线程。 ## 事件集工作机制 ### 1. 一对多同步 - 其中任意一个事件唤醒线程,或几个事件都到达后才唤醒线程进行后续的处理。 ### 2. 多对多同步 - 多个事件的集合可以用一个 32 位无符号整型变量来表示,变量的每一位代表一个事件。线程通过“逻辑与”或“逻辑或”将一个或多个事件关联起来,形成事件组合。 - 事件的“逻辑或”也称为独立型同步,指的是线程与任何事件之一发生同步。 - 事件的“逻辑与”也称为关联型同步,指的是线程与若干事件都发生同步。 ## 事件集特点 1. **事件只与线程相关,事件间相互独立**:每个线程可拥有 32 个事件标志,采用一个 32 bit 无符号整型数进行记录,每一个 bit 代表一个事件。 2. **事件仅用于同步,不提供数据传输功能**。 3. **事件无排队性**,即多次向线程发送同一事件(如果线程还未来得及读走),其效果等同于只发送一次。 ## 线程事件信息标记 每个线程事件信息标记有三个主要属性: - `RT_EVENT_FLAG_AND`(逻辑与):线程只有在指定所有事件都发生时才会被唤醒。 - `RT_EVENT_FLAG_OR`(逻辑或):线程在指定事件中的任意一个发生时就会被唤醒。 - `RT_EVENT_FLAG_CLEAR`(清除标记):当线程被唤醒后,自动清除触发线程的事件标志。 ## 实例 [![pkbSxTf.png](https://s21.ax1x.com/2024/07/24/pkbSxTf.png)](https://imgse.com/i/pkbSxTf) ### RT_EVENT_FLAG_AND(逻辑与) - 线程 #1 只有在事件 1 和事件 30 都发生以后才会被触发唤醒。 ### RT_EVENT_FLAG_OR(逻辑或) - 事件 1 或事件 30 中的任意一个发生都会触发唤醒线程 #1。 - 若同时设置了清除标记位,则线程 #1 唤醒后将把事件 1 和事件 30 清为零;否则,事件标志将依然存在(即置 1)。 ## 事件集操作 - **创建 / 初始化事件集** - **发送事件** - **接收事件** - **删除 / 脱离事件集** [![pkbpSk8.png](https://s21.ax1x.com/2024/07/24/pkbpSk8.png)](https://imgse.com/i/pkbpSk8) # 四、邮箱 实时操作系统中一种典型的线程间通信方法。 ## 举例(官网) 有两个线程,线程 1 检测按键状态并发送,线程 2 读取按键状态并根据按键的状态相应地改变 LED 的亮灭。这里就可以使用邮箱的方式进行通信,线程 1 将按键的状态作为邮件发送到邮箱,线程 2 在邮箱中读取邮件获得按键状态并对 LED 执行亮灭操作。 ## PS(理解) 你让你好哥们送信给你暗恋女神,成不成取决于你好哥们。 ## 邮箱工作机制 1. **特点**:开销比较低,效率较高。 2. **邮件内容**:邮箱中的每一封邮件只能容纳固定的 4 字节内容(针对 32 位处理系统,指针的大小即为 4 个字节,所以一封邮件恰好能够容纳一个指针)。 PS(理解):信里只写了一个“LOVE”,女神见你好哥们音容相貌尚可,委身于他,悲~ ## 阻塞与非阻塞 1. **非阻塞发送**:非阻塞方式的邮件发送过程能够安全的应用于中断服务中,是线程、中断服务、定时器向线程发送消息的有效手段。 2. **阻塞接收**:通常来说,邮件收取过程可能是阻塞的,这取决于邮箱中是否有邮件,以及收取邮件时设置的超时时间。当邮箱中不存在邮件且超时时间不为 0 时,邮件收取过程将变成阻塞方式。在这类情况下,只能由线程进行邮件的收取。 当一个线程向邮箱发送邮件时,如果邮箱没满,将把邮件复制到邮箱中。如果邮箱已经满了,发送线程可以设置超时时间,选择等待挂起或直接返回 - RT_EFULL。如果发送线程选择挂起等待,那么当邮箱中的邮件被收取而空出空间来时,等待挂起的发送线程将被唤醒继续发送。 PS(理解):这就是送完信的你,要么等候此地,小鹿乱撞、急不可耐;要么转身回头,心中想着下一次的为爱冲锋~ 当一个线程从邮箱中接收邮件时,如果邮箱是空的,接收线程可以选择是否等待挂起直到收到新的邮件而唤醒,或可以设置超时时间。当达到设置的超时时间,邮箱依然未收到邮件时,这个选择超时等待的线程将被唤醒并返回 - RT_ETIMEOUT。如果邮箱中存在邮件,那么接收线程将复制邮箱中的 4 个字节邮件到接收缓存中。 ## 邮箱操作 - 创建 / 初始化邮箱 - 发送邮件 - 接收邮件 - 删除 / 脱离邮箱 邮箱的相关接口如下图所示: [![pkbpF6s.png](https://s21.ax1x.com/2024/07/24/pkbpF6s.png)](https://imgse.com/i/pkbpF6s) # 五、消息队列 消息队列是另一种常用的线程间通讯方式,是邮箱的扩展。可以应用在多种场合:线程间的消息交换、使用串口接收不定长数据等。 ## 消息队列的工作机制 1. **接收不固定长度消息**:消息队列能够接收来自线程或中断服务例程中不固定长度的消息,并把消息缓存在自己的内存空间中。 2. **先进先出原则**:其他线程也能够从消息队列中读取相应的消息,而当消息队列是空的时候,可以挂起读取线程。当有新的消息到达时,挂起的线程将被唤醒以接收并处理消息。线程先得到的是最先进入消息队列的消息,即先进先出原则。 PS(理解):送完情书,晚上撅着臀儿趴在床上死死盯住女神微信的你~ 3. **异步通信**:消息队列是一种异步的通信方式。 ## 消息队列的创建 创建消息队列时先从对象管理器中分配一个消息队列对象,然后给消息队列对象分配一块内存空间,组织成空闲消息链表,这块内存的大小 = [消息大小 + 消息头(用于链表连接) 的大小] * 消息队列最大个数,接着再初始化消息队列;接口返回RT_EOK表示动态消息队列创建成功。 ## 消息队列的管理方式 - 创建消息队列 - 发送消息 - 接收消息 - 删除消息队列 消息队列的相关接口如下图所示: [![pkbpkXn.png](https://s21.ax1x.com/2024/07/24/pkbpkXn.png)](https://imgse.com/i/pkbpkXn) 注:线程接收消息队列的顺序可分为:RT_IPC_FLAG_FIFO(先进先出) 或 RT_IPC_FLAG_PRIO(优先级等待);而消息队列里的消息为:先进先出原则 (FIFO)。 # 六、实战 创建一个生产-加工模型 - 生产端(Producer)负责模拟生产数据的过程。 - 加工段线程(Consumer)负责模拟消费这些数据的过程。 它们之间通过信号量(Semaphore)和事件集(Event)进行同步。 ## 代码部分 ```c #include
#include
#define THREAD_PRIORITY 25 #define THREAD_STACK_SIZE 512 #define THREAD_TIMESLICE 5 // 定义事件集标志位 #define EVENT_FLAG_1 (1 << 3) #define EVENT_FLAG_2 (1 << 5) // 信号量用于同步 static rt_sem_t sem = RT_NULL; // 事件集用于表示不同的事件 static rt_event_t evt = RT_NULL; // 生产端线程 static void producer_thread_entry(void *parameter) { rt_uint32_t event; while (1) { // 假设生产了一些数据 rt_kprintf("产生一些数据\n"); // 发送一个事件给加工端 event = EVENT_FLAG_1 | EVENT_FLAG_2; rt_event_send(evt, event); // 释放信号量,允许加工端继续 rt_sem_release(sem); // 休眠一段时间 rt_thread_mdelay(1000); } } // 加工端线程 static void consumer_thread_entry(void *parameter) { rt_uint32_t recv_event; rt_err_t result; while (1) { // 等待信号量,表示生产端已经生产了数据 rt_sem_take(sem, RT_WAITING_FOREVER); // 等待事件 result = rt_event_recv(evt, EVENT_FLAG_1 | EVENT_FLAG_2, RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, &recv_event); if (result == RT_EOK) { if (recv_event & EVENT_FLAG_1) { rt_kprintf("已接收到数据\n"); } } } } int DAY3_sample(void *p) { // 初始化信号量 sem = rt_sem_create("prod_sem", 0, RT_IPC_FLAG_FIFO); if (sem == RT_NULL) { rt_kprintf("Failed to create semaphore\n"); return -RT_ERROR; } // 初始化事件集 evt = rt_event_create("prod_evt", RT_IPC_FLAG_FIFO); if (evt == RT_NULL) { rt_kprintf("Failed to create event\n"); rt_sem_delete(sem); return -RT_ERROR; } // 创建生产端线程 rt_thread_t producer_tid = rt_thread_create("producer", producer_thread_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); if (producer_tid != RT_NULL) rt_thread_startup(producer_tid); // 创建加工端线程 rt_thread_t consumer_tid = rt_thread_create("consumer", consumer_thread_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); if (consumer_tid != RT_NULL) rt_thread_startup(consumer_tid); return 0; } /* 导出到 msh 命令列表中 */ MSH_CMD_EXPORT(DAY3_sample,DAY3 sample); ```
0
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
留矽兮
这家伙很懒,什么也没写!
文章
4
回答
0
被采纳
0
关注TA
发私信
相关文章
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
ota在线升级
UART
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
keil_MDK
rt_mq_消息队列_msg_queue
at_device
ulog
C++_cpp
本月问答贡献
踩姑娘的小蘑菇
7
个答案
3
次被采纳
张世争
8
个答案
2
次被采纳
rv666
5
个答案
2
次被采纳
用户名由3_15位
11
个答案
1
次被采纳
KunYi
6
个答案
1
次被采纳
本月文章贡献
程序员阿伟
6
篇文章
2
次点赞
hhart
3
篇文章
4
次点赞
大龄码农
1
篇文章
2
次点赞
ThinkCode
1
篇文章
1
次点赞
Betrayer
1
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部