本节摘要:嵌入式协议栈的内存账与桌面软件完全不同:没有堆或堆极小、不能 malloc 后不 free、缓冲区要提前规划。本节讲静态分配原则、缓冲区生命周期与 LoRaMac-node 的内存模型。位置:5.1 定了调度骨架,本节给骨架装上内存。
三个理由:确定性——堆碎片导致"跑一段时间后莫名失败",静态分配没有碎片问题;可审计——内存占用编译期确定,选型时就知道够不够;安全——嵌入式堆的越界与泄漏难以定位,静态数组把问题范围锁死。
LoRaMac-node 的典型内存模型:协议栈上下文(设备状态、计数器、密钥)是一个全局静态结构体;收发缓冲区是预分配的固定数组(如 256 字节收、256 字节发);MAC 命令列表、多播列表用固定容量数组。整个协议栈运行期零堆分配。
射频收发与上层处理共用缓冲区,必须约定清晰的"归属转移":
/* 伪代码:缓冲区归属转移(单一所有者原则) */ static uint8_t tx_buf[256]; static uint8_t rx_buf[256]; void on_tx_done(void) { /* 归属:射频已释放,交还协议栈 */ tx_buf_owner = OWNER_STACK; /* 上层可复用 tx_buf */ } void on_rx_ready(void) { /* 归属:射频已写入 rx_buf,交给上层解析 */ rx_buf_owner = OWNER_APP; app_process_payload(rx_buf, rx_len); rx_buf_owner = OWNER_STACK; /* 处理完毕交回 */ }
三条纪律:同一时刻只有一个所有者;处理完立即交还;绝不跨事件持有缓冲区(异步回调里用完后必须释放,否则下一次收发互相覆盖)。
| 项目 | 典型占用 | 说明 |
|---|---|---|
| 协议栈状态(上下文) | 约 200–400B | 密钥、计数、状态机字段 |
| 收发缓冲区 | 256×2 = 512B | 按最大帧长预留 |
| 射频驱动 | 约 500B–1KB | 寄存器缓存与临时量 |
| 加密运算临时区 | 约 200B | AES 状态块 |
| 应用业务区 | 按需 | 传感器数据、队列 |
32KB RAM 的 MCU 跑 LoRaWAN + 简单应用绰绰有余;8KB RAM 需要精简(缓冲 128×2、裁剪打印)。选型先算这笔表,比开发到一半发现内存不够再换芯片划算得多。
静态内存设计的正确性要靠审计而不是运气。三步法:
第一步,编译期统计:链接器输出(map 文件)看 .bss/.data 段大小,确认全局静态量符合预期;第二步,运行期峰值:用调试器在关键路径(收发、入网、命令处理)设断点,查看栈指针最低值,算出栈峰值;第三步,压力测试:连续跑几千次收发 + 入网 + 命令处理,配合看门狗与内存填充(0xAA/0x55 交替)检测越界写。
map 文件关键段(32KB RAM 的 MCU 示例): .bss 6400B (全局静态量:协议栈上下文 + 缓冲 + 驱动) .data 320B (初始化数据) 栈 2048B (最大任务栈) 剩余 ≈ 24KB (应用可用)
越界写是嵌入式最难查的 bug:可能几小时后才在无关变量上爆雷。预防手段:缓冲区边界填充哨兵值、调试构建开 stack 检查、每次收发后断言缓冲归属状态机一致性。
射频芯片的收发常走 DMA(直接内存访问),DMA 与 CPU 共享缓冲区时有竞争窗口:DMA 正在写缓冲时 CPU 读,读到半包。处理原则:DMA 完成中断作为"缓冲可用"的唯一信号,CPU 在 DMA 完成前不碰缓冲;换用双缓冲(乒乓结构)时,CPU 处理"旧缓冲"的同时 DMA 写"新缓冲",前提是两片缓冲都静态分配且大小按最大帧预留。
要点回顾:零堆分配 + 单一所有者 + 按最大帧预留,是嵌入式协议栈内存的三条铁律;先算内存预算再选芯片。下一节把电流剖面摊开,算"十年电池"到底怎么来。
收发缓冲区"按最大帧预留"不是拍脑袋,推导路径:净荷上限(区域参数表给出,EU868 SF12 上行 51B、SF7 可到 222B)→ 加上 MAC 头与 MIC(+13B 左右,3.1 的字节账)→ 得到最大帧长 → 按最大帧长 × 2(收/发各一)+ 少量余量。US915 的 SF10 跳频净荷上限只有 11B,缓冲区按它预留反而浪费——缓冲区大小应随区域参数表联动,而不是全代码写死 256。
最大帧长推导示例(EU868 上行): 净荷上限 222B(SF7)+ 帧头 13B = 235B → 收发缓冲各 256B 留余量 → 共 512B (若只跑 SF10 跳频,净荷上限 11B,缓冲 64B×2 就够)
人工审计 map 文件不可持续,成熟项目把内存审计做成 CI 步骤:链接后解析 map 文件,断言 .bss/.data 总和不超过预算线;压力测试用例统计栈峰值并断言不超过任务栈;越界检测用编译器的栈保护(-fstack-protector)与调试期的缓冲哨兵。内存问题要能在提交时被发现,而不是等量产现场。
小结与速查:零堆分配 + 单一所有者 + 按最大帧预留,三条铁律;缓冲区 DMA 与 CPU 用完成中断同步;内存审计三步:map 文件统计、运行期栈峰值、压力测试;越界写用哨兵与断言预防;先算内存预算再选芯片。