RTU/mimo/问题处理文档.md

375 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 问题处理文档
---
## 2026-06-26: 远程调试 gdbserver 未自动启动 & RTU 打印信息查看问题
**问题**F5 选择 "ARM 编译+部署+调试 (RK3568)" 后无法断点调试。
**根因**`deploy-to-rk` 任务只做了编译+scp 传输,没有自动启动 gdbserver。VS Code GDB 尝试连接 `198.120.0.100:2345` 时端口无监听。
**修复**
1. 修改 `deploy-to-rk` 任务scp 传输后自动 `killall gdbserver` + 启动新 gdbserver
2. 修改 `start-gdbserver` 任务:统一用 `>/tmp/rtu_output.log 2>&1` 重定向 gdbserver+RTU 输出
3. 新增 `tail-rtu-output` 任务:一键 `tail -f /tmp/rtu_output.log` 实时查看 RTU printf 输出
**原理**RTU 作为 gdbserver 的子进程,继承 gdbserver 重定向后的 fd 1/2所以 printf/LOG_I/LOG_E 全部进入 `/tmp/rtu_output.log`
**涉及文件**`.vscode/tasks.json`
**注意事项**RTU 的 stdin 被 gdbserver 接管,无法在 tail 窗口输入 CLI 命令。交互式命令测试需单独 `ssh root@198.120.0.100 /mnt/RTU/RTU`
**验证**GDB 连接→continue→tail 文件成功看到所有 LOG_I/LOG_E 输出。
### 补充修复 (2026-06-26): "ARM 调试 (不编译)" preLaunchTask 卡住
**问题**F5 选 "ARM 调试 (不编译, RK3568)" 不工作,且后续引入复合任务后 "ARM 编译+部署+调试" 也失效。
**根因**:尝试用 `dependsOrder: "sequence"/"parallel"` + `isBackground: true` 复合任务作为 preLaunchTask。VS Code preLaunchTask 需要子任务明确完成信号,复合任务+后台子任务组合行为不可靠exit code 异常、卡住)。
**最终修复**
1. 删除所有复合任务 (`deploy-with-tail`, `start-gdbserver-with-tail`)
2. `deploy-to-rk` = scp + gdbserver同步preLaunchTask 可靠)
3. `start-gdbserver` = gdbserver同步preLaunchTask 可靠)
4. `tail-rtu-output` = 手动 `tail -f`(独立任务,不参与 preLaunchTask
5. `stopAtEntry` 改为 `false`RTU 直接运行不需手动按继续
**涉及文件**`.vscode/tasks.json`、`.vscode/launch.json`
**tail 使用方式**F5 启动调试后,`Ctrl+Shift+P` → Run Task → `tail-rtu-output` 查看实时打印。
---
## 2026-06-18: X-Macro 应用模块注册表 — 名称大小写匹配修复
**问题**X-Macro 生成 g_vec_app 名称时使用 `"app_" #name`(如 `app_SYS`),但配置文件 `app_config.json` 使用小写 `app_sys`,导致名称不匹配,所有线程被 "skipped (disabled by config)"。
**根因**C 预处理器 `#name` 运算符产生宏参数的原始文本,`APP_MODULE(SYS, ...)` → `"app_SYS"`,无法自动转小写。
**修复**:增加第二个参数 `cfg_name`(小写),与 `name`(大写,枚举后缀)分离:
```c
// 修改前: APP_MODULE(SYS, init1, init2, func)
// 修改后: APP_MODULE(SYS, sys, init1, init2, func)
```
- `#cfg_name``"app_sys"`(匹配配置文件)
**涉及文件**`app_modules.h`、`mySystem.h`(两处)、`app_sys.cpp`(一处)
**注意事项**:旧进程残留占端口会导致 `mms_s_init` 绑定失败。app_modules.h 禁止加 `#pragma once`
**验证**编译通过8 个线程全部 `pthread_create success`
---
## 2026-06-16: ICP67 模块多实例重构
**来源**[libicp67模块分析](./工程/libicp67模块分析.md)
### 修复项
1. **全局回调表单例化** — 将 `g_genneral_method` 从 static 全局变量移入 `stru_icp67.method`。26 个 setter 改为实例化接口(`stru_icp67 *` 第一参数)。删除 `general_method.h`、`icp67_get_genneral_method()`。所有 decode 函数改为 `p_icp67->method.xxx_cb` 回调。
2. **重传竞态修复**`icp67_timer_handler``resend_cnt++`/`tm_cnt=0`/`tm_cnt++` 移入 `rtx_sem` 保护区。
3. **发送覆盖防御**`icp67_rtx_flag_set` 增加 TX_FLAG 重复设置 LOG_E 警告。
4. **空桩填充**`icp67_decode_ti_4`、`icp67_decode_ti_203` 改为 LOG_E + ICP67_RX_FLAG 设置。
5. **硬编码清理** — 所有魔数替换为命名宏(`ICP67_TX_BUF_SIZE`、`ICP67_MD5_LEN`、`ICP67_TM_TICK_MS`/`ICP67_TM_OUT_MS`、`ICP67_HEAD_OVERHEAD`/`ICP67_FRAME_OVERHEAD` 等)。
6. **配置参数宏化** — 超时/重试/缓冲/定时器 tick 统一在 `myIcp67.h` 中用 `#define` 管理。
7. **sender 函数迁移** — 12 个内部 sender 函数从 `general_method.cpp` 迁入 `icp67.cpp``icp67_init` 中初始化。
**状态**:✅ 已完成
**涉及文件**`myIcp67.h`, `icp67.h`, `icp67.cpp`, `general_method.cpp`, `self_ptl.cpp`, `method.cpp`, `general_method.h`(已删除)
**设计决策**
- pack(1) 不转换ARM/x86 小端固定,风险收益比不成立)
- TI_11 len==11 注释不启(子函数已各自处理标志)
- dispatch map`g_map_ti_decode` 等)保持 staticread-only多实例安全
### #11 libcom_channel + libcom_scan 合并为 libcom_decode
**问题**两个模块紧耦合com_channel 调 com_recv_datacom_scan 调 com_channel_interface_get且 app_comm_channel 线程为空壳
**修复**
1. 合并为 `libcom_decode` 模块,按功能拆分 4 个文件
2. 整合为 1 个 app 线程(删除空壳线程)
3. 枚举值合并ENUM_APP_COMM + ENUM_APP_COM_SCAN → ENUM_APP_COM_DECODE
**状态**:✅ 已完成
**涉及文件**:新建 `src/system/libcom_decode/`4源文件+makefile修改 `mySystem.h`、`app_sys.cpp`、`self_ptl.cpp`、`iec.cpp`、`makefile`
**验证**`./release/build.sh` 编译通过
---
### #10 libcomm + 调用者缺陷修复
**问题**
1. UDP `recvfrom` 永久阻塞,无超时无退出机制
2. UDP `close``state_cb(id, -1, disconnected)`,回调拿到的是已关闭的 -1
3. `com_scan`/`self_ptl`/`iec` 三处 send+event 回滚竞态:`msg_queue_send` 成功 → `event_send` 失败 → `try_recv` 可能取出旧消息
4. `com_channel_recv_cb``comm_send` 到已断开的 TCP_C_0 不检查 fd
**修复**
1. `udp_run``recvfrom` 前加 `select` 1s 超时
2. `udp_close` 先保存 fd 再 close回调传入正确值
3. 三处改为先 `event_send``msg_queue_send`,消除回滚竞态
4. `com_channel_recv_cb``comm_send` 前检查 `socket_fd >= 0`
**状态**:✅ 已完成
**涉及文件**`comm_udp.cpp`, `com_scan.cpp`, `self_ptl.cpp`, `iec.cpp`, `com_channel.cpp`
**验证**`./release/build.sh` 编译通过
---
### #9 libcomm 模块修补
**问题**
1. UART send 不完整:裸 write 无流量控制,缺少原始模式终端设置(~ICANON/~ECHO/~OPOST
2. TCP client select 100ms 空轮询,空闲时 CPU 空转
3.`comm_destroy` 接口,内存泄漏
4. `stru_comm` 结构体中 6 个函数指针字段从未使用,冗余
**修复**
1. 重写 `comm_uart.cpp`:恢复原始模式 + VTIME=1 读超时 + tcflush/tcdrain 完整发送
2. TCP client select 去掉超时参数,永久阻塞
3. 新增 `comm_destroy(id)` API
4. 删除 `stru_comm` 中 6 个未使用字段
**状态**:✅ 已完成
**涉及文件**`comm_uart.cpp`, `comm_tcp.cpp`, `comm.cpp`, `comm.h`, `myComm.h`
**验证**`./release/build.sh` 编译通过
---
### #8 libtask 事件/消息队列缺陷修复
**问题**
1. `task_event_destroy``task_msg_queue_destroy``free(p)` 后仍访问 `p->name`use-after-free
2. `task_event_recv` 中 AND 检查用 `opt == TASK_EVENT_FLAG_AND` 而非位测试,`AND | CLEAR` 组合被误识别为 OR
3. `task_event_send``pthread_cond_signal` 而非 `cond_broadcast`,多等待者场景可能唤醒不足
**修复**
1. `free(p)` 移到 `LOG_I` 之后,先打日志再释放
2. `opt ==` 改为 `opt &` 位测试
3. `cond_signal` 改为 `cond_broadcast`
**状态**:✅ 已完成
**涉及文件**`src/public/libtask/src/myTask.c`
**验证**`./release/build.sh` 编译通过
---
### #7 libtask 定时器 SIGEV_THREAD 线程爆炸
**问题**`myTask.c` 使用 `timer_create(CLOCK_REALTIME, SIGEV_THREAD)` 模式每次定时器超时内核创建一个新线程执行回调。9 个 app 线程 × 3 个定时器10ms/100ms/1000ms= 27 个定时器,每秒约 1000 次内核线程创建/销毁,系统开销极大。回调仅做 `task_event_send` 设一个事件位(几微秒),线程创建开销远大于实际工作。
**需求**
1. 消除每次定时器超时创建线程的开销
2. API 签名全部不变,调用方零修改
3. 仅 Linux 平台
**处理计划**[libtask定时器SIGEV_THREAD优化](./plan/libtask定时器SIGEV_THREAD优化.md)
**修复方案**:用 Linux **timerfd** + **epoll** + **单例管理器线程** 替代 `SIGEV_THREAD`
- `stru_task_timer``timer_t timerid` → `int timerfd`,新增 `pthread_cond_t cond`、`int in_epoll`
- 删除 `task_timer_sig_handler`,新增 `timer_manager_thread`epoll_wait 监听所有 timerfd超时时调用原始回调
- `task_timer_stop/destroy``while(running) usleep(1000)` 忙等 → `cond_wait` 零 CPU 阻塞
- 6 个 API 签名全部不变
**状态**:✅ 已完成
**涉及文件**`src/public/libtask/src/myTask.c`(仅此一个文件,`release/inc/myTask.h` 无改动)
**验证**`./release/build.sh` 编译通过,零错误零警告
---
## 2026-06-12
### #6 Tab 补全子命令前缀丢失
**问题**:输入 `datacenter ` + `p` + Tab 后,`datacenter ` 前缀被 `param` 覆盖,整行只剩 `param`
**根因**`linenoiseEdit` 中 Tab 键处理用 `strncpy(buf, completions[0])` 整行覆盖。
**修复**:改为找到最后一个空格,只替换空格之后的当前词,保留前缀。
---
### #5 app_cmd 交互卡顿与 Tab 补全失效
**问题**:上一轮将 `cmd_recv` 改为 select 非阻塞 + EV_TIMER2(100ms) 后:
1. 回车后 `cmd>` 回显不及时,有明显卡顿感
2. 输入 `d` 后按 Tab不会弹出命令补全
**根因**`select()` 方案与 `linenoise()` 交互式行编辑器根本性不兼容——select 时终端处于规范模式行缓冲Tab 不是行终止符不会触发 select100ms 定时器引入最多 100ms 延迟。
**修复**:将 `app_cmd` 线程从定时器事件驱动改为简单阻塞循环 `while(1) { cmd_recv(); }`。`linenoise()` 自管理终端模式切换和 Tab 补全,线程阻塞在 stdin 上零延迟响应。
**验证**:编译通过。
---
### #4 app_cmd 线程启用后 CPU 飙升至 108%
**问题**`app_cmd` 线程中 `cmd_recv()` 启用后CPU 占用 108%,导致该入口被暂时屏蔽([app_cmd.cpp:117](src/system/RTU/src/app_cmd.cpp#L117) 被注释)。
**根因**
1. **死循环层面**`linenoiseEdit()`[my_cmd.cpp:107](src/public/libcmd/src/my_cmd.cpp#L107))中 `read()` 只检查了返回 `-1`,未处理返回 `0`EOF。当进程无真正控制终端时RTU 嵌入式环境常见),`read()` 立即返回 0 形成死循环。
2. **架构层面**`linenoise()` 是同步阻塞调用,却被放在 10ms 定时器 EVI_TIMER1 中执行,与其他定时器事件循环模型不兼容。
**修复内容**
1. `my_cmd.cpp``read()` 返回值判断改为 `<= 0`(含 EOF 处理);`enableRawMode()` 返回值在 `linenoise()` 中检查,失败直接返回 NULL
2. `app_cmd.cpp`:重构 `cmd_recv``cmd_recv_nonblock()`,用 `select()` 实现非阻塞 stdin 检查 + `isatty()` 过滤非终端环境,移至 EV_TIMER2100ms调用
3. `CLAUDE.md`:全文翻译为中文
**验证**:编译通过(零错误零警告),`./test/RTU < /dev/null` 运行 3 秒 CPU 占用 0.0%。
---
## 2026-06-18: 跨模块耦合解耦 — self_ptl_set_interface → dc_signal + app_get_ptr → app_get_mq_target
**问题**:系统模块间存在两种硬编码耦合:
1. `decode_channel_mgr` 直接调用 `self_ptl_set_interface()` 传递 TCP 接口 → 强依赖 self_ptl 头文件
2. `iec.cpp`/`self_ptl.cpp`/`decode_data_router.cpp` 中通过 `app_get_ptr(ENUM_APP_XXX)` + 硬编码事件号发送跨模块通知 → 模块间直接知晓对方身份
**根因**早期快速迭代中模块间交互未经过中间层抽象。协议处理模块iec/self_ptl/comm互相知道对方的应用 ID、事件号、接口函数。
**修复**
### 问题一self_ptl_set_interface 解耦
- `decode_channel_mgr.cpp`:移除 `#include "self_ptl.h"``self_ptl_set_interface()` 调用,改为 `dc_signal_out("sys.tcp_c0_interface", ...)` 通过 datacenter 发布接口信息
- `self_ptl.cpp`:移除 `self_ptl_set_interface()` 函数,`self_ptl_init()` 改为通过 `dc_get_out_signal_info("sys.tcp_c0_interface")` 从 datacenter 读取接口
### 问题二app_get_ptr + 硬编码事件号解耦
- `mySystem.h`:新增 `stru_mq_target {app_id, event}` 结构和 `app_get_mq_target(enum_mq_id)` 声明
- `app_sys.cpp`:新增 `g_mq_target_map[ENUM_MQ_MAX]` 全局映射表和 `app_get_mq_target()` 实现
- `iec.cpp``iec_data_tx()` 中 `app_get_ptr(ENUM_APP_COM_DECODE) + EV_COM_RX_IEC``app_get_mq_target(ENUM_MQ_IEC_TO_COM)`
- `self_ptl.cpp``self_ptl_data_tx()` 中硬编码事件号 → `app_get_mq_target(ENUM_MQ_SELF_PTL_TO_COM)`
- `decode_data_router.cpp``com_scan_send_data_to_app()` 中 if/else 判断目标 MQ → 通过 `app_get_mq_target(mq_id)` 统一处理
**状态**:✅ 已完成
**涉及文件**`decode_channel_mgr.cpp`, `self_ptl.cpp`, `iec.cpp`, `decode_data_router.cpp`, `mySystem.h`, `app_sys.cpp`
**验证**`./release/build.sh` 编译通过,运行日志显示 `self_ptl_init got tcp_c0_if from dc: 20006` 确认 datacenter 信号通信正常。
---
## 2026-06-10
### #1 libmms_m RCB 订阅编号硬编码
**问题**`mms_m_icd_report_init()` 中硬编码 `if(0 == rpt_no.compare("01"))`,只订阅编号为 `"01"` 的 RCB其他编号被丢弃无法按需灵活订阅多个 RCB 实例。
**需求**
1. 灵活可配置订阅的控制块编号
2. libmms_m 提供接口,由 libiec61850m 传入
3. 可传入一个或多个编号,不传默认 `"01"`
4. 无效数据打印错误并返回失败
**处理计划**[RCB订阅编号可配置化](./mid/RCB订阅编号可配置化.md)
**状态**:✅ 已完成
**涉及文件**`myMms_m.h`, `mms_m.h`, `mms_m.cpp`, `iec61850m.cpp`
---
### #2 libweb_server 模块缺陷修复
**问题**WebSocket 服务端模块存在 10 个缺陷含严重级别单客户端、多线程竞态、悬空指针、non-null-terminated UB和高危/中等级别(调试 printf、SBO 阻塞、LOG 格式化缺失等)。
**需求**
1. 兼容多个 WebSocket 客户端同时连接
2. 完善断连处理
3. 多线程安全保护
4. 修复 UB 和格式化问题
5. 增量推送优化(仅变化时发送)
**处理计划**[libweb_server模块分析](../工程/libweb_server模块分析.md)
**状态**:✅ 已完成
**涉及文件**`web_server.cpp`, `ws_method.cpp`
---
### #3 libweb_server 多连接资源共享冲突
**问题**:支持多客户端后,所有连接共享同一套全局信号资源(`g_ws_out_signals` 等),一个客户端的 add/del 操作会影响其他客户端的数据推送。
**需求**
1. 每个连接独立的信号资源per-connection session
2. 连接建立时自动开辟资源,断开时自动释放
3. `ws_task()` 按 session 独立构建 JSON 推送到对应连接
**处理计划**[libweb_server模块分析](../工程/libweb_server模块分析.md)
**状态**:✅ 已完成
**涉及文件**`ws_method.h`, `ws_method.cpp`, `web_server.cpp`
---
---
## 2026-06-16: Self_PTL 缺陷优化
**来源**[libself_ptl模块分析](./工程/libself_ptl模块分析.md)
### 修复项
1. **D4 测试命令分离**`self_ptl_method_task()` 增加 `g_self_ptl_test_active` 守卫,仅当 CLI 触发测试命令(`cmd_self_ptl` 设置 flag时才进入遍历TIMER2 先检查 active 标志再调用。
2. **D2 环形缓冲区加锁**`stru_self_rx` 增加 `pthread_mutex_t mutex` 字段,`put_rx_data`/`get_rx_data` 操作加锁保护。
3. **D3 溢出日志启**`put_rx_data` 溢出处理中原被注释的 `LOG_E` 已启。
4. **D8 SBO 超时**`stru_signal_ctrl` 增加 `select_time_ms` + `sbo_timeout_ms` 字段。`dc_check_ctrl_valid` 在 SELECT 时记录时间戳。新增 `dc_signal_check_sbo_timeout()` 函数。`self_ptl_task()` 每定时器 tick 检查 SBO 状态,超时(默认 30s自动回退到 READY。
5. **D6 硬编码清理**`2048``SELF_PTL_BUF_SIZE``256` → `SELF_PTL_TEMP_SIZE`IEC-104 APCI 魔数 → `IEC_APCI_MIN_LEN`/`IEC_FRAME_OVERHEAD`。
### 未处理
- **D7 双缓冲优化** — 改动大、风险高,暂缓。
- **D5 IEC-104 帧校验** — 已完整,无需处理。
- **D9 滑窗丢弃** — 逻辑正确,无需处理。
**状态**:✅ 已完成
**涉及文件**`self_ptl.cpp`, `method.cpp`, `myDatacenter.h`, `dc_signal.cpp`
---
## 2026-06-16: libdatacenter 缺陷修复
**来源**[libdatacenter模块分析](./工程/libdatacenter模块分析.md)
### 修复项
1. **缺陷1&2: AO/Param 重注册持锁外 delete**`dc_signal_ao``dc_signal_param` 的 re-registration 路径中 `dc_delete_signal_data` 改为在 `signal_ao.mtx`/`signal_param.mtx` 锁内执行,消除 use-after-free 风险。
2. **缺陷3: 回调向量无锁写入**`out_change_cb_list`/`change_cb_list` 的 `push_back` 加对应的 `stru_signal_map.mtx` 锁保护。
3. **缺陷4: YK 跳过值校验**`dc_signal_yk_set_status` 的 DIRECT 步骤增加 `dc_check_val_valid` 调用,与 AO/Param 行为一致。
4. **缺陷5: strncpy 安全** — IP/MAC/C 类型 `dc_set_signal_val` 中的 `strncpy` 改为 `memcpy`,避免源串等于缓冲区长时无 `\0` 终止。
**状态**:✅ 已完成
**涉及文件**`src/system/libdatacenter/src/dc_signal.cpp`
---
## 2026-06-16: 参数缺省值重构
**来源**:设计讨论——缺省值应只存在于配置文件,不应在运行时存储。
### 完成项
1. **移除 `stru_signal.vec_p_default_data`** — 默认值不再存储于运行时结构中
2. **移除 API 默认值参数**`dc_signal_ao()` / `dc_signal_param()` 不再接收 `p_default_data``dc_get_ao_signal_info()` / `dc_get_param_signal_info()` 不再输出默认值
3. **删除 `dc_signal_{ao,param}_add_check()`** — 不再需要比较默认值变化
4. **dc_param.cpp 不再创建默认值** — 初始化时只从 `param.xml`/`self_param.xml` 读值并注册
5. **self_ptl 不再创建/传默认值** — 删除 `vec_p_default_data` 创建和传参
6. **ws_method 去掉默认值** — 删除读取/展示/序列化
7. **iec61850m 不再创建默认值** — 删除 `p_default[]` 创建
8. **self_param.xml 不写 default 字段**`dc_param_cfg_check()` 只写 `value`
**状态**:✅ 已完成
**涉及文件**`dc_signal.h/cpp`, `myDatacenter.h`, `dc_param.cpp`, `self_ptl.h/cpp`, `method.cpp`, `ws_method.cpp`, `iec61850m.cpp`, `iec61850s.cpp`