0. 引言

本文展示一个实践路径:以轻量级 C++ 事件库 eventpp 为核心,设计并实现一个面向嵌入式的、可移植的 Active Object(AO)事件驱动架构。该架构满足以下目标:

  • 跨平台兼容:单套代码在 RT-Thread(或裸机)与 ARM-Linux 下均可编译与运行
  • 开源免费:使用 eventpp 与自研轻量库,避免商业授权成本
  • 可定制:通过策略(Policy)层注入不同的锁、容器、优先级实现适配不同平台

1. 为什么选择 eventpp

eventpp 是一个纯头文件的现代 C++ 事件库,特点非常契合嵌入式与资源受限环境的需求:

  • 纯头文件、无运行时依赖,易于移植到 RT-Thread、裸机或交叉编译的 ARM-Linux
  • 提供 CallbackList、EventDispatcher、EventQueue 三大功能模块,能满足同步与异步事件场景
  • 支持 Policy 注入,可替换底层容器、锁、优先级策略 — 便于将内存/并发策略绑定到平台要求
  • 小巧、可读、易于裁剪:便于做静态分配或替换动态容器

总结:eventpp 不是一个完整的 RTOS 或高级状态机框架,但作为“事件发布/订阅 + 异步队列”的基石非常合适。

2. eventpp 三大核心组件速览

  1. CallbackList

    • 基础的回调列表,可注册任意可调用对象(函数、Lambda、成员函数),在回调执行过程中可安全添加/删除。适合“固定事件、原型各异”的简单场景。
  2. EventDispatcher

    • 类型到回调列表的映射:同步按事件类型分发,所有监听器立刻执行。支持自定义 Policy,从复杂事件对象中抽取事件类型。
  3. EventQueue

    • 异步版 EventDispatcher:先 enqueue 入队,再 process 批量分发。支持跨线程 wait()/process(),也可基于自定义 Policy 实现优先级调度。

这些组件可以组合成 AO(Active Object)模型:每个 AO 维护自己的 EventQueue(或多个队列),独立线程消费事件并驱动状态机或回调。

3. 使用速览(示例)

3.1 CallbackList 简单示例

#include <eventpp/callbacklist.h>eventpp::CallbackList<void(int)> cbList;auto h1 = cbList.append([](int x){ printf("A: %d\n", x); });
auto h2 = cbList.append([](int x){ printf("B: %d\n", x); });cbList(42);   // 输出 A: 42  B: 42cbList.remove(h2);
cbList(7);    // 只输出 A: 7

3.2 EventDispatcher 基础用法

#include <eventpp/eventdispatcher.h>enum class Sig { Start, Stop };eventpp::EventDispatcher<Sig, void(int), std::map> dispatcher;dispatcher.appendListener(Sig::Start, [](int v){ printf("Start %d\n", v); });dispatcher.dispatch(Sig::Start, 123);  // 输出 Start 123

3.3 EventQueue 异步队列

#include <eventpp/eventqueue.h>eventpp::EventQueue<std::string> queue;queue.appendListener([](const std::string &s){printf("Got: %s\n", s.c_str());
});queue.enqueue("Hello");
queue.enqueue("World");queue.process();  // 输出 Got: Hello  Got: World

4. 内存策略:静态分配与零动态分配

在 RT-Thread 或硬实时路径中要保证可测的 WCET 与避免内存抖动,应优先使用静态或预分配结构存放事件。以下给出两种常见策略与实现样例。

  • 静态环形缓冲(单生产者单消费者 / 多生产者场景需额外锁或 lock-free 结构)
  • 对象池(预分配固定数量事件对象,支持复用)
  • 可控的少量动态内存(仅在初始化阶段分配)在资源非常受限时也可接受

静态环形缓冲示例(已经在问题中给出,这里补充线程安全注意):

// StaticRing.h
#include <atomic>
#include <cstddef>template<typename T, size_t N>
class StaticRing {static_assert(N >= 2, "N must be >= 2");T buffer[N];std::atomic<size_t> head{0}, tail{0};
public:bool enqueue(const T &v) {size_t t = tail.load(std::memory_order_relaxed);size_t next = (t + 1) % N;if(next == head.load(std::memory_order_acquire)) {return false; // 队列已满}buffer[t] = v;tail.store(next, std::memory_order_release);return true;}bool dequeue(T &out) {size_t h = head.load(std::memory_order_relaxed);if(h == tail.load(std::memory_order_acquire)) {return false; // 队列为空}out = buffer[h];head.store((h + 1) % N, std::memory_order_release);return true;}
};

注意:

  • 对于多生产者/多消费者,需要额外的原子或锁保护(或使用专门的 lock-free 队列实现)
  • 内存拷贝成本:如果事件对象较大,建议使用小事件句柄(ID + 指针到对象池)或移动语义
  • 避免在 ISR 中进行占用长时间的操作:在 ISR 中只做入队与唤醒,处理留给 AO 线程

5. 平台抽象层(PAL):解耦 RTOS / Linux 实现

为了实现同一套 AO 代码在 RT-Thread 和 ARM-Linux 下工作,推荐引入一个 PAL(Platform Abstraction Layer)最小 API:

  • 线程 / 任务创建:PalThread::create(…)
  • 互斥锁 / 递归锁:PalMutex / PalRecursiveMutex
  • 信号量 / 事件:PalSemaphore
  • 中断安全入队的 primitive(如果 RTOS 提供 ISR-safe API,可包装)
  • 时间与延时:PalTime::sleepMs, now

示例接口(伪头文件):

// pal.h (伪接口)
#pragma once
#include <functional>
#include <cstdint>namespace pal {using ThreadFunc = std::function<void()>;struct ThreadHandle { /* opaque */ };class Thread {
public:static ThreadHandle create(const char* name, ThreadFunc func, int priority, size_t stackSize = 4096);static void join(ThreadHandle);// ...
};class Mutex {
public:Mutex();void lock();bool try_lock();void unlock();
};class Semaphore {
public:Semaphore(unsigned initial = 0);void acquire();bool try_acquire();void release();
};} // namespace pal

在 RT-Thread 下实现这些接口时要注意 ISR-safe API(例如 rt_sem_release_from_isr);在 Linux 下用 pthreads 或 std::thread/std::mutex 实现。

6. EventQueue 的策略注入(Policy)与 AO 模型实现

利用 eventpp 的 Policy 注入机制,我们可以为不同平台定制底层锁、容器和优先级策略,例如把静态环形缓冲注入到 EventQueue。

示例 Policy 定义:

// MyPolicies.h
#include <eventpp/eventqueue.h>
// 假设已包含 StaticRing<Event> 和平台 PalMutexusing RtStaticPolicy = eventpp::EventQueuePolicy</*ContainerBuilder*/ eventpp::policy::VectorLikeBasedContainer<StaticRingWrapper>,/*Lock*/ PalMutex,/*PriorityPolicy*/ eventpp::DefaultPriorityPolicy
>;// 使用示例(伪代码,视具体 eventpp 版本接口而定)
using AoEventQueueRt = eventpp::EventQueue<Event, void(const Event&), RtStaticPolicy>;

Active Object 的实现伪代码如下(补充完整细节):

class ActiveObject {
public:ActiveObject(const char* name): running(true){threadHandle = pal::Thread::create(name, [this](){ run(); }, /*priority=*/10, /*stack=*/4096);}~ActiveObject() {running = false;eventSem.release(); // wake up to exitpal::Thread::join(threadHandle);}// 普通上下文发事件bool post(const Event &e) {if(eventQueue.enqueue(e)) {eventSem.release();return true;} else {++stats.dropped;return false;}}// ISR 中调用(必须使用 ISR-safe enqueue 与 notify)bool postFromIsr(const Event &e) {if(eventQueue.enqueueFromIsr(e)) { // 需要容器/策略支持isrFlag.store(true, std::memory_order_release);// 使用 ISR-safe 唤醒eventSem.releaseFromIsr();return true;} else {++stats.dropped;return false;}}private:void run() {while(running) {eventSem.acquire();// 处理直到队列为空或处理批量eventQueue.process(); }}AoEventQueueRt      eventQueue;pal::Semaphore      eventSem;std::atomic<bool>   isrFlag{false};std::atomic<bool>   running{true};pal::ThreadHandle   threadHandle;// 统计、状态机、回调列表等
};

要点:

  • eventQueue.enqueueFromIsr 与 Semaphore::releaseFromIsr 的可用性取决于具体 PAL 与容器实现
  • 在 RTOS/裸机路径,确保 ISR 中的操作为最小耗时且可中断安全
  • AO 的 run() 中应尽量避免长阻塞(除非这是设计意图),可在处理每个事件时记录处理时间用于 WCET 测量

7. ISR 与 AO 协作:从中断安全到唤醒机制

设计 AO+ISR 协作时的典型模式:

  1. 在 ISR 中构建或引用事件(尽量小),调用 ISR-safe enqueue(或写入环形缓冲直接内存写入)
  2. 在 ISR 中仅进行必要的唤醒(如给信号量/事件标志),避免调用复杂的调度逻辑
  3. AO 线程被唤醒后逐条或批量处理事件并执行较长/不安全的操作(比如动态内存、文件操作等)

注意事项:

  • 事件对象的内存管理:ISR 中最好只写入小而固定大小的数据或索引到对象池,避免在 ISR 中 new/delete
  • 优先级反转:如果 AO 与 ISR 之间有锁竞争,需防止优先级反转,使用 RTOS 提供的优先级继承或选择无锁方案
  • 批处理以减少上下文切换:在 AO 中 process() 可以一次处理 N 条事件,或者处理直到队列为空,平衡延迟与吞吐

8. 优先级、调度与避免饥饿(Priority Policy)

如果系统包含高/中/低优先级事件,需要在 EventQueue 层支持优先级:

常见方案:

  • 多队列(per-priority queue):高优先级队列先处理,低优先级队列后处理,可防止高频低优先任务饥饿低优先任务(通过令牌/轮询策略)
  • 单队列带优先排序:插入时用比较器排列,缺点是插入复杂度高且插队可能破坏 WCET 可测性
  • 混合:固定优先级数目的环形缓冲数组 + 限额处理策略(限制连续处理高优先事件的数量)

示例:多队列 + 轮询限额(伪代码)

void process() {int highCount = 0;while(true) {if(dequeueFromHighQueue(event)) {handle(event);++highCount;if(highCount >= HIGH_LIMIT) {// 让出一次机会处理中/低优先if(dequeueFromMidQueue(event)) { handle(event); }if(dequeueFromLowQueue(event)) { handle(event); }highCount = 0;}continue;}if(dequeueFromMidQueue(event)) { handle(event); continue; }if(dequeueFromLowQueue(event)) { handle(event); continue; }break;}
}

要点:

  • WCET:引入优先级后必须对最坏情况执行路径重新评估
  • 可测性优先:在硬实时场景下选择更可控(固定时间限制/批量上限)的策略

9. 部署示例:RT-Thread 与 ARM-Linux 的实现要点

RT-Thread 实现注意点:

  • 使用 rt_thread_create、rt_sem_take/release、rt_mutex_* 等替代 PAL 接口
  • ISR 中使用 rt_sem_release_fromISR(或 rt_sem_release + rt_hw_interrupt_mask/unmask)
  • 在 bsp 层做好堆栈、内存池的静态分配,避免动态分配(new/malloc)

ARM-Linux 实现注意点:

  • 使用 std::thread / pthreads / std::mutex / std::condition_variable 或者基于 epoll 的事件循环
  • 如果需要硬实时级别,可使用 PREEMPT_RT 或基于 rtprio 的实时进程来运行 AO 线程
  • 内存策略:在进程初始化时使用 malloc 大对象池,运行时避免再分配

两端共享代码实践:

  • 把核心 AO、eventpp 使用、状态机逻辑放入可编译在两端的库(仅依赖 STL 或做条件编译)
  • PAL 在不同平台实现不同文件,通过 cmake 或 makefile 在交叉编译时选择

示例目录结构(建议):

  • src/core/ (AO、事件、状态机、policy glue)
  • src/pal/rtthread/ (RT-Thread 的 PAL 实现)
  • src/pal/linux/ (Linux 的 PAL 实现)
  • examples/ (运行示例)
  • tests/ (单元与集成测试)

10. 示例架构图

时序图

ISRPALEventQueueAOCBpostFromIsr(e)enqueue ISR-safesignal / wakeupprocess()dispatch callbacksISRPALEventQueueAOCB

类关系

ActiveObject
+post(Event)
+postFromIsr(Event)
-run()
EventQueue
PalThread
PalMutex

优先级多队列示意

ISR Producers
high
mid
low
process
QH
ISR1
QM
ISR2
QL
ThreadProd
Active Object
Handler

11. 与其它框架的对比与权衡

  • QP/C++(付费)

    • 优点:成熟的 AO 框架、事件池、状态机支持、面向嵌入式的设计、硬实时适配能力强
    • 缺点:授权成本、学习曲线、集成与裁剪成本
  • eventpp + 自研 PAL + 对象池(本文方案)

    • 优点:零成本、极简、可裁剪、跨平台、可控内存分配
    • 缺点:需要自行完成对象池、状态机、严格的实时保障需要手工设计

选择要点:

  • 如果团队需要商用支持、成熟工具链与硬实时保障,QP/C++ 更合适
  • 如果希望快速上手、跨平台且避免授权成本,eventpp+自研方案更灵活

12. 总结与建议

本文给出一种基于 eventpp 的轻量级 AO 模式实践,适用于对可移植性与内存可控性有较高要求的嵌入式项目。关键建议:

  • 核心事件分发逻辑使用 eventpp,其 policy 注入能力让跨平台实现更简单
  • 在 RTOS/裸机路径优先使用静态/预分配结构,避免中断与任务中动态分配
  • 设计 PAL,隔离平台差异,保持核心逻辑可复用
  • 对优先级、饥饿与 WCET 做专门测试与测量,并在设计中加上容错策略(如丢弃策略、统计报警)
  • 对性能要求极高或强实时约束的场景,慎重评估是否需要更底层(内核级)支持或采用成熟商业框架

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/94161.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/94161.shtml
英文地址,请注明出处:http://en.pswp.cn/web/94161.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【python实用小脚本-193】Python全能PDF小助手:剪切/合并/旋转/加密一条龙——再也不用开会员

Python全能PDF小助手&#xff1a;剪切/合并/旋转/加密一条龙——再也不用开会员 PDF编辑, 本地处理, 零会员费, 多功能脚本, 瑞士军刀 故事开场&#xff1a;一把瑞士军刀救了周五下班的你 周五 17:55&#xff0c;老板甩来一堆 PDF&#xff1a; “把第 3、7 页删掉”“再和合同合…

Ubuntu根分区扩容

目录 1.先查看/dev/sda 整块磁盘设备的分区占用情况&#xff1a; 2.在VMware中编辑虚拟机&#xff1a; 3.进入虚拟机&#xff0c;进入disk应用程序&#xff1a; 4.扩容文件系统 5.最后通过df-h lsblk或通过可视化GParted进行验证。 1.先查看/dev/sda 整块磁盘设备的分区占…

智慧城市SaaS平台/市政设施运行监测系统之空气质量监测系统、VOC气体监测系统、污水水质监测系统及环卫车辆定位调度系统架构内容

1. 空气质量监测系统1) 监测点管理 a) 监测点基本信息 支持记录空气质量监测点的名称、位置、类型、设备配置等信息。 b) 监测点分布地图 支持通过GIS地图展示监测点的分布情况&#xff0c;支持地图查询和导航。 2) 空气质量监测 a) 实时数据采集 支持实时采集空气质量数据&…

PiscCode迅速集成YOLO-Pose 实现姿态关键点轨迹跟踪应用

在计算机视觉领域&#xff0c;人体姿态检测与轨迹跟踪是很多应用场景的核心技术&#xff0c;例如运动分析、行为识别、智能监控等。本文将介绍如何在 PiscCode 平台上&#xff0c;利用 YOLO-Pose 模型进行姿态估计&#xff0c;并实现多人关键点轨迹跟踪。 一、什么是 PiscCode …

HTTP的状态码有哪些,并用例子说明一下

问题HTTP的状态码有哪些&#xff0c;并用例子说明一下我的回答HTTP状态码是服务器对客户端请求的响应码&#xff0c;它们按照不同的功能被分为五大类。我来介绍一下主要的状态码及其实际应用场景&#xff1a;1xx&#xff08;信息性状态码&#xff09;&#xff1a;表示请求已接收…

【51单片机】【protues仿真】基于51单片机宠物投食器系统

目录 一、主要功能 二、使用步骤 三、硬件资源 四、软件设计 五、实验现象 一、主要功能 1、LCD1602液晶显示当前时间 2、按键设置时间&#xff0c;5个定时投喂时间​ 3、可以通过手动按键进行投喂食物 4、步进电机模拟投喂食物 二、使用步骤 基于51单片机的宠物自动投…

掌握设计模式--命令模式

命令模式&#xff08;Command Pattern&#xff09; 命令模式&#xff08;Command Pattern&#xff09;是一种行为型设计模式&#xff0c;它将请求&#xff08;命令&#xff09;封装成对象&#xff0c;从而使您能够参数化客户端&#xff08;调用者&#xff09;使用不同的请求、…

STM32之beep、多文件、延迟、按键以及呼吸灯

一、Beep控制 原理图分析&#xff1a; 蜂鸣器三极管控制引脚对应 MCU PB8。当前蜂鸣器对应的电路中&#xff0c;三极管是 NPN 三极管&#xff0c;当前【基极】存在小电流&#xff0c;当前三极管导通。要求对应 PB8 引脚对外输出电压 / 电流。当前 PB8 输出高电平&#xff0c;当…

C++的struct里面可以放函数,讨论一下C++和C关于struct的使用区别

我们来看一个C代码下面的struct结构体: struct UserValue {float lx;float ly;float rx;float ry;float L2;// 【构造函数】UserValue() {setZero();}// 【成员函数】void setZero() {lx 0;ly 0;rx 0;ry 0;L2 0;} };在这篇文章中&#xff0c;我们将来详细解释一下为什么 U…

【Kubernetes知识点】资源配额与访问控制

目录 1.解释ResourceQuota的作用。 2.解释Service Account的用途。 3.详细解释Role和ClusterRole。 4.什么是K8s的NetworkPolicy&#xff1f; 5.详细描述在K8s中如何控制跨Namespace的Pod访问&#xff1f; 1.解释ResourceQuota的作用。 ResourceQuota&#xff08;资源配额…

在SAP Query中添加双击事件

在SAP系统中&#xff0c;SAP Query是一个强大的工具&#xff0c;允许用户自定义报告以满足特定的数据查询需求。它提供了灵活的报表设计功能&#xff0c;使非编程背景的用户也能创建和修改查询。在某些情况下&#xff0c;我们可能希望在查询结果上添加交互性&#xff0c;比如通…

c++:MFC中sqlite3的使用(附实际案例)

MFC中sqlite3的使用sqlite3介绍sqlite3安装常用API函数操作流程接口函数执行sql语句函数回调函数MFC中案例实践控制台实践sqlite3介绍 SQLite 是一个软件库&#xff0c;实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。SQLite 是在世界上最广泛部署的 SQL …

LeetCode第1019题 - 链表中的下一个更大节点

题目 解答 class Solution {Stack<Integer> stack new Stack<>();List<Integer> values new LinkedList<>();public int[] nextLargerNodes(ListNode head) {nextLargerNodes2(head);return values.stream().mapToInt(x -> x).toArray();}publi…

STM32 硬件I2C读写MPU6050

本文代码基于 STM32 单片机&#xff0c;通过 I2C 总线驱动 MPU6050 六轴传感器&#xff08;集成加速度计与陀螺仪&#xff09;&#xff0c;实现传感器初始化、ID 读取、原始数据采集&#xff0c;并借助 OLED 显示屏实时展示加速度&#xff08;AccX、AccY、AccZ&#xff09;与角…

倍福下的EC-A10020-P2-24电机调试说明

今天调试EC-A10020-P2-24电机&#xff0c;采用力位混合控制指令进行控制&#xff0c;无前馈力矩&#xff0c;只调节Kp和Kd,跟踪红色轨迹&#xff08;正弦信号&#xff1a;幅值10&#xff0c;频率0.5Hz&#xff09;&#xff0c;结果显示Kp 180, Kd 40&#xff0c;实际上Kp进一步…

SQL注入1----(sql注入原理)

一.前言前面我们讲解了一下信息收集&#xff0c;本章节我们来讲解一下sql注入的基本原理&#xff0c;我们拿之前搭建的测试网站pikachu来测试&#xff0c;对应工具包也已经放在了工具里面&#xff0c;大家可以自行去下载。SQL注入攻击漏洞的原因&#xff0c;是由于程序员在编写…

C++智能指针详解:用法与实践指南

C智能指针详解&#xff1a;用法与实践指南 在C编程中&#xff0c;动态内存管理始终是开发者面临的重要挑战。手动分配和释放内存不仅繁琐&#xff0c;还容易因疏忽导致内存泄漏、悬垂指针等问题。为解决这些痛点&#xff0c;C标准库引入了智能指针&#xff08;Smart Pointers&a…

fastdds qos:DurabilityQosPolicy

假如DataWriter先起来&#xff0c;并且已经写了一些数据&#xff0c;之后有新的DataReader起来&#xff0c;那么新起来的DataReader能不能接收到它启动之前&#xff0c;DataWriter发布的数据呢。DurabilityQosPolicy用来做这种控制。VOLATILE_DURABILITY_QOS&#xff1a;易失的…

【读代码】SQLBot:开源自然语言转SQL智能助手原理与实践

一、项目简介 SQLBot 是 DataEase 团队开源的自然语言转 SQL 智能助手,致力于让非技术用户也能通过自然语言与数据库对话,自动生成 SQL 查询,实现自助数据分析、智能BI问答、报表生成等场景。SQLBot 结合了大语言模型(LLM)、数据库元数据解析、SQL解析与执行等多项技术,…

开题报告被退回?用《基于大数据的慢性肾病数据可视化分析系统》的Hadoop技术,一次通过不是梦

&#x1f496;&#x1f496;作者&#xff1a;计算机编程小咖 &#x1f499;&#x1f499;个人简介&#xff1a;曾长期从事计算机专业培训教学&#xff0c;本人也热爱上课教学&#xff0c;语言擅长Java、微信小程序、Python、Golang、安卓Android等&#xff0c;开发项目包括大数…