1. 事件集的使用

单个指定事件唤醒线程,任意事件唤醒线程,多个指定事件一起唤醒线程。信号量主要用于“一对一”的线程同步,当需要“一对多”、“多对一”、“多对多”的同步时,就需要事件集来处理了。RT-Thread中的事件集用一个32位无符号整型变量来表示,变量中的一个位代表一个事件,线程通过“逻辑与”或“逻辑或”与一个或多个事件建立关联形成一个事件组合。

  • 事件的“逻辑或”也称为是独立型同步,指的是线程与任何事件之一发生同步,只要有一个事件发生,即满足条件
  • 事件的“逻辑与”,也称为是关联型同步,指的是线程与若干事件都发生同步,只有这些事件全部发生,才满足条件

1.1 事件集控制块

struct rt_event
{
struct rt_ipc_object parent; // 从ipc_object继承而来
rt_uint32_t set; // 事件集 set
} typedef struct rt_event *rt_event_t; 静态事件集:struct rt_event static_evt;
动态事件集:rt_event_t dynamic_evt;

1.2 事件集操作

  1. 初始化与脱离
静态事件集操作
rt_err_t rt_event_init(rt_event_t event, const char *name, rt_uint8_t falg) //上同
rt_err_t rt_event_detach(rt_event_t event)
  1. 创建与删除
动态事件集操作
rt_event_t rt_event_create(const char *name, rt_uint8_t flag)
rt_err_t rt_event_delete(rt_event event)
  1. 发送事件
// set的值为0x01则代表第0个事件发生了,0x08则代表第3个事件发生了
// 1 << 0, 1 << 3
rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set)
  1. 接受事件
rt_err_t rt_event_recv(rt_event_t event, rt_uint32_t_t set, rt_uint8_t option, rt_int32_t timeout, rt_uint32_t *recved)
// set的值表示对哪个事件感兴趣。例如0x01 | 0x08,则表示对第0个事件和第3个事件感兴趣
// option:
RT_EVENT_FLAG_AND:都发生才唤醒
RT_EVENT_FLAG_OR:有一个发生就唤醒
RT_EVENT_FLAG_CLEAR:线程唤醒后,系统会将事件的set对应的位清零。否则系统不会清除对应位
// timeout: 上同
// recved: 保存接收到的事件set

2. 邮箱的使用

邮箱用于线程间通信,特点是开销比较低,效率较高。邮箱中的每一封邮件只能容纳固定的4字节内容(针对32位处理系统,指针的大小即为4个字节,所以一封邮件恰好能够容纳一个指针)。线程或中断服务例程把一封4字节长度的邮件发送到邮箱中,而其他需要的线程可以从邮箱中接受这些邮件并进行处理

2.1 邮箱控制块

struct rt_mailbox
{
struct rt_ipc_object parent; //从IPC对象继承而来
rt_uint32_t *msg_pool; //指向邮箱消息的缓冲区的地址
rt_uint16_t size; //邮箱的容量,可以放多少个邮件
rt_uint16_t entry; // 邮箱中邮件的数目
rt_uint16_t in_offset; // 邮箱进偏移量
rt_uint16_t out_offset; // 邮箱出偏移量
rt_list_t suspend_sender_thread; // 记录了挂起在该邮箱的线程。比如邮箱满了,这些线程就不能发了,要挂起等待
}
typedef struct rt_mailbox *rt_mailbox_t;
静态邮箱:struct rt_mailbox static_mb;
动态邮箱:rt_mailbox_t dynamic_mb;

2.2 邮箱的操作

  1. 初始化与脱离
// 静态邮箱。flag: RT_IPC_FLAG_FIFO, RT_IPC_FLAG_PRIO
// 如果size=10,则 msgpool就要有4 * 10 = 40byte的空间
rt_err_t rt_mb_init(rt_mailbox_t mb, const char *name, void *msgpool, rt_size_t size, rt_uint8_t flag)
rt_err_t rt_mb_detach(rt_mailbox_t mb)
  1. 创建与删除
// 动态邮箱
rt_mailbox_t rt_mb_create(const char *name, rt_size_t size, rt_uint8_t flag)
rt_err_t rt_mbdelete(rt_mailbox_t mb)
  1. 发送邮件
// value就是邮件的内容,4字节。如果发送的内容<4字节,则直接赋值给value即可,如果很多,那么可以传送地址。
// 如果邮箱已经满了,那么会直接返回错误
// 可以在线程和中断中调用
rt_err_t rt_mb_send(rt_mailbox_t mb, rt_uint32_t value);
// 如果邮箱满了,则最多等待timeout时间。
// 只能在线程中调用,因为它会造成阻塞
rt_err_t rt_mb_send_wait(rt_mailbox_t mb, rt_uint32_t value, rt_int32_t timeout)
  1. 接收邮件
rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_uint32_t *value, rt_int32_t timeout)

3. 消息队列

消息队列是RT-Thread另一种常用的线程间通信方式,消息队列是对邮箱的扩展。消息队列能够接收来自线程或中断服务例程中发出的不固定长度的消息,并把消息缓存在自己的内存空间中,而其它线程能够从消息队列中读取相应的消息,并进行相应的处理。支持紧急消息发送,即将紧急消息链接到消息列表链表头。当消息队列满,还往消息队列发送消息,该发送就会失败。当消息队列空,还从消息队列接收消息,该接收就会失败。

3.1 消息队列控制块

struct rt_messagequeue
{
struct rt_ipc_object parent; // 继承自IPC对象
void *msg_pool; // 指向消息队列空间的地址
rt_uint16_t msg_size; // 消息最大长度.在rtconfig.h 中定义对其字节,一般是4字节对齐。因此最小为4,应设置为4的倍数
rt_uint16_t max_msgs; // 消息队列容量,能容纳最多消息个数。例如设置msg_pool的大小为1024byte,那么max_msgs = 1024 / (msg_size + 4指针地址) = 1024/8
rt_uint16_t entry; // 消息队列中消息个数
void *msg_queue_head; // 消息队列头指针
void *msg_queue_tail; // 消息队列尾指针
void *msg_queue_free; // 消息队列未被使用的消息框
} typedef struct rt_messagequeue *rt_mq_t;
静态消息队列:struct rt_messagequeue static_mq
动态消息队列:rt_mq_t dynamic_mq

3.2 消息队列的操作

  1. 初始化与脱离
rt_err_t rt_mq_init(rt_mq_t mq, const char *name, void *msgpool, rt_size_t msg_size, rt_size_t pool_size, rt_uint8_t flag) // RT_IPC_FLAG_FIFO, RT_IPC_FLAG_PRIO
rt_err_t rt_mq_detach(rt_mq_t mq)
  1. 创建于删除
rt_mq_t rt_mq_create(const char *name, rt_size_t msg_size, rt_size_t max_msgs, rt_uint8_t flag)
rt_err_t rt_mq_delete(rt_mq_t mq)
  1. 发送消息
// size <= msg_size.将消息放在尾部
rt_err_t rt_mq_send(rt_mq_t mq, void *buffer, rt_size_t size)
// 紧急消息,消息放在列表头部
rt_err_t rt_mq_urgent(rt_mq_t mq, void *buffer, rt_size_t size)
  1. 接收消息
// timeout不等于0,则收不到消息就会挂起
rt_err_t rt_mq_recv(rt_mq_t mq, void *buffer, rt_size_t size, rt_int32_t timeout)

4. 软件定时器

软件定时器是由操作系统提供的一类系统接口,构建在硬件定时器基础之上(系统滴答定时器),软件定时器使系统能够提供不受数目限制的定时器服务。RT-Thread提供的软件定时器,以系统节拍(OS Tick)的时间长度为定时单位,提供了基于系统节拍整数倍的定时能力,即定时器数值是OS Tick的整数倍。例如一个OS Tick是10ms,那么上层软件定时器只能提供10的倍数的定时。到时之后,会调用用户设置的回调函数

4.1 定时器模式

4.1.1 HARDTIMER模式

超时函数在中断上下文环境中执行。此模式在定时器初始化时指定。对于超时函数的要求和中断服务例程的要求相同。执行时间应该尽量短,执行时不应导致当前上下文挂起,HARD_TIMER模式是RT-Thread软件定时器的默认方式

4.1.2 SOFTTIMER模式

超时函数在系统timer线程的线程上下文中执行。通过宏定义RT_USING_TIMER_SOFT来决定是否启用该模式。当启动SOFTTIMER模式后,可以在定时器初始化时指定定时器工作在SOFTTIMER模式

4.2 软件定时器控制块

struct rt_timer
{
struct rt_object parent; // 从系统对象继承而来
rt_list_t row[RT_TIMER_SKIP_LIST_LEVEL]; // 系统定时器链表
void (*timeout_func)(void *parameter); // 超时回调函数
void *parameter; // 回调函数输入参数
rt_tick_t init_tick; // 指定超时的时钟节拍。例如60 * 10 = 600ms
rt_tick_t timeout_tick; // 系统在超时时的系统节拍
};
typedef struct rt_timer *rt_timer_t;
静态软件定时器:struct rt_timer static_timer
动态软件定时器:rt_timer_t dynamic_timer

4.3 软件定时器的操作

  1. 初始化与脱离
void rt_timer_init(rt_timer_t timer, const char *name, void(*timeout)(void *parameter), void *parameter, rt_tick_t time, rt_uint8_t flag);
// RT_TIMER_FLAG_ONE_SHOT, RT_TIMER_FLAG_PERIODIC, RT_TIMER_FLAG_HARD_TIMER, RT_TIMER_FLAG_SOFT_TIMER
第1和第2个选一个 | 第3和第4个选一个
// 没有回调参数就传RT_NULL
rt_err_t rt_timer_detach(rt_timer_t timer)
  1. 创建与删除
rt_timer_t rt_timer_create(const char *name, void(*timeout)(void *parameter), void *parameter, rt_tick_t time, rt_uint8_t flag)
rt_err_t rt_timer_delete(rt_timer_t timer)
  1. 启动定时器
rt_err_t rt_timer_start(rt_timer_t timer)
  1. 停止定时器
rt_err_t rt_timer_stop(rt_timer_t timer)

5. 内存池

动态内存堆可以分配任意大小的内存块,非常灵活和方便,但其存在明显的缺点:一是分配效率不高,在每次分配时,都要进行空闲内存块查找;二是容易产生内存碎片。为了提高内存分配效率,并且避免内存碎片,RT-Thread提供了另一种内存管理方法:内存池。内存池是一种内存分配方式,用于分配大量大小相同的小内存块,使用内存池可以极大的加快内存分配与释放的速度,且能尽量避免内存碎片化。RT-Thread的内存池支持线程挂起,当内存池无空闲内存块时,申请线程会被挂起,直到内存池中有新的可用内存块,再将挂起的线程唤醒。基于这个特点内存池非常适合需要通过内存资源进行同步的场景。

5.1 内存池控制块

struct rt_mempool
{
struct rt_object parent; // 从系统对象继承
void *start_address; // 内存池起始地址
rt_size_t size; // 内存池大小
rt_size_t block_size; // 内存池中一个内存块大小
rt_uint8_t *block_list; // 内存池中小内存块链表
rt_size_t block_total_count; // 记录内存池中能容纳多少小内存块
rt_size_t block_free_count; // 内存池中空闲内存块个数
rt_list_t suspend_thread; // 挂载在该资源上的线程
rt_size_t suspend_thread_count; // 挂载在该资源上的线程个数
};
typedef struct rt_mempool *rt_mp_t;
静态内存池:struct rt_mempool static_mp;
动态内存池:rt_mp_t dynamic_mp;

5.2 内存池操作

  1. 初始化与脱离
静态内存池
// block_size仍然要遵循字节对齐。例如4字节对齐,就必须设置成4的整数倍。有了block_size和size就可以计算内存块数量=size/(block_size + 4)。其中4为指针大小
rt_err_t rt_mp_init(struct rt_mempool *mp, const char *name, void *start, rt_size_t size, rt_size_t block_size)
rt_err_t rt_mp_detach(struct rt_mempool *mp)
  1. 创建与删除
rt_mp_t rt_mp_create(const char *name, rt_size_t block_count, rt_size_t block_size)
rt_err_t rt_mp_delete(rt_mp_t mp)
  1. 申请内存块
// time参数为0,则立刻返回结果,如果time大于0,则无内存块会挂起线程,如果time小于0,则无内存块会一直挂起线程,直到有内存块
void *rt_mp_alloc(rt_mp_t mp, rt_int32_t time)
  1. 释放内存块
// 输入内存块的地址
void rt_mp_free(void *block)

参考文献

  1. RT-Thread视频中心内核入门
  2. RT-Thread文档中心

本文作者: CrazyCatJack

本文链接: https://www.cnblogs.com/CrazyCatJack/p/14408849.html

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

关注博主:如果您觉得该文章对您有帮助,可以点击文章右下角推荐一下,您的支持将成为我最大的动力!


RT-Thread学习笔记3-线程间通信 & 定时器的更多相关文章

  1. 0038 Java学习笔记-多线程-传统线程间通信、Condition、阻塞队列、《疯狂Java讲义 第三版》进程间通信示例代码存在的一个问题

    调用同步锁的wait().notify().notifyAll()进行线程通信 看这个经典的存取款问题,要求两个线程存款,两个线程取款,账户里有余额的时候只能取款,没余额的时候只能存款,存取款金额相同 ...

  2. Java多线程学习(五)线程间通信知识点补充

    系列文章传送门: Java多线程学习(二)synchronized关键字(1) Java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Java多 ...

  3. Java笔记(二十)……线程间通信

    概述 当需要多线程配合完成一项任务时,往往需要用到线程间通信,以确保任务的稳步快速运行 相关语句 wait():挂起线程,释放锁,相当于自动放弃了执行权限 notify():唤醒wait等待队列里的第 ...

  4. Java多线程编程核心技术-第3章-线程间通信-读书笔记

    第 3 章 线程间通信 线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体.线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大 ...

  5. Java学习:线程间通信

    线程间通信 概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同重点:有效的利用资源 分析:需要那些类 1 资源类:包子类 设置包子的属性 包子的状态:有true 没有false 2 ...

  6. Windows环境下多线程编程原理与应用读书笔记(4)————线程间通信概述

    <一>线程间通信方法 全局变量方式:进程中的线程共享全局变量,可以通过全局变量进行线程间通信. 参数传递法:主线程创建子线程并让子线程为其服务,因此主线程和其他线程可以通过参数传递进行通信 ...

  7. 《JAVA多线程编程核心技术》 笔记:第三章:线程间通信

    一. 等待/通知机制:wait()和notify()1.1.使用的原因:1.2 具体实现:wait()和notify()1.2.1 方法wait():1.2.2 方法notify():1.2.3 wa ...

  8. zeromq源码分析笔记之线程间收发命令(2)

    在zeromq源码分析笔记之架构说到了zmq的整体架构,可以看到线程间通信包括两类,一类是用于收发命令,告知对象该调用什么方法去做什么事情,命令的结构由command_t结构体确定:另一类是socke ...

  9. 操作系统学习笔记----进程/线程模型----Coursera课程笔记

    操作系统学习笔记----进程/线程模型----Coursera课程笔记 进程/线程模型 0. 概述 0.1 进程模型 多道程序设计 进程的概念.进程控制块 进程状态及转换.进程队列 进程控制----进 ...

随机推荐

  1. 函数式编程(__slots__)

    正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性.先定义class: class Student(object): pa ...

  2. 解决java.lang.NoClassDefFoundError: ch/qos/logback/core/joran/spi/Pattern

    明明引入了这个,却提示没有 看下面文章: http://www.maocaoying.com/article/109

  3. trunk

    今天我们一起聊trunk(接vlan之后),一台switch我们用vlan就可以划分vlan(虚拟局域网),但是2台switch该怎么办呢? 实验环境搭建 switch0 : enable //切换到 ...

  4. VXLAN理论解析

    转自:https://www.jianshu.com/p/cccfb481d548 产生背景:云计算成为企业IT建设新形态 云计算,凭借其在系统利用率高.人力/管理成本低.灵活性.可扩展性强等方面表现 ...

  5. 前端html基础学习笔记二

    表单 1 : 表单标签 <form></form> 属性 : action = '接口地址' method = 'get / post' name = '表单名称' 2 : 表 ...

  6. 2019牛客暑期多校训练营(第七场)F-Energy stones(思维+树状数组)

    >传送门< 题意:有n块能量石,每秒钟会增加Li的能量,但是一旦增长到了Ci它就不会增长了,它初始的能量为Ei. 现在有若干个时刻ti,会选择下标在[Si,Ti]的能量石吸取它们的能量,这 ...

  7. c++格式化输入输出以及操纵器的使用

    C++格式化输入和输出 1,ios类中定义的格式控制标志 ios类中定义了一个数据成员:格式控制标志字,long x_flags x_flags每一位的状态值用枚举符号常量定义:如下列出常用几个 en ...

  8. map详细的复习

    map 就是一种基于自建红黑树的 一一对应的hash 的容器 通过模板方式实现  map<type,type> mapname: 前边是key 后边是 vale 转载如下作者:sevenc ...

  9. c语言实现--双向循环链表操作

    1,双向链表相当于两个单向循环链表. 2,双向链表的结点定义. 1 struct DULNode 2 { 3 int data; 4 struct DULNode * prior; 5 struct ...

  10. Codeforces Round #690 (Div. 3) E2. Close Tuples (hard version) (数学,组合数)

    题意:给你一长度为\(n\)的序列(可能含有相等元素),你要找到\(m\)个位置不同的元素使得\(max(a_{i-1},a_{i_2},...,a_{i_m})-min(a_{i-1},a_{i_2 ...