【事件驱动框架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 软件与设计系列课程中,观察者模式是一个重要的设计模式。本系列课程旨在深入探讨该模式的实现与优化。在之前的课程里,我们已对观察者模式有了初步认识,本次将在前两次课程的基础上,进一步深入研究,着重…...

wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...

黑马Mybatis
Mybatis 表现层:页面展示 业务层:逻辑处理 持久层:持久数据化保存 在这里插入图片描述 Mybatis快速入门 ; List<Integer> evens new ArrayList…...

MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...