跳到主要内容

设计思想

嵌入式系统中,运行时的内存分配是设计缺陷

在嵌入式系统中,运行时的内存分配应被视为设计缺陷。系统的所有资源应尽可能在构造或初始化阶段就完成分配和配置。这不仅有助于提高系统的可预测性和稳定性,也更容易进行内存使用分析和资源规划。

需要注意的是,这一原则并不意味着禁止使用动态内存分配(如 mallocnew),而是强调动态内存分配的时间点必须在系统的初始化阶段完成。在系统进入主循环或开始任务调度之后,不应再进行新的内存分配操作。否则将引入:

  • 未知的延迟(如碎片整理等)
  • 难以追踪的内存泄漏或越界错误
  • 潜在的内存分配失败

换句话说,“动态分配可以用,但必须在静态时机使用”。在这种前提下,你甚至可以用一个只出不进的栈来实现内存分配,并非常方便的监视内存使用情况。

一切回调/中断都必须是无阻塞的

回调是跨上下文的执行点,无论来自中断、调度器还是线程之间的异步触发,其职责都应当极度克制。回调的设计目的不是“处理”,而是“转交”。

在 LibXR 中,所有回调仅允许执行以下两类操作:

  1. 通知线程:设置状态、释放信号量、发布事件;
  2. 推送数据:写入无锁队列、缓存结构等预分配数据区。

除此之外的行为一律禁止:

  • 阻塞等待(如信号量、锁、睡眠)
  • 业务逻辑处理或调用上层接口
  • 动态内存分配
  • 调用任何不确定时延的函数

上下文(thread/isr)必须在回调中显式传递

  • 所有回调统一传入 bool in_isr 参数
  • 操作系统封装 API 明确分为 Api() / ApiFromCallback(in_isr)
  • 用户无需判断当前上下文,只需将 in_isr 向上传递
  • 保证所有回调、任务切换、信号交互在 ISR / Thread 两种模式下都能安全运行
  • 永远不需要猜当前是不是在中断运行

任何 I/O 操作都必须绑定确定的完成行为

I/O 操作的重点不是“发出请求”,而是如何得知它完成了,以及之后如何处理结果

LibXR 通过 Operation 类型对 I/O 行为进行建模,在操作发起时就绑定好完成后的响应机制:

行为模式类型描述
回调CALLBACK操作完成后自动触发回调函数
阻塞BLOCK当前任务等待,直到操作完成或超时
轮询POLLING用户主动检查操作是否完成
忽略NONE发起即忘,不等待任何反馈
WriteOperation op_cb(callback);     // 异步回调
WriteOperation op_block(sem, 100); // 阻塞等待
WriteOperation op_poll(status); // 轮询状态
WriteOperation op_none; // 忽略结果

uart1.Write("Hello, world!", op_block);

一个没有绑定完成行为的操作,是不可控的。

接口中不应出现任何平台相关类型

使用者不应在任何接口中看到平台相关的结构体或依赖。例如:

  • STM32 的 UART_HandleTypeDef*
  • ESP-IDF 的 uart_port_t
  • Linux 的 termios
  • FreeRTOS 的 TaskHandle_tSemaphoreHandle_t

这类类型只能存在于平台实现内部,绝不应出现在接口声明、参数或返回值中。

接口必须保持平台无关性,以确保:

  • 不同平台间可替换
  • 行为可测试、可模拟
  • 未使用平台代码可裁剪

若平台类型出现在接口中,则该接口不能复用、不可维护,且会导致上层逻辑依赖具体实现。