一种简单安全的消息队列的C语言解决方案

〇、背景

  基于嵌入式编程,对于有安全等级要求的项目,一般都会对编码有诸多安全性考虑的规则限制。在实际的编程中,消息队列的使用还是比较频繁的,但是对于不使用操作系统的情况下,消息队列相关的功能就需要手动实现。下面将会介绍一种简单的、安全等级较高的消息队列的实现方式。

一、基本思路

1.1 消息队列的属性要求

  作为一个存储用的消息队列,一般情况下需要几个属性。

  • 消息队列的读指针:也就是数据从什么位置开始读取
  • 消息队列的写指针:也就是数据从什么位置开始写入
  • 消息队列的现存数据长度:也就是读取多少数据
  • 消息队列的空闲容量:也就是目前数据缓存区剩余的大小
  • 消息队列的已使用容量:也就是目前数据缓存区使用了的大小
  • 消息队列的容量:一般情况下,此容量表示的数据缓存区的大小
  • 消息队列数据缓冲区:也就是队列用于存放数据的地方

1.2 消息队列的结构定义

  考虑到表示的精准性、使用的简洁性等方面,所以需要尽可能少的变量成员定义。本例中定义的结构如下。

/* 定义消息队列的数据类型 */
typedef uint8_t QueueDataType_t;

/* 定义消息队列的数据结构 */
typedef struct _message_queue_class_struct
{
   
   
    uint32_t read_pos;           /* 写队列数据指针 */
    uint32_t write_pos;          /* 读队列数据指针 */
    uint32_t queue_size;         /* 队列容量 */
    QueueDataType_t *queue_data; /* 队列数据 */
} MsgQueueClass_st;

  在如上的定义中,并没有直接定义 “消息队列的现存数据长度”“消息队列的空闲容量”“消息队列的已使用容量” 等信息变量,是因为上述信息均可以通过结构中定义的 “消息队列的读指针”“消息队列的写指针” 进行相关的运算获得,后面代码中会有相关的实现。

1.3 写入、读取数据的长度信息处理

  通过上述的消息队列的数据结构定义,不难看出来成员 queue_data 指向的是一个 uint8_t 的数据缓存区(数组),对于每一次写入操作,如何记录本次写入数据的长度其实很重要,因为本消息队列中存放的数据可能是通过多次写入的,而且每次写入的数据长度还不相同,那么在进行数据读取的时候,也不可能一下子读取缓存区中所有的数据,也不可能只读取一个随便长度的数据,这样的话整个队列管理起来就很困难。

  所以为了方便消息队列的管理,设定每一次读取数据的长度即为此数据写入的时候的长度。

  对于写入数据的长度信息的记录,比较方便记录和获取的方式就是写入到实际数据的头部,其占用的字节数可以自定义,本例中采用最常用的 uint16_t 来标识。所以:

  • 每次在消息队列中写入的数据时候,先在队列中写入本次数据的长度,然后再写入数据。
  • 每次在消息队列中读取的数据时候,先在队列中读取本次数据的长度,然后再读取目标长度数据。

二、基本实现

2.1 消息队列的初始化

  消息队列的初始化,主要就是初始化读指针、写指针、绑定数据缓冲区、初始化缓冲区容量等基础属性的设置。其实现代码也比较简单。

/*
 *  @fn             MsgQueue_Init
 *  @brief          初始化队列,队列结构体首地址,队列数据类型,函数将初始化队列数据为初始化值状态
 *  @param[in]      queue_handle       队列结构体首地址
 *  @param[in]      queue_buffer       队列数据类型
 *  @param[in]      queue_size         队列数据容量
 *  @return         true 初始化成功  false 初始化失败
 */
bool MsgQueue_Init(MsgQueueClass_st *queue_handle, QueueDataType_t *queue_buffer, uint32_t queue_size)
{
   
   
    /** 参数判断 */
    if ((NULL == queue_handle) || (0U == queue_size) || (NULL == queue_buffer))
    {
   
   
        return false;
    }

    memset(queue_buffer, 0U, queue_size);
    /** 初始化队列数据 */
    queue_handle->read_pos = 0U;
    queue_handle->write_pos = 0U;
    queue_handle->queue_size = queue_size;
    queue_handle->queue_data = queue_buffer;

    /** 返回 */
    return true;
}

2.2 消息队列的写入操作

2.2.1 消息队列的基础写入操作

  消息队列数据的写入操作,需要考虑几个方面的问题:

  • 当前空闲容量是否足够
  • 当前写入数据位置是否需要翻转(也就是写入数据指针位置 + 写入数据的长度 是否 超过缓存区末尾)
  • 当前写入数据指针是否需要翻转

  综上所述,消息队列的写入的基础操作,编码如下。

/*
 *  @fn         MsgQueue_WriteData
 *  @brief      队列写操作,输入队列结构体首地址、待写入数据和长度,函数将数据存在队列中
 *  @param[in]  queue_handle        队列结构体首地址
 *  @param[in]  write_buffer        待写入队列的数据
 *  @param[in]  write_data_len      待写入队列的数据长度
 *  @return     NONE
 *  @exception  此函数对输入指针参数未判断是否为NULL,如果任一指针参数为NULL,则可能引发core dump的异常。所以需要调用者保证指针参数的有效性
 */
static void MsgQueue_WriteData(MsgQueueClass_st *queue_handle, const QueueDataType_t *write_buffer, uint32_t write_data_len)
{
   
   
    register uint32_t i, j;
    register QueueDataType_t *t_in_ptr;

    /* 由于已经能够判定有足够的空间,不需要再对 read_pos 值进行判断 */
    j = queue_handle->queue_size - (queue_handle->write_pos);
    t_in_ptr = &(queue_handle->queue_data[queue_handle->write_pos]);

    if (j > write_data_len)
    {
   
   
        /* 写入时可以不用翻转 */
        /* 用自加指令可以明显提高指令的执行速度 */
        for (i = 0U; i < write_data_len; i++)
        {
   
   
            *t_in_ptr = *write_buffer;
            t_in_ptr++;
            write_buffer++;
        }
        queue_handle->write_pos += write_data_len;
    }
    else
    {
   
   
        /* 写入过程中必然翻转 */
        /* 将数据从当前指针到缓冲区尾部,写入的长度为j */
        for (i = 0U; i < j; i++)
        {
   
   
            *t_in_ptr = *write_buffer;
            t_in_ptr++;
            write_buffer++;
        }

        /* 已经写到缓冲区尾部了,应该从缓冲区头开始写起 */
        t_in_ptr = 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青椒*^_^*凤爪爪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值