# `mem-budget` — RAM 总量红线(`RW_IRAM1` / `RW_CCM` 超 92% 即拦) > 一句话:它是**总量刹车**,不是预算规划器。它只管两件事:任一静态区域别超 92%,以及 > 台账里的水位数字别和 `.map` 漂移。 - 配置:`entry: python scripts/ram_baseline.py --gate`、`pass_filenames: false`、 `files:` 覆盖 `App/`、`Drivers/BSP/`、`Src/`、`Inc/`、`Lib/lv_conf.h` 等会影响占用面。 - 背景:登记簿条目「缩堆迁 CCM 后半(暂缓)」的前半;三件套里的机械部分(阈值门禁 + 趋势台账 + 台账受管区)。 ## 1. 它在查什么 1. 解析 `.map`,算 `RW_IRAM1`、`RW_CCM` 的占用率;**任一 > 92.0% → FAIL**,并附 Top-10 `RW+ZI` 对象表(直接告诉你是谁吃掉的); 2. 顺带核对 `docs/内存管理/RAM预算台账.md` 的受管区(`MEM_BUDGET:BEGIN/END`)是否与 `.map` 一致——防台账漂移,与 `mem-numbers` 同一思路; 3. `.map` 不存在 → 自适应跳过(这台机器没做 Keil 构建)。 配套的两个入口(**不在提交门禁里**,属于日常): - `--update`:每次 `@build` 后往 `scripts/ram_baseline.tsv` 追一行趋势; - `--trend`:打印趋势并**高亮 `RW+ZI` 较上一行增长 > 1 KB 的对象**——把"ZI 增量超 1 KB 必须解释" 这条纪律机械化; - `--write`:同步台账受管区(区域水位 + 子系统配额对照)。 子系统配额(规划值,改配额要先在台账变更记录里登记,再改脚本 `QUOTAS`): 网络 12 KB / 存储 12 KB / UI 16 KB / RTOS 堆 56 KB / 应用 8 KB。 ## 2. 为什么要它 - 本仓 RAM 是真红线:`RW_CCM` 常年 **87.9%**(FreeRTOS 堆迁 CCM 之后),离 92% 只差几个百分点; 链接期才发现塞不下,代价是回退一大片改动。 - 实跑当前值:`RW_IRAM1 57.4%` / `RW_CCM 87.9%`(阈值 92%)。 - 趋势台账让"谁什么时候吃掉了这 3 KB"可查,不必靠记忆。 ## 3. 触发面 / 看不见什么 - 只看 `RW_IRAM1` 与 `RW_CCM` 两个**静态区域**;不看:栈运行时水位、FreeRTOS 堆运行时余量、 堆碎片、MicroLIB 的 512 B C 堆(那条纪律是"自研代码禁用 `malloc/free`",**2026-10-23 起由 `coding-standard` 的 CODE-RULE-20 机器拦截**(见 `coding-standard.md` §1/§7),不再只靠评审)。 - `.map` 缺失即跳过 → 无构建的机器(含远端 CI)这一门不产生信息。 ## 4. 报错怎么读 ``` RAM 预算门禁 FAIL:RW_CCM 占用 92.3% > 92%(阈值 92%) Top-10 RW+ZI 对象 … ``` - 单独跑:`& '.venv\Scripts\python.exe' scripts/ram_baseline.py --gate`(只读); `--trend -n 12` 看趋势。**`--gate` 不修改任何东西**,要同步台账用 `--write`。 - 报 FAIL 的正确动作是**回去减**(缩缓冲/挪区段/减日志调用点),不是抬阈值。 - 两类**软提醒不拦提交**:`tsv` 末行落后于 `.map`(忘了跑 `--update`)、`.map` 早于最新源码(忘了 `@build`)。 - 退出码:0 = 通过或跳过 / 1 = 超阈值或台账漂移 / 2 = **解析失败**(`.map` 读不出结构,不等于"没问题")。 - `.map` 解析口径复用 `scripts/ram_report.py`,两个脚本不会各说各话。 - 为什么需要(背景):2026-09 体系评审提出的"超阈值即失败 + Top-10 附表"此前**没有任何机械检查** ——`mem-numbers` 只核数字对不对,不判是否超限。 ## 5. 好处 - 总量红线机械化,红线以下谁都能放心加东西; - Top-10 归属表把"超了"变成"某某对象吃了多少",省掉一次手工 .map 考古; - 趋势 + >1 KB 高亮让"增量必须有解释"变成可查记录,而不是口头承诺。 ## 6. 限制与盲区 1. **92% 是拍的管理阈值,不是安全余量计算**(脚本里 `THRESHOLD_PCT = 92.0`)。它不回答 "还剩多少才够下一个任务/缓冲"——那是 `docs/内存管理/RAM预算台账.md` 的配额表与人的事。 2. **静态视角**:栈峰值、堆低水位、碎片一概看不见(运行时水位要靠串口 `[STACK]` 与 `stack_report.py`)。 3. 只看两个区:Flash(`ER_IROM1`)不在本门阈值内,它由 `mem-numbers` 报数、无人卡阈值。 4. `.map` 缺失即"通过"式跳过 → **在没有 Keil 构建的环境里绿,不代表有余量**。**远端缺口已于 2026-10-25 关闭**:`scripts/check_size_gate.py --ci` 用同码凭证按**同一常数**复核红线(runner 无需 Keil),`--gate` 无 `.map` 的 SKIP 文案也已改为明说「SKIP 不等于 PASS」;口径正文 = 该脚本 docstring。 5. 配额对照是"目标态 vs 现状",现状超配(例如 UI)会被如实展示但**不拦提交**——它是台账,不是红线。 6. 阈值/配额改动没有双人复核机制,改 `QUOTAS` 或 `THRESHOLD_PCT` 只靠提交说明约束。 7. 并行分支约定(Lead 裁决 2026-10-12):**分支不跑 `--write`**,台账由主干统一回填;分支上 本门与 `mem-numbers` 可能因受管快照与 `.map` 漂移而红,那不代表预算超标(处置见 §7 的 `mem-numbers` 篇)。 8. **同码凭证在「多人同树」下必然不成立**(2026-10-27 实测):`check_size_gate.py --local` 的代码指纹取 **git 索引里的 blob**,所以「工作区有别人在飞、尚未入索引的改动」时指纹一定对不上,报「这份源码没有被 真实链接测量过」——**这不是预算问题,也不拦提交**(该脚本只挂在 `scripts/ci_local.py` 的 L0c,不在 51 条 pre-commit 钩子内)。判据:报错里若同时列出「未入索引的文件」且那些文件正被别的任务改,就等它们入库后 统一 `@build` → `ram_baseline.py --update` 补凭证,**别为了把它调绿去 `git add` 别人在飞的改动**。本条写作时 (2026-10-27 09:4x)凭证**仍是红的**:`App/protocol/iec104/iec104_comm.{c,h}` 正被通讯道改、未入索引; 回填动作发生在该卡入库之后,届时以 `--update` 的实际输出为准,本节不预告结果。 ## 7. 豁免与基线 - 没有基线、没有 `--no-verify` 之外的合法豁免:**超了就减**。 - `docs/内存管理/RAM预算台账.md` 的受管区是事实展示区,不是豁免登记表;别在那里"解释一下"就算过关。 ## 8. 改这个门禁本身 - 抬阈值前必须在台账里写清"为什么 92% 变松了",否则等于把刹车线往后画; - 新增区域(例如再开一个自定义段)要同时改解析器与台账口径,否则新区域完全不在监控内; - `--update` 的 tsv 是趋势事实源,改列格式会让 `--trend` 的历史对比失效。 ## 9. 变更记录 | 日期 | 变更 | |---|---| | 2026-10-27 | §6 补第 8 条:同码凭证按 **git 索引 blob** 生成,工作区有在飞改动时 `--local` 必红;该检查只在 `ci_local` 的 L0c,**不是 pre-commit 钩子**,不拦提交(成因由道G 报出、Lead 复核) | | 2026-10-23 | 首篇逐项文档(实跑:`RW_IRAM1 57.4%` / `RW_CCM 87.9%`,阈值 92%) |