文章概要
〇、背景
基于嵌入式编程,对于有安全等级要求的项目,一般都会对编码有诸多安全性考虑的规则限制。在实际的编程中,消息队列的使用还是比较频繁的,但是对于不使用操作系统的情况下,消息队列相关的功能就需要手动实现。下面将会介绍一种简单的、安全等级较高的消息队列的实现方式。
一、基本思路
1.1 消息队列的属性要求
作为一个存储用的消息队列,一般情况下需要几个属性。
- 消息队列的读指针:也就是数据从什么位置开始读取
- 消息队列的写指针:也就是数据从什么位置开始写入
- 消息队列的现存数据长度:也就是读取多少数据
- 消息队列的空闲容量:也就是目前数据缓存区剩余的大小
- 消息队列的已使用容量:也就是目前数据缓存区使用了的大小
- 消息队列的容量:一般情况下,此容量表示的数据缓存区的大小
- 消息队列数据缓冲区:也就是队列用于存放数据的地方
1.2 消息队列的结构定义
考虑到表示的精准性、使用的简洁性等方面,所以需要尽可能少的变量成员定义。本例中定义的结构如下。
/* 定义消息队列的数据类型 */typedefuint8_t QueueDataType_t;/* 定义消息队列的数据结构 */typedefstruct_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的异常。所以需要调用者保证指针参数的有效性
*/staticvoidMsgQueue_WriteData(MsgQueueClass_st *queue_handle,const QueueDataType_t *write_buffer,uint32_t write_data_len){
registeruint32_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++;
版权归原作者 青椒*^_^*凤爪爪 所有, 如有侵权,请联系我们删除。