Fork me on GitHub

RT-Thread学习笔记3-线程间通信 & 定时器


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字节长度的邮件发送到邮箱中,而其他需要的线程可以从邮箱中接受这些邮件并进行处理
BOC0xK.png

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

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的内存池支持线程挂起,当内存池无空闲内存块时,申请线程会被挂起,直到内存池中有新的可用内存块,再将挂起的线程唤醒。基于这个特点内存池非常适合需要通过内存资源进行同步的场景。
BjCAbD.png

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 许可协议。转载请注明出处!

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


posted @ 2021-02-18 14:34  CrazyCatJack  阅读(732)  评论(0编辑  收藏  举报