【事件驱动框架OSAL】二.消息的管理机制
OSAL消息管理机制
- 二、消息管理机制
- 2.1 消息的数据结构
- 2.2 消息内存分配
- 2.3 消息的接收和销毁
- 2.3 源码链接地址
二、消息管理机制
在上一篇文中提到,系统消息事件(SYS_EVENT_MSG)用于任务间传递数据,而消息队列是这种机制的基础,它允许任务通过异步消息传递复杂数据(如网络数据包、控制命令等),而不仅仅是简单的事件通知。
消息队列,即用来保存消息的队列数据结构,本文结合OSAL的源码,简述OSAL中消息的实现和管理,包括消息结构、消息内存管理,发送和接收等流程。
2.1 消息的数据结构
typedef struct
{uint8 event; // 消息事件类型,可以从0到255,用于区分不同的消息,和前面的事件集没有关系uint8 status; // 状态,作为备用
} osal_event_hdr_t; // 统一的消息事件头typedef struct
{osal_event_hdr_t hdr; // 第一个成员必须为osal_event_hdr_tuint8* Data_t; // 变量指针,指向用户定义的数据或消息体,这里可以随便定义,定义其他结构体也行。
} osal_sys_msg_t; //使用默认系统消息结构体osal_sys_msg_t做用户消息结构体
注意:消息结构体的第一个成员必须是osal_msg_hdr_t,否则无法正确解析。在32位单片机中,sizeof(osal_sys_msg_t) = 8 字节,sizeof(uint8_t ) = 4字节 。
OSAL的消息队列传递的每条消息必须包含一个消息头(osal_msg_hdr_t),用于标识消息类型和基本元数据。用户可根据需求扩展自定义数据字段。指向结构体的指针,经过强制转换,可以指向该结构体的首个成员,因为结构体变量的地址和首个成员地址是同一个。
在用户消息定义的结构体中,除了第一个成员变量必须为osal_event_hdr_t 结构外,之后的数据类型,可以随便定义。
例如:定义一个温度数据
// 用户自定义消息示例(温度数据)
typedef struct {osal_msg_hdr_t hdr; // 必须包含消息头uint16 temp; // 温度值uint8 sensor_id; // 传感器ID
} TempMessage_t;
在本文中,使用指针变量Data_t来解释代码执行流程。
osal_sys_msg_t消息结构体,在内存中示意图如下:
2.2 消息内存分配
消息内存通过 osal_msg_allocate() 动态分配,需指定消息总长度(包括消息头和数据);接收任务处理完消息后,必须手动调用osal_msg_deallocate()释放内存,避免泄漏。
注意:osal_msg_allocate() ≠ osal_mem_alloc() 内存分配函数,osal_msg_allocate() 要完成消息队列结点的构造。
源码实现如下:
uint8 * osal_msg_allocate(uint16 len)
{osal_msg_hdr_t *hdr;if(len == 0){return (NULL);}// 分配一块内存空间,并强制转换为 osal_msg_hdr_t*. 相当于为用户消息体osal_sys_msg_t增加了一个包头osal_msg_hdr_thdr = (osal_msg_hdr_t *) osal_mem_alloc((short)(len + sizeof(osal_msg_hdr_t)));if(hdr){hdr->next = NULL;hdr->len = len;hdr->dest_id = TASK_NO_TASK;return ((uint8 *)(hdr + 1)); // 返回用户消息的osal_sys_msg_t变量地址}else{return (NULL);}
}
而 osal_msg_hdr_t 结构为:
typedef struct
{void *next;uint16 len;uint8 dest_id;
} osal_msg_hdr_t;
是队列的结点,用于形成队列数据结构。
经过这一步之后,一个完整的队列结点在内存中的分布示意如下 :
在这里,用户数据是一个int变量,用于保存打印次数count。
重新回到发送消息的任务处理函数中:
uint16 print_task_event_process(uint8 task_id, uint16 task_event)
{// 不接收消息,忽略系统消息事件if(task_event & PRINTF_STR){static int print_count = 0;printf("Print task printing, total memory : %d byte, used memory : %d byte !\n", MAXMEMHEAP, osal_heap_mem_used());print_count++;if(print_count % 5 == 0 && print_count != 0){//向统计任务发送消息general_msg_data_t *msg;msg = (general_msg_data_t*)osal_msg_allocate(sizeof(general_msg_data_t) + sizeof(int));if(msg != NULL){//消息结构体的data数据指针偏移至申请到的内存的数据段//msg->data = (unsigned char*)( msg + sizeof(osal_event_hdr_t) );msg->data = (unsigned char*)(msg + 1);msg->hdr.event = PRINTF_STATISTICS;msg->hdr.status = 0;*((int*)msg->data) = print_count;osal_msg_send(statistics_task_id, (uint8*)msg);}}return task_event ^ PRINTF_STR; //处理完后需要清除事件位}return 0;
}
这里的 general_msg_data_t 和前面提到的osal_sys_msg_t 结构一样。
可以看到,经过消息内存分配之后,osal_msg_allocate 函数返回的是类型为osal_sys_msg_t的消息地址,指向的是用户自定义的结构体内存,之后调用 osal_msg_send(),消息队列入队,然后调用osal_set_event 设置系统消息事件。
uint8 osal_msg_send(uint8 destination_task, uint8 *msg_ptr)
{if(msg_ptr == NULL){return (INVALID_MSG_POINTER);}if(destination_task >= tasksCnt){osal_msg_deallocate(msg_ptr);return (INVALID_TASK);}// Check the message headerif(OSAL_MSG_NEXT(msg_ptr) != NULL ||OSAL_MSG_ID(msg_ptr) != TASK_NO_TASK){osal_msg_deallocate(msg_ptr);return (INVALID_MSG_POINTER);}OSAL_MSG_ID(msg_ptr) = destination_task;// queue messageosal_msg_enqueue(&osal_qHead, msg_ptr);// Signal the task that a message is waitingosal_set_event(destination_task, SYS_EVENT_MSG);return (SUCCESS);
}
这一段代码中,需要注意的是这两个宏定义:
#define OSAL_MSG_NEXT(msg_ptr) ((osal_msg_hdr_t *) (msg_ptr) - 1)->next
#define OSAL_MSG_ID(msg_ptr) ((osal_msg_hdr_t *) (msg_ptr) - 1)->dest_id
结合前面提到的,队列结点在内存中的分布示意图可知,msg_ptr 是指向用户消息体的指针,将其强制转换为 osal_msg_hdr_t*,再减 1 操作,即向低地址前移sizeof(osal_msg_hdr_t)个字节,便可以回到队列结点首地址。
下面是调试过程中的截图:
按照函数执行的顺序来说明。
一、首先在内存中分配20个字节,从0x20000784字节开始,分配20个字节空间。前8个字节用于保存 osal_msg_hdr_t 数据,返回用户消息体的地址。
二、然后再把返回的指针转换为指向用户消息体的指针,红色部分表示 osal_sys_msg_t 结构体内存块,data指针中保存的是地址,该地址中保存的是打印次数的整型值。由于分配的内存块是连续的,因此data指针变量的下一个地址,就是数据单元。
2.3 消息的接收和销毁
本例子中,在统计任务中接收其他任务发送过来的消息。有消息事件时,会进入系统消息事件中。接收任务处理完消息后,必须手动调用osal_msg_deallocate()释放内存,避免泄漏。
/*** @brief 当前任务的事件回调处理函数* @param task_id [任务ID]* @param task_event [收到的本任务事件]* @return uint16 [未处理的事件]*/
uint16 statistics_task_event_process(uint8 task_id, uint16 task_event)
{if(task_event & SYS_EVENT_MSG) //判断是否为系统消息事件{osal_sys_msg_t *msg_pkt;msg_pkt = (osal_sys_msg_t *)osal_msg_receive(task_id); //从消息队列获取一条消息while(msg_pkt){switch(msg_pkt->hdr.event) //判断该消息事件类型{case PRINTF_STATISTICS:{int count = *(int*)(((general_msg_data_t*)msg_pkt)->data);printf("Statistics task receive print task printf count : %d\n", count);break;}default:break;}osal_msg_deallocate((uint8 *)msg_pkt); //释放消息内存msg_pkt = (osal_sys_msg_t *)osal_msg_receive(task_id); //读取下一条消息}// return unprocessed eventsreturn (task_event ^ SYS_EVENT_MSG);}return 0;
}
2.3 源码链接地址
OSAL github 链接地址
相关文章:
【事件驱动框架OSAL】二.消息的管理机制
OSAL消息管理机制 二、消息管理机制2.1 消息的数据结构2.2 消息内存分配2.3 消息的接收和销毁2.3 源码链接地址 二、消息管理机制 在上一篇文中提到,系统消息事件(SYS_EVENT_MSG)用于任务间传递数据,而消息队列是这种机制的基础&…...
《论多源数据集成及应用》审题技巧 - 系统架构设计师
论多源数据集成及应用写作框架 一、考点概述 本论题“论多源数据集成及应用”主要考察的是计算机软件测试工程师在数据管理和集成方面的专业知识与实践能力。论题聚焦于信息爆炸时代企业、组织和个人所面临的数据挑战,特别是如何有效地收集、整理和清洗来自不同渠…...
【企业微信开发工具,获取位置】
微信开发者工具获取位置失败 报错原因解决方案 报错原因 getLocation:fail, the permission value is offline verifying解决方案 在开发工具栏输入链接,进行位置获取获取成功后,重新进入调用获取位置的页面即可如下图:...
HTML之JavaScript DOM编程获取元素的方式
HTML之JavaScript DOM编程获取元素的方式 1.获得document DOM树window.document(是window的属性)2.从document中获取要操作的元素1.直接获取var aaa document.getElementById("username") // 根据元素的id值获取页面上的唯一一个元素,有同名的则返回找到的第一个var…...
如何安装vm和centos
以下是在VMware中安装CentOS的一般步骤: 一、安装VMware 以下是在 Windows 系统中安装 VMware 软件的详细步骤: 1. 下载 VMware 软件: - 访问 VMware 官方网站(https://www.vmware.com/)。 - 根据您的操作系统选择合…...
docker 安装redis 7.4.2并挂载配置文件以及设置密码
文章目录 docker 安装redis 7.4.2下载 redis如果你喜欢使用最新的版本创建挂载redis 配置文件创建容器 docker 安装redis 7.4.2 截至2025年2月21日,Redis的最新稳定版本是 7.4.2。 下载 redis 如果你想拉取Redis的特定版本(例如最新的稳定版本 7.4.2&a…...
计算机毕业设计SpringBoot+Vue.js在线教育系统(源码+文档+PPT+讲解)
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...
Linux-C-函数栈-SP寄存器
sp(Stack Pointer,栈指针)是计算机体系结构中一个非常重要的寄存器,下面将详细介绍其作用和原理。 作用 1. 管理栈内存 栈是一种后进先出(LIFO,Last In First Out)的数据结构,在程…...
vi的基本使用
vi 是 Unix/Linux 系统中最常用的文本编辑器之一,功能强大但学习曲线较陡。以下是 vi 的基本使用方法: --- ### **1. vi 的两种模式** - **命令模式(Command Mode)**: - 默认进入的模式,用于执行命令&a…...
clickhouse--表引擎的使用
表引擎决定了如何存储表的数据。包括: 数据的存储方式和位置,写到哪里以及从哪里读取数据。(默认是在安装路径下的 data 路径)支持哪些查询以及如何支持。(有些语法只有在特定的引擎下才能用)并发数据访问。索引的使用࿰…...
LeetCode刷题零碎知识点整理
系列博客目录 文章目录 系列博客目录 数组变量有length属性,String类的对象有length()方法。String s; s.split("\\s");不能去除头部空格,需要使用s s.trim();String类的对象有toCharArray()方法,List<>类型有toArray()方法…...
GLTFLoader.js和OrbitControls.js两个 JavaScript 文件都是 Three.js 生态系统中的重要组成部分
GLTFLoader.js和OrbitControls.js两个 JavaScript 文件都是 Three.js 生态系统中的重要组成部分: 1. GLTFLoader.js 作用 GLTFLoader.js 是 Three.js 库中的一个辅助加载器脚本,其主要功能是加载 GLB 或 GLTF 格式的 3D 模型。GLTF(GL Tra…...
大厂数据仓库数仓建模面试题及参考答案
目录 什么是数据仓库,和数据库有什么区别? 数据仓库的基本原理是什么? 数据仓库架构是怎样的? 数据仓库分层(层级划分),每层做什么?分层的好处是什么?数据分层是根据什么?数仓分层的原则与思路是什么? 数仓建模常用模型有哪些?区别、优缺点是什么?星型模型和雪…...
angular简易计算器
说明: 用angular实现计算器效果,ui风格为暗黑 效果图: step1: C:\Users\Administrator\WebstormProjects\untitled4\src\app\calnum\calnum.component.ts import { Component } from angular/core;Component({selector: app-calnum,import…...
谈谈 ES 6.8 到 7.10 的功能变迁(3)- 查询方法篇
上一篇咱们了解了 ES 7.10 相较于 ES 6.8 新增的字段类型,这一篇我们继续了解新增的查询方法。 Interval 间隔查询: 功能介绍 Interval 查询,词项间距查询,可以根据匹配词项的顺序、间距和接近度对文档进行排名。主要解决的查询…...
16、Python面试题解析:python中的浅拷贝和深拷贝
在 Python 中,浅拷贝(Shallow Copy) 和 深拷贝(Deep Copy) 是处理对象复制的两种重要机制,它们的区别主要体现在对嵌套对象的处理方式上。以下是详细解析: 1. 浅拷贝(Shallow Copy&a…...
游戏引擎学习第119天
仓库:https://gitee.com/mrxiao_com/2d_game_3 上一集回顾和今天的议程 如果你们还记得昨天的进展,我们刚刚完成了优化工作,目标是让某个程序能够尽可能快速地运行。我觉得现在可以说它已经快速运行了。虽然可能还没有达到最快的速度,但我们…...
爬虫解析库:Beautiful Soup的详细使用
文章目录 1. 安装 Beautiful Soup2. 基本用法3. 选择元素4. 提取数据5. 遍历元素6. 修改元素7. 搜索元素8. 结合 requests 使用9. 示例:抓取并解析网页10. 注意事项 Beautiful Soup 是一个用于解析 HTML 和 XML 文档的 Python 库,它提供了简单易用的 API…...
OpenHarmony-4.基于dayu800 GPIO 实践(2)
基于dayu800 GPIO 进行开发 1.DAYU800开发板硬件接口 LicheePi 4A 板载 2x10pin 插针,其中有 16 个原生 IO,包括 6 个普通 IO,3 对串口,一个 SPI。TH1520 SOC 具有4个GPIO bank,每个bank最大有32个IO: …...
【C++设计模式】观察者模式(1/2):从基础到优化实现
1. 引言 在 C 软件与设计系列课程中,观察者模式是一个重要的设计模式。本系列课程旨在深入探讨该模式的实现与优化。在之前的课程里,我们已对观察者模式有了初步认识,本次将在前两次课程的基础上,进一步深入研究,着重…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...
AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...
初学 pytest 记录
安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...
处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的
修改bug思路: 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑:async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...
CSS | transition 和 transform的用处和区别
省流总结: transform用于变换/变形,transition是动画控制器 transform 用来对元素进行变形,常见的操作如下,它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...
Golang——6、指针和结构体
指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...
NPOI操作EXCEL文件 ——CAD C# 二次开发
缺点:dll.版本容易加载错误。CAD加载插件时,没有加载所有类库。插件运行过程中用到某个类库,会从CAD的安装目录找,找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库,就用插件程序加载进…...
