fix(lftpd): 对端行为不再打 [ERR]——新增 lftpd_peer 级别并划清级别边界

现象:抓自检台账基线那次 FTP 会话一结束,设备就打 `[ERR] [FTP] error reading next command`,
本次 `@test` 直接 FAIL(Forbidden 含 `[ERR]`)。查下去发现 lftpd 把**全部**异常路径一律按
error 级打,其中九处其实由外部输入决定:客户端关窗口、连上就走、PASV 后不建数据连接、
运维改 IP 触发监听重建。两条代价:① 任何人"连 21 端口一下就断开"就能把一次启动测试打成
FAIL;② 同样的动作可以**无限制造固件级 ERROR 日志**——日志面与门禁面都可被外部驱动,
工业现场过不了审。

**做法**:`private/lftpd_log.h` 新增 `lftpd_log_peer()`(info 级,带 `[FTP]` 前缀),而不是
就地改成 `DBG_INFO`——让"谁才算真故障"在读代码时一目了然,也防止后来者顺手把对端行为升回
error。降级的九处:控制通道被对端关闭(原 `error reading next command`,改为
`control channel closed by peer (ret=%d)`,措辞原先把正常收尾写成故障、误导过排查方向)、
发送 220 前对端已走、`listener rebuilt`(网络重建,原因由 accept 层自己记)、四处
`data accept failed`(PASV 后没来数据连接)、`accept timeout`。

**刻意留在 error 的**:create/bind/listen/取端口失败、写通道失败、文件打开失败、
`DOWNLOAD/UPLOAD FAILED`(对端中途退出确实让一次传输没走完 = 数据完整性事件,
`../问题记录/CH395F_Trap_Records.md` 的中止家族要靠这些锚点定位)、
`data listener disarmed`(控制连接还活着而我方数据监听已不在服役 = 本侧异常,就地注明)。

**实测划清的边界**:客户端**干净收尾**(发 `QUIT` 或 FIN 半关)现在整条链路没有任何 `[ERR]`;
但 **RST 硬拆**(`SO_LINGER` 清零 / 进程被杀 / 拔线)仍会留一条 `[ERR][NET] sockN TIMEOUT`——
那是 net 层的套接字超时锚点,网络自愈架构判据要读它,不在本次降级范围。所以"采集窗口内别跑
`data listener disarmed`(控制连接还活着而我方数据监听已不在服役 = 本侧异常,就地注明)。

**真机验证**:`@build` 0 错 0 警告;`@test --require-selftest-pass --device-ip` →
  **PASS**
(基线 29 条 → 本次无新增;`net init: OK` 726 ms);专门写了 RST 复现脚本(登录后 `SO_LINGER`
清零直接关)→ 串口得到 `[FTP] control channel closed by peer (ret=-1)`,**不再是 `[ERR]` 行**;
同一份日志里 QUIT 收尾的那次会话完全没有错误行。

体量:`Code` 213,236 → **213,264 B(+28)**、`RO` 18,580 → **18,652 B(+72)**(措辞变长),
同一份日志里 QUIT 收尾的那次会话完全没有错误行。
`ZI` 131,500、`RW_IRAM1` 75,264 B 57.4% / `RW_CCM` 57,376 B 87.9% 全不变)。数字联动照做:
两份 `data/` 快照、`ram_baseline --update/--write`、`check_mem_numbers --write`、FLASH
  配图
常量重算重生成(桶合计 = 231,916 与 `Code+RO` 相符;越界复查 0)、报告 §1.3/§5.1/§5.4 与
部署文档 §3.3 同步。

口径落文:新增 `docs/架构设计/STM32F4-Base存储架构说明.md` §10.3「FTP 日志级别划分:对端行为
vs 固件故障」(含两类的判据表与边界),并更新 `docs/测试说明/自动化测试流程.md` 的采集窗口警示。
顺手把 `private/lftpd_log.h` 工作区的 CRLF 混用归一回 LF(仓库里 blob 本来就是 LF,diff 无影响)。

门禁:`doc-links --all --sections-strict` 146 文档 0 问题、`id-check --strict` 增量拦截通过
(可判定 82.82%)、`mem-numbers` / `mem-budget` / `layer-deps` 全绿;改动文件字节卫生 BOM 无 /
CR 0 / U+FFFD 0 / 无行尾空格。未推送。
This commit is contained in:
2026-10-22 22:31:09 +08:00
parent af5bad5609
commit 4c21cff753
14 changed files with 118 additions and 63 deletions
+21 -21
View File
@@ -9,33 +9,33 @@
</defs>
<text x="410.0" y="24" text-anchor="middle" class="title">Flash (1MB) 使用布局 — 2026-10-22 @build</text>
<rect class="bar-seg" x="60" y="50" width="150.5" height="40" fill="#2196F3" rx="2"/>
<text x="135.2" y="70.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">代码段 (Code) 208.2KB</text>
<rect class="bar-seg" x="210.4846954345703" y="50" width="13.1" height="40" fill="#4CAF50" rx="2"/>
<text x="227.6" y="70.0" text-anchor="start" dominant-baseline="central" class="legend-text" fill="#333">只读数据 (RO) 18.1KB</text>
<rect class="bar-seg" x="223.59695434570312" y="50" width="575.6" height="40" fill="#E0E0E0" rx="2"/>
<text x="511.4" y="70.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">Flash 空闲 796.5KB</text>
<text x="60" y="108" class="label" fill="#333">已用: 227.5KB (22.2%) 空闲: 796.5KB (77.8%) ER_IROM1(Code+RO): 226.4KB (22.1%) 总量: 1MB (= ROM 合计口径)</text>
<text x="60" y="124" class="label" fill="#555">Code + RO 分布(ER_IROM1 226.4KB,按 .map 对象归类)</text>
<rect class="bar-seg" x="60" y="130" width="280.0" height="24" fill="#4CAF50" rx="2"/>
<text x="200.0" y="142.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">85.7KB</text>
<rect class="bar-seg" x="339.9739448528143" y="130" width="182.0" height="24" fill="#2196F3" rx="2"/>
<text x="431.0" y="142.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">55.7KB</text>
<rect class="bar-seg" x="521.963712599648" y="130" width="81.9" height="24" fill="#F44336" rx="2"/>
<text x="562.9" y="142.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">25.0KB</text>
<rect class="bar-seg" x="603.8241536390931" y="130" width="57.1" height="24" fill="#607D8B" rx="2"/>
<text x="135.3" y="70.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">代码段 (Code) 208.3KB</text>
<rect class="bar-seg" x="210.50445556640625" y="50" width="13.2" height="40" fill="#4CAF50" rx="2"/>
<text x="227.7" y="70.0" text-anchor="start" dominant-baseline="central" class="legend-text" fill="#333">只读数据 (RO) 18.2KB</text>
<rect class="bar-seg" x="223.6675262451172" y="50" width="575.5" height="40" fill="#E0E0E0" rx="2"/>
<text x="511.4" y="70.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">Flash 空闲 796.4KB</text>
<text x="60" y="108" class="label" fill="#333">已用: 227.6KB (22.2%) 空闲: 796.4KB (77.8%) ER_IROM1(Code+RO): 226.5KB (22.1%) 总量: 1MB (= ROM 合计口径)</text>
<text x="60" y="124" class="label" fill="#555">Code + RO 分布(ER_IROM1 226.5KB,按 .map 对象归类)</text>
<rect class="bar-seg" x="60" y="130" width="279.9" height="24" fill="#4CAF50" rx="2"/>
<text x="199.9" y="142.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">85.7KB</text>
<rect class="bar-seg" x="339.8532227185705" y="130" width="181.9" height="24" fill="#2196F3" rx="2"/>
<text x="430.8" y="142.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">55.7KB</text>
<rect class="bar-seg" x="521.7645181876196" y="130" width="82.1" height="24" fill="#F44336" rx="2"/>
<text x="562.8" y="142.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">25.1KB</text>
<rect class="bar-seg" x="603.9087428206764" y="130" width="57.0" height="24" fill="#607D8B" rx="2"/>
<text x="632.4" y="142.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">17.5KB</text>
<rect class="bar-seg" x="660.8876867860718" y="130" width="34.9" height="24" fill="#FF9800" rx="2"/>
<rect class="bar-seg" x="695.7814818649274" y="130" width="34.4" height="24" fill="#00BCD4" rx="2"/>
<rect class="bar-seg" x="730.1357973565242" y="130" width="27.0" height="24" fill="#9C27B0" rx="2"/>
<rect class="bar-seg" x="757.1672360837907" y="130" width="17.5" height="24" fill="#795548" rx="2"/>
<rect class="bar-seg" x="774.6795734548091" y="130" width="1.5" height="24" fill="#B0BEC5" rx="2"/>
<rect class="bar-seg" x="776.1735169272182" y="130" width="23.8" height="24" fill="#E0E0E0" rx="2"/>
<rect class="bar-seg" x="660.9476707083599" y="130" width="34.9" height="24" fill="#FF9800" rx="2"/>
<rect class="bar-seg" x="695.8264199106573" y="130" width="34.3" height="24" fill="#00BCD4" rx="2"/>
<rect class="bar-seg" x="730.1659221442246" y="130" width="27.0" height="24" fill="#9C27B0" rx="2"/>
<rect class="bar-seg" x="757.1857051691129" y="130" width="17.5" height="24" fill="#795548" rx="2"/>
<rect class="bar-seg" x="774.6904913848117" y="130" width="1.5" height="24" fill="#B0BEC5" rx="2"/>
<rect class="bar-seg" x="776.1837906828334" y="130" width="23.8" height="24" fill="#E0E0E0" rx="2"/>
<rect x="60" y="167" width="10" height="10" fill="#4CAF50" rx="2"/>
<text x="74" y="176" class="legend-text" fill="#333">LVGL 库 85.7KB</text>
<rect x="255" y="167" width="10" height="10" fill="#2196F3" rx="2"/>
<text x="269" y="176" class="legend-text" fill="#333">应用代码/UI 55.7KB</text>
<rect x="450" y="167" width="10" height="10" fill="#F44336" rx="2"/>
<text x="464" y="176" class="legend-text" fill="#333">CH395F+FTP 25.0KB</text>
<text x="464" y="176" class="legend-text" fill="#333">CH395F+FTP 25.1KB</text>
<rect x="645" y="167" width="10" height="10" fill="#607D8B" rx="2"/>
<text x="659" y="176" class="legend-text" fill="#333">HAL 驱动 17.5KB</text>
<rect x="60" y="183" width="10" height="10" fill="#FF9800" rx="2"/>

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

@@ -38,10 +38,10 @@
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
| 区域 | 用量 | 容量 | 占比 |
|---|---|---|---|
| ER_IROM1 | 231,816 B (226.38 KB) | 1,048,576 B (1024 KB) | 22.1% |
| ER_IROM1 | 231,916 B (226.48 KB) | 1,048,576 B (1024 KB) | 22.1% |
| RW_IRAM1 | 75,264 B (73.50 KB) | 131,072 B (128 KB) | 57.4% |
| RW_CCM | 57,376 B (56.03 KB) | 65,280 B (64 KB) | 87.9% |
| **Flash (ROM 合计)** | **233,004 B (227.5 KB)** | 1,048,576 B (1024 KB) | **22.2%** |
| **Flash (ROM 合计)** | **233,104 B (227.6 KB)** | 1,048,576 B (1024 KB) | **22.2%** |
<!-- MEM_SNAPSHOT:END -->
各区的容量与常量来源:
+1 -1
View File
@@ -15,7 +15,7 @@
<!-- MEM_BUDGET:BEGIN generated by scripts/ram_baseline.py -->
| 区域 | 用量 | 容量 | 占比 | 门禁(≤92%) |
|---|---|---|---|---|
| ER_IROM1 (Flash) | 231,816 B | 1,048,576 B | 22.1% | 参考 |
| ER_IROM1 (Flash) | 231,916 B | 1,048,576 B | 22.1% | 参考 |
| RW_CCM | 57,376 B | 65,280 B | 87.9% | PASS |
| UNINIT_CCM | 48 B | 256 B | 18.8% | 参考 |
| RW_IRAM1 | 75,264 B | 131,072 B | 57.4% | PASS |
+11 -11
View File
@@ -1,24 +1,24 @@
# STM32F4-Base RAM/Flash Usage Report
> Generated: 2026-10-22 19:56 Map: `STM32F407-Demo.map`
> Generated: 2026-10-22 22:26 Map: `STM32F407-Demo.map`
## 1. Grand Totals (全局总量)
| Metric | Value |
|---|---|
| Code (inc. data) | 213,236 B (208.2 KB) |
| RO Data | 18,580 B (18.1 KB) |
| Code (inc. data) | 213,264 B (208.3 KB) |
| RO Data | 18,652 B (18.2 KB) |
| RW Data (initialized) | 1,188 B (1.16 KB) |
| ZI Data (zero-init) | 131,500 B (128.4 KB) |
| **Total RO (Code + RO)** | **231,816 B (226.4 KB)** |
| **Total RO (Code + RO)** | **231,916 B (226.5 KB)** |
| **Total RW (RW + ZI) = SRAM used** | **132,688 B (129.58 KB)** |
| **Total ROM (Code + RO + RW init)** | **233,004 B (227.5 KB)** |
| **Total ROM (Code + RO + RW init)** | **233,104 B (227.6 KB)** |
## 2. 执行区水位 (Execution Regions)
| Region | Base | Size | Max | Util% |
|---|---|---|---|---|
| ER_IROM1 | 0x08000000 | 231,816 B (226.38 KB) | 1,048,576 B (1024 KB) | 22.1% |
| ER_IROM1 | 0x08000000 | 231,916 B (226.48 KB) | 1,048,576 B (1024 KB) | 22.1% |
| RW_CCM | 0x10000000 | 57,376 B (56.03 KB) | 65,280 B (64 KB) | 87.9% |
| UNINIT_CCM | 0x1000FF00 | 48 B (0.05 KB) | 256 B (0 KB) | 18.8% |
| RW_IRAM1 | 0x20000000 | 75,264 B (73.50 KB) | 131,072 B (128 KB) | 57.4% |
@@ -136,7 +136,7 @@
|---|---|---|---|---|---|---|
| 1 | heap_4.o | 512 | 69 | 32 | 57,344 | 57,376 |
| 2 | lv_mem_core_builtin.o | 410 | 0 | 0 | 16,384 | 16,384 |
| 3 | lftpd.o | 6,108 | 509 | 160 | 7,724 | 7,884 |
| 3 | lftpd.o | 6,180 | 509 | 160 | 7,724 | 7,884 |
| 4 | ui_net_sink.o | 1,126 | 67 | 20 | 6,746 | 6,766 |
| 5 | ui_port.o | 418 | 0 | 12 | 6,408 | 6,420 |
| 6 | nand_ftl.o | 2,352 | 175 | 16 | 6,200 | 6,216 |
@@ -204,7 +204,7 @@
| 68 | vol_lock.o | 92 | 0 | 4 | 0 | 4 |
| 69 | errno.o | 20 | 0 | 4 | 0 | 4 |
| 70 | ui_page_net_cfg.o | 1,004 | 105 | 2 | 0 | 2 |
| | **All .o files** | **213,236** | **18,580** | **1,188** | **131,500** | **132,688** |
| | **All .o files** | **213,264** | **18,652** | **1,188** | **131,500** | **132,688** |
## 5. Flash (Code + RO Data) Top-30(按大小降序)
@@ -212,7 +212,7 @@
|---|---|---|---|---|
| 1 | ff.o | 7,962 | 282 | 8,244 |
| 2 | cfg_file.o | 5,970 | 810 | 6,780 |
| 3 | lftpd.o | 6,108 | 509 | 6,617 |
| 3 | lftpd.o | 6,180 | 509 | 6,689 |
| 4 | net_socket.o | 4,944 | 443 | 5,387 |
| 5 | lv_indev.o | 4,928 | 0 | 4,928 |
| 6 | ch395f.o | 3,978 | 603 | 4,581 |
@@ -240,6 +240,6 @@
| 28 | printfa.o | 2,312 | 0 | 2,312 |
| 29 | stm32f4xx_hal_i2c.o | 2,294 | 0 | 2,294 |
| 30 | lv_display.o | 2,284 | 0 | 2,284 |
| | **All .o files** | **213,236** | **18,580** | **231,816** |
| | **All .o files** | **213,264** | **18,652** | **231,916** |
> 自洽校验:per-file 213,236/18,580/1,188/131,500 vs Grand Totals 213,236/18,580/1,188/131,500(CodeΔ=+0 / ROΔ=+0 / RWΔ=+0 / ZIΔ=+0;精确一致(含 '('incl. Generated)'-'(incl. Padding)' 台账行))→ **PASS**
> 自洽校验:per-file 213,264/18,652/1,188/131,500 vs Grand Totals 213,264/18,652/1,188/131,500(CodeΔ=+0 / ROΔ=+0 / RWΔ=+0 / ZIΔ=+0;精确一致(含 '('incl. Generated)'-'(incl. Padding)' 台账行))→ **PASS**
@@ -1,7 +1,7 @@
# Stack budget report
- Source: `MDK-ARM\STM32F407-Demo\STM32F407-Demo.htm` (Keil static call graph)
- Linker run: Thu Oct 22 19:53:08 2026
- Linker run: Thu Oct 22 22:25:25 2026
- Global maximum stack usage: **3024 B** (any call tree)
- Runtime watermark: 8 task(s) from 串口采集日志 `.dsh\skills\serial-assistant\scripts\state\serial.log`
@@ -11,12 +11,12 @@ Rule: `margin = cfg - max(static_max_depth, runtime_peak)` must be >= max(1024 B
|---|---|---|---|---|---|---|---|---|
| defaultTask | `StartDefaultTask` | 5120 | 3024 | 2152 | 3024 | 2096 | 40% | PASS |
| netTask | `StartNetTask` | 3840 | 1912 | 1116 | 1912 | 1928 | 50% | PASS |
| ftpTask | `StartFtpTask` | 4096 | 1456 | 1404 | 1456 | 2640 | 64% | PASS |
| ftpTask | `StartFtpTask` | 4096 | 1456 | 740 | 1456 | 2640 | 64% | PASS |
| uiTask | `StartUiTask` | 16384 | 1336 | 4052 | 4052 | 12332 | 75% | PASS |
| testTask | `start_test_task` | 8192 | 736 | - | 736 | 7456 | 91% | PASS |
| timeSyncTask | `time_sync_task` | 2560 | 1264 | 648 | 1264 | 1296 | 50% | PASS |
| timeSyncTask | `time_sync_task` | 2560 | 1264 | 616 | 1264 | 1296 | 50% | PASS |
| adcTask | `adc_task_func` | 2560 | 1320 | 952 | 1320 | 1240 | 48% | PASS |
| iec104Task | `iec104_serv_task` | 2560 | 1456 | 540 | 1456 | 1104 | 43% | PASS |
| iec104Task | `iec104_serv_task` | 2560 | 1456 | 580 | 1456 | 1104 | 43% | PASS |
| protectTask | `protect_task` | 2048 | 872 | 828 | 872 | 1176 | 57% | PASS |
`Static max` = armlink worst-case call-tree depth; `Runtime peak` = measured high-water mark (boot + whatever workload ran before the sample); `Worst` = max of the two, which is what the verdict is computed from.
@@ -593,3 +593,31 @@ FTP 根目录 = 卷根,lftpd 是卷的网络文件入口(`Drivers/BSP/NET/lf
- **STOR/DELE 写门** — `[ftp] write=0` 时写命令一律 `550` 拒,卷对 FTP 侧只读(`/log`、`/config.ini` 仍可下载不可改写)。
- **全局失败锁定** — 连续 5 次鉴权失败 → 锁定 60 s(跨会话全局、易失、重启清零);纯 lftpd 层状态,不触存储。
- **默认口令告警锚点** — 出厂 `pwd_hash` 在用期间每次启动打 INFO `[FTP] using factory default password`。
### 10.3 FTP 日志级别划分:对端行为 vs 固件故障(2026-10-22)
lftpd 原先把全部异常路径一律按 error 级打(串口上带 `[ERR]`),其中**九处其实由外部输入决
定**:客户端关窗口、连上就走、PASV 之后不建数据连接、运维改 IP 触发监听重建。代价实测两条:
1. `@test` 的 Forbidden 标记含 `[ERR]`(它同时是四个 Fault 崩溃日志的前缀)——"FileZilla 关了
个窗口"就能把一次启动测试判 FAIL。实测就是这样踩到的:抓自检台账基线那次 FTP 会话结束后,
设备打 `[ERR] [FTP] error reading next command`,本次 `@test` 直接 FAIL。
2. 任何对 21 端口"连一下就断开"的外部行为,都能**无限制造固件级 ERROR 日志**——日志面与门禁
面都可被外部输入驱动,这在工业现场是过不了审的。
| 级别 | 串口形态 | 覆盖的事件 | 判据 |
|---|---|---|---|
| error(`lftpd_log_error` / `DBG_ERROR`) | `[ERR] …` | 建 socket / bind / listen 失败、取端口失败、写通道失败、文件打开失败(读/写/重复打开)、`DOWNLOAD FAILED` / `UPLOAD FAILED`(传输未完成)、`data listener disarmed`(控制连接还活着而我方数据监听已不在服役) | 本侧缺陷,或**数据完整性事件** |
| peer(`lftpd_log_peer` → info 级) | 普通 INFO 行 | 控制通道被对端关闭(原 `error reading next command`)、发送 220 之前对端已走、`listener rebuilt`(网络重建,原因由 accept 层自己记)、四处 `data accept failed`(PASV 后没来数据连接)、`accept timeout` | **对端决定的正常收尾** |
- **新增专门的 `lftpd_log_peer()` 而不是就地改成 `DBG_INFO`**:让"谁才算真故障"在读代码时
一目了然,也防止后来者顺手把对端行为又升回 error。
- **"传输未完成"仍留 error**:对端中途退出确实让一次 STOR/RETR 没走完,那是需要留痕的完整性
事件(`../问题记录/CH395F_Trap_Records.md` 的中止家族就靠这些锚点定位)。
- **边界(实测划清)**:客户端**干净收尾**(发 `QUIT`,或 FIN 半关)现在整条链路上没有任何 `[ERR]`;
但用 **RST 硬拆**(`SO_LINGER` 清零、进程被杀、网线拔了)仍会留一条 `[ERR][NET] sockN TIMEOUT`——
那是 net 层的套接字超时锚点,网络自愈架构的判据要读它,**不在本次降级范围**。所以"采集窗口内
不要跑 FTP"这条规避口径仍然有效,只是不再因为一次正常下载而中弹。
- 排障信息量不减:peer 级照旧打印,且比原来更准(带 `ret=` / `sk=` / `timeout(ms)`,措辞从
"error reading next command"改成"control channel closed by peer"——原先那句把正常收尾
写成了故障,误导过排查方向。
+6 -4
View File
@@ -68,10 +68,12 @@
| 既没有 `device_ip` 也没有 `--selftest-log` | 要求判定却没有数据源 | **FAIL**(同上,不再 WARN 放行) |
| 基线最后一条在台账里找不到 | 512 条环已覆盖基线(更新量 ≥ 环容),无法证明 | **FAIL**(不拿"看不到"当"没有") |
- ⚠ **采集窗口内不要跑 FTP**:设备会把每条 FTP 命令打到串口,客户端退出时还会多打一行
`[ERR] [FTP] error reading next command` —— 默认 Forbidden 含 `[ERR]`,于是**任何**人边跑 `@test`
边用 FileZilla 传文件,都会让本次莫名 FAIL(本次改造实测踩到:抓基线原先排在采集武装之后,
开机耗时被自己的 FTP 会话拉到 ~8 s 并命中 `[ERR]`)。基线抓取因此固定在 open/clear **之前**。
- ⚠ **采集窗口内仍不要跑 FTP**:每条 FTP 命令都会打到串口上(日志变长、开机耗时指标被污染——
实测抓基线排在采集武装之后时,`net init: OK` 从 612 ms 被拉到 7997 ms)。基线抓取因此固定在
`open/clear` **之前**。(原先更要命的是客户端退出会打 `[ERR] [FTP] error reading next command`
→ 直接命中 Forbidden 里的 `[ERR]`;该措辞与级别已在 2026-10-22 归入"对端行为"级,见
`../架构设计/STM32F4-Base存储架构说明.md` §10.3。注意:RST 硬拆仍会留一条 `[ERR][NET] sockN`
`TIMEOUT`——那是网络自愈架构要读的套接字超时锚点,不在降级范围内。
- `--selftest-scope all` = 保留旧的累计口径(任一记录即 FAIL),用于**审计**或第一次建基线。
- 给了 `--selftest-log`(本地副本)就没有基线可比 → 自动降级为累计口径并打印一行说明。
- 抓基线同样要 FTP 凭据:`--ftp-user` / `--ftp-pass`(config `ftp_user` / `ftp_pass`,默认出厂
@@ -504,7 +504,7 @@ $env:DTU_KEIL_UV4 = 'D:\NotExist\UV4.exe' # 仅当前会话,不动用户级
| 资源 | 已用 | 总量 | 利用率 | 余量 | 红线说明 |
|---|---|---|---|---|---|
| Flash(`ER_IROM1` = Code + RO) | 231,816 B(226.4 KB) | 1 MB | 22.1% | ≈804 KB | **充裕**:产品需求无 OTA / 双镜像预算,Flash 不是卡点;死代码裁剪簇已收口 → `docs/问题记录/项目审查与优化建议报告.md` §5.4 |
| Flash(`ER_IROM1` = Code + RO) | 231,916 B(226.5 KB) | 1 MB | 22.1% | ≈804 KB | **充裕**:产品需求无 OTA / 双镜像预算,Flash 不是卡点;死代码裁剪簇已收口 → `docs/问题记录/项目审查与优化建议报告.md` §5.4 |
| 主 SRAM1(静态区;FreeRTOS 堆已迁 CCM) | 75,264 B(73.5 KB) | 128 KB | 57.4% | 55,808 B | 已不紧张——2026-10-11 堆迁 CCM 与 2026-10-15 `LV_MEM_SIZE` 40→16 KB 两步的结果 |
| CCM(`RW_CCM` 段) | 57,376 B(56.0 KB) | 65,280 B | **87.9%** | **7,904 B** | **当前真正的红线**:加任务 / 抬栈 / 加堆会先在这里撞墙;`mem-budget` 钩子在 92% 拦提交 |
@@ -93,7 +93,7 @@ git ls-files -z | xargs -0 stat -c '%s'
| 名词 | 口径 |
|---|---|
| Flash 占用 | `Code + RO-data` = `ER_IROM1` = **231,816 B(22.1%)**;`ROM 合计` 再加 `RW` 初始化表 = 233,004 B(22.2%) |
| Flash 占用 | `Code + RO-data` = `ER_IROM1` = **231,916 B(22.1%)**;`ROM 合计` 再加 `RW` 初始化表 = 233,104 B(22.2%) |
| SRAM 占用 | `RW_IRAM1`(DMA 可达区)与 `RW_CCM`(仅 CPU)分开计,二者不可互换(规则 R2/R3) |
| 最坏栈 | `max(armlink 静态调用图深度, 真机 [STACK] 高水位)`;判定线 `余量 ≥ max(1024 B ISR, 配置×30%)` |
| 编译单元 | uvprojx 的 `<FilePath>` 计数(今日 **326 个 `.c`**,其中 LVGL 201);"自研 `.c`" 另按目录口径 114 个 |
@@ -230,8 +230,8 @@ defaultTask 单属主;原子写经卷锁;旧档迁移已删除。**仍列为
| 指标 | 2026-10-22 | 2026-10-15 | 说明 |
|---|---|---|---|
| `Code` | 213,236 B | 213,520 B | 当日累计:F-6 −332 B、SPI 回调归属 +16 B、消息队列收口 +32 B |
| `ER_IROM1`(Code+RO) | **231,816 B / 22.1%** | 232,280 B / 22.2% | 余 ≈804 KB |
| `Code` | 213,264 B | 213,520 B | 当日累计:F-6 −332 B、SPI 回调归属 +16 B、消息队列收口 +32 B、FTP 日志级别改造 +28 B(RO 另 +72 B) |
| `ER_IROM1`(Code+RO) | **231,916 B / 22.1%** | 232,280 B / 22.2% | 余 ≈804 KB |
| `RW_IRAM1` | **75,264 B / 57.4%**(余 55,808) | 73,568 B / 56.1% | 增量来自 B5/B8 功能码 |
| `RW_CCM` | **57,376 B / 87.9%**(余 7,904) | 57,376 B / 87.9% | **当前唯一红线** |
| FreeRTOS 堆 | 56 KB(57,344 B)@CCM;实测 `free=15,800` / `min_ever=8,464` | 同 | 2026-10-22 真机 |
@@ -271,7 +271,7 @@ defaultTask 单属主;原子写经卷锁;旧档迁移已删除。**仍列为
| **F-6** 双精度软浮点 | ≈1 KB | **归因错**:全库 0 处 `%f`;剩余 `dadd/dmul/ddiv/depilogue/dfixul` 1,018 B 锁在 MicroLIB `printfa.o` 的 `_printf_core → _fp_digits`,只要用 `snprintf` 就删不掉。但**根因侧确有可修点**:`App/algo/prot_set.h` 两个 Q10 宏写成 `double` | ✅ **已落地 2026-10-22**:改单精度 → `@build` −332 B、5 个 helper(300 B)消失;数值逐位等价(穷举 42 个字面量 + int32 全域 ÷1024 往返 4.49×10⁶ 例零差异);真机 `@test` PASS |
**当前可回收 Flash = 0 B**(F-4/F-6 已定论,图片链同理不打补丁)。LVGL 仍占镜像 **37.8%**
(87,706 / 231,816 B)——这是"用 LVGL 画单色菜单"的结构性代价,F-1/F-2/F-3 已省掉 64,576 B。
(87,706 / 231,916 B)——这是"用 LVGL 画单色菜单"的结构性代价,F-1/F-2/F-3 已省掉 64,576 B。
### 5.5 内存治理长效机制(常开,不受暂缓影响)