静态排查后,暂未看到明确的 ABBA 型互斥锁环。当前现象更像“内存破坏/资源泄漏后线程停滞”或“状态机无限重试”,而不是整个 RTOS 调度器死锁。 另外,代码中使用的是 STM32 内部 IWDG,不是外部独立看门狗。 **可疑点** 1. **高风险:南北向分包接收存在确定的越界写和泄漏路径** [north](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_north.c:548) 和 [south](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_south.c:309) 在续接分包时计算: ```text len_need = len_msg - 帧头长度 ``` 但此时 `pFrame->length` 已经包含之前收到的数据,正确的剩余长度应基于 `len_msg - pFrame->length`。当前代码却从 `payload[pFrame->length]` 开始写入整个“帧头后的总长度”。 例如总长 21 字节,首批已存 8 字节,下一次仍可能从偏移 8 写入 18 字节,最终写到偏移 26,越过 21 字节堆块。这会破坏相邻堆块、邮箱消息甚至 RT-Thread 内核对象,最终表现为随机线程卡死、互斥量异常或堆分配失败。 同时,分包超时路径只清除了 `findhead`: - [chrg_north.c:627](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_north.c:627) - [chrg_south.c:399](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_south.c:399) 没有释放 `pMB/payload`,也没有清除 `length/len_msg`。而且没有新数据时会提前 `continue`,超时检查根本不会执行。串口噪声、丢字节或正常分包都可能不断积累泄漏。 此外,payload 分配失败时只返回,没有释放刚分配的 `pMB`: - [chrg_north.c:518](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_north.c:518) - [chrg_south.c:280](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_south.c:280) 这是目前最符合“运行时间越长越容易出现”的可疑点。 2. **高风险:看门狗喂法无法覆盖业务线程死锁** 主线程每秒无条件喂狗:[main.c:36](D:/proj/char/chrg_v/apps/chrg/applications/main.c:36)。 北向轮询线程又在每轮无条件喂同一只狗:[chrg_roll_nor.c:421](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_roll_nor.c:421)。 因此即使 `thr.comm`、`thr.sou`、`thr.rollsou`、LCD 线程全部停止工作,只要主线程或北向轮询线程仍能调度,IWDG 就永远不会复位。这只是在检测“CPU/调度器是否完全停止”,没有检测业务线程是否健康。 驱动最终调用的是 `HAL_IWDG_Refresh()`:[drv_wdt.c:40](D:/proj/char/chrg_v/rt-thread/bsp/stm32/libraries/HAL_Drivers/drivers/drv_wdt.c:40)。代码中没有找到外部 WDI 引脚翻转或外部看门狗驱动。 3. **高风险:当前新增的南向批处理可能永久停在 `RUN_COM`** 南向批处理遇到 CRC 错误或应答超时后,不增加重试次数、不设置截止时间,也不清除任务: - CRC 错误:[chrg_roll_sou.c:608](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_roll_sou.c:608) - 应答超时:[chrg_roll_sou.c:627](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_roll_sou.c:627) 线程会一直重发同一个寄存器,`pending` 永远不清除,`run` 永远保持 `RUN_COM`,正常南向轮询无法恢复。这属于业务状态机死锁,调度器仍正常运行,所以看门狗不会复位。 这套批处理逻辑是当前工作区相对 `HEAD` 新增的,优先级很高。 4. **高风险:南向共享状态的互斥保护实际无效** [chrg_set_sou_reg](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_roll_sou.c:435) 先在锁外写入: ```text pROLL->ch pROLL->reg pROLL->value ``` 随后互斥量只保护 `pROLL->run`。消费者也只在锁内读取 `run`,实际使用 `ch/reg/value` 时已经离开锁。 多个生产者包括 LCD、Modbus 和状态机可能交叉覆盖这些字段,形成“通道来自命令 A、寄存器来自命令 B、值来自命令 C”的组合。 批处理数组 `g_sou_com_batch` 同样由生产者和 `thr.rollsou` 同时读写,却没有共同的锁:[chrg_roll_sou.c:394](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_roll_sou.c:394)。 5. **中高风险:先切换 `RUN_COM`,再填充批处理数据** [chrg_sink_diff_send_reg](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_comm.c:2338) 先调用 `chrg_sou_com_sou_set()` 将状态切到 `RUN_COM`,然后才追加寄存器。 `chrg_sink_work()` 也是相同顺序:[chrg_sink.c:541](D:/proj/char/chrg_v/apps/chrg/applications/utils/chrg_sink.c:541)。 如果消费者在状态切换后看到尚未填充的批次,会认为没有任务并切回自动模式;随后加入的 `pending` 数据可能一直无人处理。当前线程优先级降低了部分窗口概率,但没有从同步机制上消除竞态。 6. **中高风险:应答邮箱没有请求、通道或序号关联** 邮箱消息只有 `length + payload`:[chrg_def.h:95](D:/proj/char/chrg_v/apps/chrg/applications/utils/chrg_def.h:95)。 南北轮询线程默认“收到的下一帧就是刚才请求的响应”。南向批处理只检查 CRC,任何延迟到达的旧响应都可能让当前批次 `cur_idx++`:[chrg_roll_sou.c:574](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_roll_sou.c:574)。 在超时、重发、通道切换或邮箱积压后,请求与响应很容易错位,最终造成状态机停在错误步骤或无限重试。 7. **中风险:所有业务互斥量都是永久等待** TTY、南北轮询互斥量统一使用 `RT_WAITING_FOREVER`。例如: - [chrg_tty.c:457](D:/proj/char/chrg_v/apps/chrg/applications/bsp/chrg_tty.c:457) - [chrg_comm.c:2246](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_comm.c:2246) - [chrg_roll_nor.c:439](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_roll_nor.c:439) 目前没有发现确定的反向加锁环,但一旦锁所有者因堆破坏、状态机卡死或设备写阻塞而不再释放,其他线程会永久挂起,没有超时和故障上报。 8. **中风险:串口 RingBuffer 写入结果被忽略** [chrg_north.c:623](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_north.c:623) 和 [chrg_south.c:383](D:/proj/char/chrg_v/apps/chrg/applications/thread/chrg_south.c:383) 没有检查 `rt_ringbuffer_put()` 实际写入长度。 缓冲区满时会静默丢字节,随后触发分包超时、泄漏和上述越界路径,是长期运行后出现问题的重要放大器。 **优先判断** 最可能的两条故障链是: 1. 串口分包 → 长度计算错误/超时泄漏 → 堆损坏或耗尽 → 某些业务线程永久等待;主线程继续喂狗。 2. 南向某条命令超时或响应错位 → `RUN_COM` 无限重发 → 南向业务永久停止;主线程和北向线程继续喂狗。 现场故障尚能进入 Shell 时,优先保存 `list_thread`、`list_mutex`、`list_mailbox`、`free` 输出,并检查是否持续打印 `com batch timeout, resend current reg`。这几项可以直接区分锁等待、堆耗尽和状态机无限重试。 未修改任何文件。