【BES2500x系列 -- RTX5操作系统】深入探索CMSIS-RTOS RTX -- 同步与通信篇 -- 消息队列和邮箱处理 --(四)

发布于:2024-07-02 ⋅ 阅读:(19) ⋅ 点赞:(0)

请添加图片描述

  • 💌 所属专栏:【BES2500x系列】

  • 😀 作  者:我是夜阑的狗🐶

  • 🚀 个人简介:一个正在努力学技术的CV工程师,专注基础和实战分享 ,欢迎咨询!

  • 💖 欢迎大家:这里是CSDN,我总结知识的地方,喜欢的话请三连,有问题请私信 😘 😘 😘

您的点赞、关注、收藏、评论,是对我最大的激励和支持!!!🤩 🤩 🤩

请添加图片描述


前言

  大家好,又见面了,我是夜阑的狗🐶,本文是专栏【BES2500x系列】专栏的第4篇文章;
  今天开始学习BES2500x系列的一天💖💖💖,开启新的征程,记录最美好的时刻🎉,每天进步一点点。
  专栏地址:【BES2500x系列】, 此专栏是我是夜阑的狗对BES2500x系列开发过程的总结,希望能够加深自己的印象,以及帮助到其他的小伙伴😉😉。
  如果文章有什么需要改进的地方还请大佬不吝赐教👏👏。


<<【系列文章索引】>>

1 介绍

  在嵌入式系统中,同步和通信是确保系统内各个组件协调工作的两个核心概念。它们对于实现高效、可靠的嵌入式应用至关重要。前面已经讲过同步概念了,接下来对通信概念进行简要说明。话不多说,那接下来就学习 RTX 系统中通信机制都有哪些吧,让我们原文再续,书接上回吧。😉

在这里插入图片描述

2 功能特性

  在实时操作系统(RTOS)中,任务管理和同步通信是关键组件,它们确保系统的高效和有序执行。本文将探讨这些概念,特别是线程管理、信号量、互斥锁、消息队列和邮箱处理。

  • 任务管理:RTX提供任务创建、调度和优先级管理,确保任务按照优先级及时执行。
  • 同步与通信:包括信号量、互斥锁、消息队列和邮箱,促进任务间的同步和数据交换。
  • 内存管理:内存池和动态内存分配,有效管理有限的系统资源。
  • 定时器服务:虚拟和硬件定时器,支持周期性任务和一次性事件触发。
  • 中断处理:保证中断服务的快速响应,同时保持任务的上下文安全。
  • 线程安全:通过内核级保护机制,防止多线程环境下的数据竞争和死锁。

3 同步与通信

3.1 通信

  通信是指嵌入式系统中不同组件或任务之间交换信息的过程。有效的通信机制对于分布式系统和多处理器系统尤为重要。嵌入式系统中常用的通信方式包括:

序号 方法 说明
1 消息队列(Message Queues) 任务间通过发送和接收带有数据的消息来通信,支持异步通信。
2 管道(Pipes) 一种半双工的数据传输方式,常用于进程间的通信。
3 共享内存(Shared Memory) 多个任务可以直接读写同一块内存区域,效率高但需要同步机制来避免冲突。
4 总线(Buses) I2CSPIUART等硬件接口,用于设备间的数据传输。
5 远程过程调用(RPC) 允许程序调用网络中另一台计算机上的子程序,模拟本地调用。
6 中断(Interrupts) 硬件触发的事件,用于通知 CPU 处理紧急或外部事件,是一种快速的通信方式。

  同步和通信机制的选择取决于嵌入式系统的具体需求,包括实时性、资源限制、复杂度以及系统的可靠性要求。合理设计同步和通信策略,是保证嵌入式系统高效稳定运行的关键。

4 同步与通信

4.1 消息队列

  消息队列允许线程间安全地传递固定大小的消息,提供了异步通信的方式。

4.1.1 定义

  消息队列允许线程安全地发送和接收固定大小的数据块。队列维护发送和接收的顺序。一般在文件开头会看到这样的定义:osMessageQDef

  • 代码
// 定义一个名为app_test1_queue的消息队列,可存储128个uint32_t类型的元素。
osMessageQDef(app_test1_queue, 128, uint32_t);

// 声明一个osMessageQId类型的变量app_test1_queue_id,用于保存消息队列的句柄。
// 在程序运行时,需要通过调用相关API初始化并获取有效的句柄值。
osMessageQId app_test1_queue_id = NULL;

  这段代码首先使用 osMessageQDef 宏定义了一个名为 app_test1_queue 的消息队列,它可以存储128个32位无符号整数。然后声明了一个变量 app_test1_queue_id ,用于存储消息队列的标识符(句柄),初始值设为 NULL 。在实际应用中,需要通过操作系统提供的API来初始化这个消息队列,并将返回的句柄赋值给app_test1_queue_id

/**
 * 定义一个消息队列。
 * 
 * 这个宏用于静态定义一个消息队列,它会创建一个静态队列控制块和一个用于存储消息的数据缓冲区。
 * 
 * @param name 消息队列的名称。
 * @param queue_sz 队列中能容纳的消息数量。
 * @param type 消息队列中每条消息的数据类型。
 */
#define osMessageQDef(name, queue_sz, type) \
static StaticQueue_t os_mq_cb_##name; \
static uint32_t os_mq_data_##name[(queue_sz) * sizeof(type)]; \
const osMessageQDef_t os_messageQ_def_##name = \
{ (queue_sz), \
  { NULL, 0U, (&os_mq_cb_##name), sizeof(StaticQueue_t), \
              (&os_mq_data_##name), sizeof(os_mq_data_##name) } }

  此宏定义了三个静态变量:一个静态队列控制块,一个消息数据数组,和一个用于OS的消息队列定义结构体。这个结构体包含了队列的大小、指针到静态队列控制块和消息数据数组的地址,以及这些数组的大小。这使得在系统运行时能够直接使用这个消息队列而无需动态分配内存。

  • 参数/函数讲解
序号 参数/函数 说明
1 osMessageQId 声明变量,用于存储消息队列的标识符(句柄),初始值设为NULL。
2 osMessageQDef 定义了静态变量:静态队列控制块,消息数据数组和用于OS的消息队列定义结构体
4.1.2 创建

  通过 osMessageQueueCreate() 函数创建消息队列,指定队列容量和消息大小。

  • 代码
/**
 * 初始化app_test1_queue消息队列。
 *
 * 这个函数负责创建名为app_test1_queue的消息队列,并将成功创建的句柄保存到全局变量app_test1_queue_id。
 * 如果消息队列创建失败,它会记录错误信息并返回-1。
 *
 * @return
 *   - 0: 消息队列创建成功。
 *   - -1: 创建消息队列失败。
 */
static int32_t app_test1_queue_init(void)
{
    // 使用osMessageCreate函数创建消息队列,并将句柄保存到全局变量
    app_test1_queue_id = osMessageCreate(osMessageQ(app_test1_queue), NULL);

    // 检查创建是否成功,如果失败则打印错误信息并返回-1
    if (app_test1_queue_id == NULL) {
        TRACE(0, "Failed to Create app_test_thread1_queue");
        return -1;
    }

    // 创建成功,返回0
    return 0;
}

  这段代码定义了一个名为 app_test1_queue_init 的静态函数,用于初始化之前定义的消息队列app_test1_queue 。它通过调用o sMessageCreate 函数创建消息队列,并检查返回的句柄是否有效。如果创建失败,它会记录错误信息并返回 -1 ;否则,返回 0 表示成功。

  • 参数/函数讲解
序号 参数/函数 说明
1 osMessageCreate 创建消息队列
4.1.3发送消息

  使用 osMessageQueueSend()osMessageQueuePut() 函数向队列发送消息。

  • 代码
/**
 * 尝试向app_test1_queue中发送消息。
 *
 * 此函数检查消息队列是否有足够的空间接收至少6条消息。如果队列有足够空间,
 * 它将向队列中放入一个值为0xFF的消息,不设置优先级。
 *
 * 注意:这个函数没有处理消息队列满的情况,因此在队列满时不会阻塞。
 */
void app_test1_queue_put(void)
{
    // 检查消息队列是否有超过5个空闲槽位
    if (osMessageGetSpace(app_test1_queue_id) > 5) {
        // 向消息队列app_test1_queue_id中插入一个值为0xFF的消息,优先级设为0
        osMessagePut(app_test1_queue_id, 0xFF, 0);
    }
}

  这个函数 app_test1_queue_put 尝试将一个值为 0xFF 的消息放入名为 app_test1_queue 的消息队列中。首先,它检查队列是否有足够的空间容纳至少6个新消息。如果满足条件,就调用 osMessagePut 将消息放入队列,否则不做任何操作。注意,这个函数没有处理队列已满的情况,所以如果队列已满,消息将不会被发送,也不会阻塞调用线程。

  • 参数/函数讲解
序号 参数/函数 说明
1 osMessageGetSpace 检查消息队列的空闲槽位
2 osMessagePut 将消息放入队列

4.2 邮箱处理

  邮箱是用于线程间交换结构化数据的对象池。每个邮箱包含一组预先分配的内存块,线程可以申请、发送和接收这些内存块。

4.2.1 定义

  一般在文件开头会看到这样的定义:osMailQDef

  • 代码
osMailQDef (app_test1_mailbox, APP_TEST1_MAX_MAILBOX, APP_TEST1_MAIL); 
/**
 * app_test1_mailbox: 邮箱队列定义
 *
 * 使用osMailQDef宏定义一个名为'app_test1_mailbox'的邮箱队列,最大邮件数为APP_TEST1_MAX_MAILBOX,
 * 邮件类型为APP_TEST1_MAIL。
 */

// 邮箱队列ID,用于后续操作
static osMailQId app_test1_mailbox_id = NULL;

/**
 * app_test1_mail_alloc - 分配并初始化一个APP_TEST1_MAIL类型的邮件
 *
 * @param mail 指向邮件指针的指针,用于存放新分配的邮件地址。
 *
 * 返回值: 成功分配时返回0,失败则返回非0值。
 *
 * 此函数为内部使用,负责从'app_test1_mailbox'邮箱队列中分配一个新的邮件,并将其地址存储在
 * 输入参数'mail'指向的变量中。具体实现省略。
 */
static int app_test1_mail_alloc(APP_TEST1_MAIL** mail)
{
    // ...
}

  osMailQDef 定义了一个名为 app_test1_mailbox 的邮箱队列,用于存储 APP_TEST1_MAIL 类型的数据。APP_TEST1_MAX_MAILBOX 定义了邮箱队列可容纳的最大邮件数量。这个邮箱队列可以用于多线程或任务之间的数据通信,确保数据安全地传递。

/**
 * 定义一个邮箱队列。
 * 
 * 该宏用于静态定义一个邮箱队列以及相关的OS邮箱队列结构体。它为指定的邮箱队列分配内存,
 * 并初始化OS邮箱队列结构体。
 * 
 * @param name 邮箱队列的名称。
 * @param queue_sz 邮箱队列中邮件的最大数量。
 * @param type 邮件中元素的类型。
 */
#define osMailQDef(name, queue_sz, type) \
static uint32_t os_mailQ_m_##name[3+((sizeof(type)+3)/4)*(queue_sz)]; \
osMailQDef_t os_mailQ_def_##name = \
{ {(queue_sz), sizeof(type), (os_mailQ_m_##name)}, NULL, {NULL} }

  在上述宏定义中:

  (1) 第一部分定义了一个静态数组 os_mailQ_m_##name,用于存储邮箱队列中的邮件。数组大小根据邮件类型 type 的大小和队列大小 queue_sz 动态计算得出。
  (2) 第二部分定义了一个 osMailQDef_t 类型的结构体 os_mailQ_def_##name,其中包含了邮箱队列的配置信息,如队列大小、邮件类型大小以及邮件存储区的指针。

  • 参数/函数讲解
序号 参数/函数 说明
1 osMailQDef 定义了的邮箱队列,用于存储 APP_TEST1_MAIL 类型的数据
2 app_test1_mailbox_id 是一个全局变量,用于存储邮箱队列的标识符,方便后续操作
3 app_test1_mail_alloc 用于从 app_test1_mailbox 中分配一个新的邮件,并将分配的邮件地址通过参数 mail 返回
4 os_mailQ_m_##name 用于存储邮箱队列中的邮件
5 osMailQDef_t 定义结构体,其中包含了邮箱队列的配置信息
4.2.2 创建

  通过 osMailQCreate() 函数创建邮箱,指定邮箱的大小和数据类型。

  • 代码
/**
 * app_test1_mailbox_init - 初始化app_test1_mailbox邮箱队列
 *
 * @return: 成功初始化时返回0,失败则返回-1。
 *
 * 此函数用于初始化之前定义的'app_test1_mailbox'邮箱队列。它调用osMailCreate函数创建邮箱队列,
 * 并将返回的邮箱ID存储在全局变量'app_test1_mailbox_id'中。如果创建失败,函数会输出错误信息
 * "Failed to Create app_test_thread1_mailbox",并返回-1表示初始化失败。
 */
static int32_t app_test1_mailbox_init(void)
{
    app_test1_mailbox_id = osMailCreate(osMailQ(app_test1_mailbox), NULL);

    if (app_test1_mailbox_id == NULL) {
        TRACE(0, "Failed to Create app_test_thread1_mailbox");
        return -1;
    }

    return 0;
}

  这个函数 app_test1_mailbox_init 负责初始化之前通过 osMailQDef 宏定义的 app_test1_mailbox 邮箱队列。如果初始化成功,它将返回0;如果失败(即无法创建邮箱队列),它会打印错误信息并返回-1。

  • 参数/函数讲解
序号 参数/函数 说明
1 osMailCreate 创建邮箱队列
4.2.3 发送/释放邮件

  使用 osMailQAlloc() 分配邮箱中的空间,然后用 osMailPut() 发送邮件。

  • 代码
/**
 * app_test1_mail_send - 发送一个APP_TEST1_MAIL类型的邮件到app_test1_mailbox
 *
 * @param mail 需要发送的邮件对象指针。
 *
 * 返回值: 成功发送时返回0,失败则返回非0值。
 *
 * 此函数用于将一个APP_TEST1_MAIL类型的邮件对象发送到'app_test1_mailbox'邮箱队列中。
 * 具体实现省略,可能涉及到邮箱队列的同步原语以保证线程安全。
 */
static int app_test1_mail_send(APP_TEST1_MAIL* mail)
{
    // ...
}

/**
 * app_test1_mail_free - 释放app_test1_mailbox中的一个邮件对象
 *
 * @param mail_p 已分配的邮件对象指针。
 *
 * 返回值: 成功释放时返回0,失败则返回非0值。
 *
 * 此函数用于释放'app_test1_mailbox'邮箱队列中已分配的一个邮件对象,以便于后续再使用。
 * 具体实现省略,可能涉及邮箱队列的同步原语以保证线程安全。
 */
static int app_test1_mail_free(APP_TEST1_MAIL* mail_p)
{
    // ...
}
  • 参数/函数讲解
序号 参数/函数 说明
1 app_test1_mail_send 用于向 app_test1_mailbox 邮箱队列中发送邮件
2 app_test1_mail_free 用于向 app_test1_mailbox 邮箱队列中释放已分配的邮件
4.2.4 获取邮件

  线程通过 osMailGet() 函数获取邮件,可以选择等待或立即返回。

  • 代码
/**
 * @brief          获取应用测试1的邮件对象
 * 
 * @description    该函数从内部数据结构中获取一个`APP_TEST1_MAIL`类型的邮件对象。
 *                 如果邮件可用,它将分配内存并填充邮件内容,然后将其指针返回。
 * 
 * @param[out]     mail_p     指向接收`APP_TEST1_MAIL`结构体指针的指针。
 *                            如果成功获取邮件,此参数将被设置为有效邮件对象的指针。
 * 
 * @return         成功获取邮件对象返回0,否则返回非0错误代码:
 *                 -1:邮件队列为空
 *                 -2:内存分配失败
 *                 其他值:可能表示其他错误情况
 *
 * @note           实现应考虑线程安全,可能需要加锁来保护数据结构。
 *                 如果队列为空,可以选择阻塞等待,直到有新邮件到达。
 */
static int app_test1_mail_get(APP_TEST1_MAIL** mail_p)
{
    // 实现获取邮件对象的逻辑,包括检查队列、分配内存、填充邮件内容等
    // ...

    if (/* 邮件队列为空或分配内存失败等错误条件 */) {
        return -1; // 或者 -2
    }

    return 0; // 成功获取邮件
}

  app_test1_mail_get 函数用于从 app_test1_mailbox 邮箱队列中取出一个邮件对象。当邮箱队列为空时,函数可能阻塞等待,直到有新的邮件可供消费。函数返回0表示成功获取邮件,非0值表示队列为空或出现错误。具体实现细节被省略,实际操作中可能需要考虑线程同步问题。

  • 参数/函数讲解
序号 参数/函数 说明
1 app_test1_mail_get 用于从邮箱队列中取出一个邮件对象

<<【系列文章索引】>>

请添加图片描述


总结

  感谢观看,这里就是 同步与通信篇 – 消息队列和邮箱处理,如果觉得有帮助,请给文章点个赞吧,让更多的人看到。🌹 🌹 🌹

在这里插入图片描述

  也欢迎你,关注我。👍 👍 👍

  原创不易,还希望各位大佬支持一下,你们的点赞、收藏和留言对我真的很重要!!!💕 💕 💕 最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!下期再见。🎉

更多专栏订阅:



订阅更多,你们将会看到更多的优质内容!!