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. 详解Go中内存分配

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com 本文使用的go的源码15.7 介绍 Go 语言的内存分配器就借鉴了 TCMalloc 的 ...

  2. 统一资源定位符 (URL)

    统一资源标识符(uniform/universal resource identifier,URI)用于表示Internet中的资源(通常是文档).URI 主要用于两种目的,其一是命名资源,尽管此时把 ...

  3. Java面试(解答题一)

    1.简述下列问题 List,set,map的区别 解答:List,Set都是继承自Collection接口,Map则不是: List特点:元素有放入顺序,元素可重复 ,Set特点:元素无放入顺序,元素 ...

  4. 一篇文章图文并茂地带你轻松学完 JavaScript 继承

    JavaScript 继承 在阅读本文章之前,已经默认你了解了基础的 JavaScript 语法知识,基础的 ES6 语法知识 . 继承种类 简单的继承种类可以分为 构造函数继承 原型链继承 clas ...

  5. [HDU4734] F(x)(数位dp+优化)

    >传送门<题意:对于一个有n位(这n位从高位到低位分别是An,An-1,An-2 ... A2,A1)的十进制数,我们定义它的权值F(x)=An*2n-1 + An-1*2n-2 + .. ...

  6. 2019牛客暑期多校训练营(第五场)H.subsequence 2(拓扑)

    题意:给你一个字符串的长度n 现在询问了m*(m-1)/2次 每次都可以询问两个字符 然后 会告诉你只留下这两个字符后 字符串的样子 现在问你能不能还原字符串 如果能就输出字符串 否则输出-1 思路: ...

  7. 2018 ccpc吉林 The Tower

    传送门:HDU - 6559 题意 在一个三维空间,给定一个点和他的三维速度,给定一个圆锥,问这个点最早什么时候能撞上圆锥. 题解 本来一直想着怎么求圆锥的方程,然后....队友:这不是二分吗!然后问 ...

  8. Codeforces ECR 83 C. Adding Powers (位运算)

    题意:给你n个数和一个底数k,每个数每次能减去k^i(i=0,1,2,....),每个k^i只能用一次,问是否能够将每个数变为0. 题解:我们将每个数转化为k进制,因为每个k^i只能用一次,所以我们统 ...

  9. 一、Jmeter进行Mysql数据库的压测

    1.首先需要安装配置mysql数据库连接驱动:mysql-connector-java-5.1.28.jar 1.1 网上很多资源,可自行下载: 1.2 下载完成后,分别将该jra包,存放到:jmet ...

  10. 文件的读写(cpp)

    文件的读写(cpp) c++中要进行文件的读入,首先要包含一个头文件 fstream . 输出到文件 为打开一个可供输出的文件需要定义一个ofstream 对象并将文件名传入: std::ofstre ...