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

13 KiB
Raw Blame History

问题处理文档


2026-06-16: ICP67 模块多实例重构

来源:libicp67模块分析

修复项

  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 等)保持 static(read-only,多实例安全)

#11 libcom_channel + libcom_scan 合并为 libcom_decode

问题:两个模块紧耦合(com_channel 调 com_recv_data,com_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优化

修复方案:用 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 不是行终止符不会触发 select;100ms 定时器引入最多 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 被注释)。

根因:

  1. 死循环层面:linenoiseEdit()(my_cmd.cpp:107)中 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_TIMER2(100ms)调用
  3. CLAUDE.md:全文翻译为中文

验证:编译通过(零错误零警告),./test/RTU < /dev/null 运行 3 秒 CPU 占用 0.0%。


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订阅编号可配置化

状态:✅ 已完成
涉及文件: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模块分析

状态:✅ 已完成
涉及文件: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模块分析

状态:✅ 已完成
涉及文件:ws_method.h, ws_method.cpp, web_server.cpp



2026-06-16: Self_PTL 缺陷优化

来源:libself_ptl模块分析

修复项

  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模块分析

修复项

  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