From 0a7a3c49b3308c1c16ca736b3cb577472a2fb7ef Mon Sep 17 00:00:00 2001 From: ypc <15051963820@163.com> Date: Mon, 15 Jun 2026 10:29:59 +0800 Subject: [PATCH] =?UTF-8?q?<=E4=BF=AE=E6=94=B9>=201=E3=80=81=E4=BD=BF?= =?UTF-8?q?=E7=94=A8mimo=E4=BC=98=E5=8C=96=E7=A8=8B=E5=BA=8F=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=97=AE=E9=A2=98=E5=A4=84=E7=90=86=E6=96=87?= =?UTF-8?q?=E6=A1=A3=EF=BC=9B2=E3=80=81=E8=B0=83=E6=95=B4=E4=BC=98?= =?UTF-8?q?=E5=8C=96libtask=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/launch.json | 26 +- mimo/MEMORY.md | 17 +- mimo/plan/datacenter模块分析.md | 40 ++ mimo/plan/libcomm与libtask分析.md | 40 ++ mimo/plan/libtask优化.md | 58 +++ mimo/plan/libtask定时器SIGEV_THREAD优化.md | 151 ++++++++ mimo/plan/libtask缺陷修复计划.md | 177 +++++++++ mimo/skills/模块分析文档生成/SKILL.md | 74 ++++ mimo/工程/libcomm模块分析.md | 223 +++++++++++ mimo/工程/libdatacenter模块分析.md | 408 +++++++++++++++++++++ mimo/工程/libtask模块分析.md | 248 +++++++++++++ mimo/问题处理文档.md | 43 +++ release/inc/myTask.h | 2 + src/public/libtask/src/myTask.c | 323 +++++++++++++--- src/system/libweb_server/src/packed_fs.c | 12 +- 15 files changed, 1771 insertions(+), 71 deletions(-) create mode 100644 mimo/plan/datacenter模块分析.md create mode 100644 mimo/plan/libcomm与libtask分析.md create mode 100644 mimo/plan/libtask优化.md create mode 100644 mimo/plan/libtask定时器SIGEV_THREAD优化.md create mode 100644 mimo/plan/libtask缺陷修复计划.md create mode 100644 mimo/skills/模块分析文档生成/SKILL.md create mode 100644 mimo/工程/libcomm模块分析.md create mode 100644 mimo/工程/libdatacenter模块分析.md create mode 100644 mimo/工程/libtask模块分析.md diff --git a/.vscode/launch.json b/.vscode/launch.json index e946247..986b7b4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,7 +2,7 @@ "version": "2.0.0", "configurations": [ { - "name": "Debug", + "name": "编译 + Debug", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/test/RTU", @@ -26,6 +26,30 @@ "ignoreFailures": true } ] + }, + { + "name": "Debug (不编译)", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/test/RTU", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set default output to hex", + "text": "set output-radix 16", + "ignoreFailures": true + } + ] } ] } \ No newline at end of file diff --git a/mimo/MEMORY.md b/mimo/MEMORY.md index 719510d..2f82ae9 100644 --- a/mimo/MEMORY.md +++ b/mimo/MEMORY.md @@ -69,22 +69,25 @@ _Hard constraints from user that every session must respect._ ## Architecture decisions _Major design choices with rationale. The "why" matters more than the "what" for future sessions._ -### 1. 数据中心信号变更检测 -`dc_signal.cpp` 使用 XXH128 哈希对信号快速索引(`signal_out`, `signal_in`, `signal_yk`, `signal_ao`, `signal_param` 五张表)。输出信号变更通过脏队列 + 去重 + 回调循环防护实现增量推送(仅变更时发送),`last_caller_module` 防止同模块回调自循环。 +### 1. 数据中心信号管理 +`dc_signal.cpp` 使用 XXH128 哈希对信号快速索引(`signal_out`, `signal_in`, `signal_yk`, `signal_ao`, `signal_param` 五张表,每表独立互斥锁)。输出信号变更通过脏队列 + 去重 + 比对 + 回调实现增量推送(仅变更时发送),`last_caller_module` 防止同模块回调自循环。SBO(Select Before Operate)控制流程:SELECT 暂存值 → DIRECT 校验与 SELECT 值一致 → 写入信号。事件队列(扰动/SOE/故障)使用 swap 模式低锁竞争 pop。 -### 2. WebSocket 多连接隔离 +### 2. 参数配置双文件机制 +`param.xml` 为参数模板(min/max/step/unit/default),不随运行修改。`self_param.xml` 由 datacenter 在运行时自动生成,存储当前信号值。初始化时值优先级:self_param.xml > param.xml value > param.xml default。`dc_param_cfg_check` 每 1000ms 检测变更标志,将 Ao/Param 信号值序列化写入 self_param.xml。 + +### 3. WebSocket 多连接隔离 每个 WebSocket 连接拥有独立的信号资源 session(per-connection),连接建立时开辟、断开时释放。全局共享方案会导致一个客户端的 add/del 操作影响其他客户端。 -### 3. MMS 客户端事件驱动模型 +### 4. MMS 客户端事件驱动模型 `mms_m.cpp` 使用事件队列 + 状态机模式,定时器驱动周期性操作(T0=120s all-call, T1=60s GI, T2=30s CO, T3=20s param)。RCB 订阅支持可配置化编号过滤。 -### 4. IEC 61850 服务器模型 +### 5. IEC 61850 服务器模型 `mms_s_icd.cpp` 解析 SCL XML ICD 文件构建完整数据模型。`mms_s_model.cpp` 动态创建 IedModel(LD/LN/DO/SDO/DA 树)。定值组管理通过 SGCB + SG/SE 镜像 DA 实现编辑区/运行区隔离。 -### 5. 通信通道统一抽象 +### 6. 通信通道统一抽象 `com_channel.cpp` 管理所有通信通道配置。`libcomm` 提供统一连接/发送/接收/断开接口,按类型分发到 TCP/UART/UDP 实现。ICP66 帧转换为 ICP67 格式。 -### 6. MQTT 通信 +### 7. MQTT 通信 使用自编译 mosquitto 2.x 客户端库(`libmy_mosquitto`),支持 MQTT v5 特性。 ## Discovered durable knowledge diff --git a/mimo/plan/datacenter模块分析.md b/mimo/plan/datacenter模块分析.md new file mode 100644 index 0000000..fd5e044 --- /dev/null +++ b/mimo/plan/datacenter模块分析.md @@ -0,0 +1,40 @@ +# Plan: datacenter 模块分析文档 + +## 目标 +为 `src/system/libdatacenter/` 模块撰写工程分析文档,存入 `mimo/工程/libdatacenter模块分析.md`。 + +--- + +## 步骤 + +### T1: 保存 plan +- 将本 plan 存入 `mimo/plan/datacenter模块分析.md` + +### T2: 补充读取 dc_signal.cpp 尾部 +- 从截断处读取 `src/system/libdatacenter/src/dc_signal.cpp` 剩余内容(SBO控制、参数变更检查、信号注册函数等) + +### T3: 撰写模块分析文档 +- 输出到 `mimo/工程/libdatacenter模块分析.md` +- 文档结构: + - 模块定位(datacenter 在 RTU 中的核心角色) + - 五张信号表(out/in/yk/ao/param)的存储结构 + - XXH128 哈希索引机制 + - 信号注册 API 详解(out/in/yk/ao/param 各接口) + - SBO 控制流程(Select→Check→Direct→Done) + - 增量推送机制(脏队列→去重→比对→回调→回路阻断) + - 事件队列系统(扰动/SOE/故障) + - 参数配置管理(param.xml/self_param.xml) + - 18 种数据类型支持 + - 线程安全设计 + +### T4: 更新 MEMORY.md +- 在项目记忆中添加 datacenter 模块的架构决策点 + +--- + +## 关键文件 +- **源文件**: `src/system/libdatacenter/src/dc_signal.cpp` (~2443行) +- **源文件**: `src/system/libdatacenter/src/dc_param.cpp` (342行) +- **源文件**: `src/system/libdatacenter/src/dc_event.cpp` (183行) +- **源文件**: `src/system/libdatacenter/src/datacenter.cpp` (51行) +- **输出**: `mimo/工程/libdatacenter模块分析.md` diff --git a/mimo/plan/libcomm与libtask分析.md b/mimo/plan/libcomm与libtask分析.md new file mode 100644 index 0000000..3dd60ad --- /dev/null +++ b/mimo/plan/libcomm与libtask分析.md @@ -0,0 +1,40 @@ +# Plan: libcomm + libtask 公共库分析 + +## 目标 +分析 `src/public/libcomm/` 和 `src/public/libtask/` 两个公共库,形成文档并指出优缺点。 + +--- + +## 步骤 + +### T1: 保存 plan +- 存入 `mimo/plan/libcomm与libtask分析.md` + +### T2: 撰写 libcomm 模块分析 +- 输出: `mimo/工程/libcomm模块分析.md` +- 覆盖: + - 统一通信抽象层架构(stru_comm + 工厂模式 + 类型分发) + - TCP 客户端/服务端实现(非阻塞 connect + select 事件循环) + - UART 实现(termios 配置 + send 被注释的分析) + - UDP 实现 + - fd 类型检测工具(get_fd_type / get_fd_info) + - 调试数据包 hex dump(含颜色) + - **优点**: 统一接口、自动重连、调试开关、fd 诊断 + - **缺点**: TCP 客户端空轮询 CPU、UART send 缺失、无内存释放、void* 类型不安全 + +### T3: 撰写 libtask 模块分析 +- 输出: `mimo/工程/libtask模块分析.md` +- 覆盖: + - 三个子系统:Event / MessageQueue / Timer + - Event: pthread_cond 事件标志(AND/OR/CLEAR/超时) + - MessageQueue: 环形缓冲区 + 长度头编码 + - Timer: POSIX timer_create(SIGEV_THREAD) 单次/周期 + - 引用计数销毁机制 + - **优点**: 纯 C + POSIX、零外部依赖、完备的超时支持、双条件 AND/OR + - **缺点**: timer 回调线程不安全、msg_queue 满写直接丢弃、事件无优先级 + +--- + +## 验证 +1. 确认 `mimo/工程/libcomm模块分析.md` 与 `mimo/工程/libtask模块分析.md` 已创建 +2. 检查文档包含优缺点章节 diff --git a/mimo/plan/libtask优化.md b/mimo/plan/libtask优化.md new file mode 100644 index 0000000..18840c8 --- /dev/null +++ b/mimo/plan/libtask优化.md @@ -0,0 +1,58 @@ +# Plan: libtask 优化 + +## 优化建议列表 + +基于《libtask模块分析.md》中 8 个缺点,按严重度排优先级: + +| # | 优化项 | 严重度 | 改动量 | 说明 | +|---|--------|--------|--------|------| +| 1 | Timer TOCTOU 修复 | 中 | 2行 | stop/destroy 等 handler 执行完毕再返回 | +| 2 | 消息队列阻塞 send | 中 | ~40行 | 新增 `task_msg_queue_send_timeout`,满时阻塞等待 | +| 3 | 事件查询接口 | 低 | ~15行 | 新增 `task_event_query`,不等待查询当前事件位 | +| 4 | 清理死代码 | 低 | ~10行 | 删除未使用的 `#include `、注释掉的 `non_block` 等 | +| 5 | Timer SIGEV_THREAD 保留 | — | 0行 | 评估后不修改(回调仅做 task_event_send,mutex 安全) | + +每个优化项独立、低风险、不改变现有 API 兼容性。 + +--- + +## 步骤 + +### T1: 保存 plan 到 mimo/plan/ + +### T2: 优化 #1 — Timer TOCTOU 修复 +- 在 `stru_task_timer` 新增 `int running` 字段 +- `task_timer_sig_handler`: `fun()` 前设 `running=1`,后设 `running=0` +- `task_timer_stop`: 置 `active=0` 后 `while(running) usleep(100)` 等 handler 完成 +- `task_timer_destroy`: 同 stop 的等待逻辑 + +### T3: 优化 #2 — 消息队列阻塞 send +- 新增 API: `int task_msg_queue_send_timeout(queue, msg, size, timeout_ms)` +- 逻辑: 满时 `pthread_cond_timedwait` → 有空位则写入 → signal 接收者 +- 在 `myTask.h` 中新增声明 + +### T4: 优化 #3 — 事件查询接口 +- 新增 API: `uint32_t task_event_query(stru_task_event_t p_event)` +- 逻辑: lock → 读取 events → unlock → 返回(不清除) +- 在 `myTask.h` 中新增声明 + +### T5: 优化 #4 — 清理死代码 +- 删除未使用头文件: `#include `, `#include `, `#include `, `#include ` +- 删除 `stru_task_msg_queue` 中注释掉的 `int non_block` + +### T6: 编译验证 +- 执行 `./release/build.sh` 确保所有改动编译零错误零警告 + +### T7: 更新分析文档 +- 更新 `mimo/工程/libtask模块分析.md` 的优缺点章节,反映优化结果 + +--- + +## 涉及文件 +- **修改**: `src/public/libtask/src/myTask.c`(核心修改) +- **修改**: `release/inc/myTask.h`(新增 API 声明) +- **修改**: `mimo/工程/libtask模块分析.md`(同步优缺点) + +## 验证 +1. `./release/build.sh` 编译通过 +2. 确认所有现有调用点(app_sys.cpp 中的 task_timer_create + task_event_send)不受影响 diff --git a/mimo/plan/libtask定时器SIGEV_THREAD优化.md b/mimo/plan/libtask定时器SIGEV_THREAD优化.md new file mode 100644 index 0000000..e9d04df --- /dev/null +++ b/mimo/plan/libtask定时器SIGEV_THREAD优化.md @@ -0,0 +1,151 @@ +# Plan: libtask 定时器优化 — timerfd 替代 SIGEV_THREAD + +**日期**: 2026-06-15 +**目标**: 消除定时器每次超时创建/销毁内核线程的开销(~1000线程/秒) + +--- + +## 问题根因 + +`myTask.c` 使用 `timer_create(CLOCK_REALTIME, SIGEV_THREAD, ...)` 模式,每次超时内核创建新线程执行回调。9 个 app × 3 个定时器(10/100/1000ms) = 27 个定时器,每秒约 1000 次线程创建/销毁。回调仅做 `task_event_send`(几微秒),线程创建开销远大于实际工作。 + +## 方案概要 + +用 Linux **timerfd** + **epoll** + **单例管理器线程** 替代 `SIGEV_THREAD`: + +- 每个定时器用 `timerfd_create` 创建文件描述符 +- 一个持久化 manager 线程用 `epoll_wait` 监听所有 timerfd +- timerfd 可读时在 manager 线程中调用原始回调 +- API 签名全部不变,调用方零修改 + +## 涉及文件 + +| 文件 | 改动 | +|------|------| +| `src/public/libtask/src/myTask.c` | **主要修改** — 重写定时器子系统 | +| `release/inc/myTask.h` | **无改动** — `stru_task_timer_t` 是不透明 `void*` | + +## 结构体变更 + +```c +// myTask.c 内部 (对外不可见) +typedef struct +{ + char name[64]; + int timerfd; // 替代 timer_t timerid + timer_func_cb fun; + void *arg; + uint32_t timeout_ms; + int flags; + int active; + int running; // 1=回调执行中 + int in_epoll; // 1=timerfd 已注册到 epoll + pthread_mutex_t mutex; + pthread_cond_t cond; // 新增: stop/destroy 等回调完成 + int ref_count; +} stru_task_timer; +``` + +变更: `timer_t timerid` → `int timerfd`,新增 `pthread_cond_t cond`、`int in_epoll` + +## 管理器线程设计 + +``` +全局状态: + static int g_epfd; // epoll 实例 + static int g_eventfd; // wakeup eventfd + static pthread_t g_mgr_thread; + static pthread_mutex_t g_mgr_mutex; // 串行化调用者 epoll_ctl + static int g_mgr_init_done; + +timer_manager_thread(): + g_epfd = epoll_create1(0) + g_eventfd = eventfd(0, EFD_NONBLOCK) + epoll_ctl(ADD, g_eventfd) // wakeup fd 永久在 epoll 中 + + while(1): + epoll_wait(events, -1) + for each ready fd: + if fd == g_eventfd: read() 清除; continue + read(fd, &expirations) // 清除过期计数 + lock(p_timer->mutex) + if active: + running = 1; unlock + fun(arg) // 调用原始回调 + lock + running = 0 + cond_broadcast(&cond) + if !PERIODIC: active = 0 + unlock +``` + +## 关键同步设计 + +### 1. 动态 epoll 增删 + +- **添加** (`task_timer_start`): 调用者持有 `g_mgr_mutex` 做 `epoll_ctl(ADD)`,然后 write eventfd 唤醒 manager +- **删除** (`task_timer_stop`): 调用者持有 `g_mgr_mutex` 做 `epoll_ctl(DEL)` +- Manager 线程**永不**做 `epoll_ctl`,避免跨线程 epoll 操作竞态 +- `g_mgr_mutex` 仅防护调用者之间并发的 epoll_ctl + +### 2. stop/destroy 同步(替代 usleep 自旋) + +用 `running` + `condvar` 替代当前 `while(running) usleep(1000)` 忙等: + +``` +stop 路径: + lock(mutex) + active = 0 + timerfd_settime(its={0,0}) // disarm + while(running): + cond_wait(&cond, &mutex) // 阻塞等待,零 CPU + if in_epoll: epoll_ctl(DEL) + unlock + +manager 路径 (回调结束后): + lock(mutex) + running = 0 + cond_broadcast(&cond) // 唤醒 stop 等待者 + if !PERIODIC: active = 0 + unlock +``` + +**锁顺序注意**: stop 中 `epoll_ctl(DEL)` 之前先 `unlock(&p->mutex)` 再 `lock(&g_mgr_mutex)`,避免与 start 路径的 `lock(&p->mutex)` → `lock(&g_mgr_mutex)` 形成 ABBA 死锁。 + +### 3. 惰性初始化 + +首次 `task_timer_start` 时调用 `timer_manager_ensure_init()`,用 `g_mgr_init_done` + `g_mgr_mutex` 做双重检查锁定,确保 manager 线程仅创建一次。 + +## API 实现概要 + +### task_timer_create +1. 参数校验 → calloc 结构体 → 初始化字段 +2. `timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK)` +3. 初始化 mutex + cond → 返回指针(未启动) + +### task_timer_start +1. `timer_manager_ensure_init()` 惰性初始化 manager +2. lock → `timerfd_settime` 设间隔 → 若 `!in_epoll` 则 `epoll_ctl(ADD)` + write eventfd → `active=1` → unlock + +### task_timer_stop +1. lock → `timerfd_settime({0,0})` disarms → `active=0` +2. `while(running) cond_wait` 等回调完成 +3. 若 `in_epoll`: unlock → lock g_mgr → `epoll_ctl(DEL)` → unlock g_mgr → lock → `in_epoll=0` → unlock + +### task_timer_destroy +1. 调用 `task_timer_stop`(含同步等待 + epoll 移除) +2. lock → `ref_count--`;若 >0 直接返回 +3. `close(timerfd)` → 销毁 mutex/cond → `free` + +### task_timer_restart +`stop()` → 更新 `timeout_ms` → `start()` + +### task_timer_is_active +lock → 读 `active` → unlock → 返回(不变) + +## 验证 + +1. `./release/build.sh` 编译零错误零警告 +2. 确认 `myTask.h` 未改动(API 透明兼容) +3. 确认 app_sys.cpp 等所有调用方无需修改(9 个 app 线程的定时器用法不变) +4. `./test/RTU` 启动运行,验证各 app 的 run_cnt 正常递增 diff --git a/mimo/plan/libtask缺陷修复计划.md b/mimo/plan/libtask缺陷修复计划.md new file mode 100644 index 0000000..6f94d6d --- /dev/null +++ b/mimo/plan/libtask缺陷修复计划.md @@ -0,0 +1,177 @@ +# libtask 缺陷修复计划 + +> **For agentic workers:** 此计划列出 libtask 事件和消息队列子系统的已知缺陷修复步骤。 + +**目标**: 修复 4 个缺陷 — use-after-free ×2、AND 位测试陷阱、cond_signal 稳健性 + +**涉及文件**: 仅 `src/public/libtask/src/myTask.c` + +--- + +### Task 1: 修复 `task_event_destroy` use-after-free + +**文件**: `src/public/libtask/src/myTask.c:106-131` + +- [ ] **Step 1: 修改 `task_event_destroy`** + +将 `free(p)` 后访问 `p->name` 改为先保存到栈变量: + +```c +int task_event_destroy(stru_task_event_t p_event) +{ + if(!p_event) + { + LOG_E("task_event_destroy error, p_event is NULL"); + return -1; + } + + stru_task_event *p = (stru_task_event *)p_event; + + pthread_mutex_lock(&p->mutex); + p->ref_count--; + if(p->ref_count > 0) + { + pthread_mutex_unlock(&p->mutex); + return 0; + } + + pthread_mutex_unlock(&p->mutex); + pthread_mutex_destroy(&p->mutex); + pthread_cond_destroy(&p->cond); + + LOG_I("task_event_destroy success, name:%s", p->name); + + free(p); + return 0; +} +``` + +**改动**: `free(p)` 下移到 `LOG_I` 之后。 + +- [ ] **Step 2: 编译验证** + +```bash +./release/build.sh +``` +预期: 编译通过,零错误零警告。 + +--- + +### Task 2: 修复 `task_msg_queue_destroy` use-after-free + +**文件**: `src/public/libtask/src/myTask.c:317-343` + +- [ ] **Step 1: 修改 `task_msg_queue_destroy`** + +同样将 `free(p)` 放到 `LOG_I` 之后: + +```c +int task_msg_queue_destroy(stru_task_msg_queue_t p_queue) +{ + if(!p_queue) + { + LOG_E("task_msg_queue_destroy error, p_queue is NULL"); + return -1; + } + + stru_task_msg_queue *p = (stru_task_msg_queue *)p_queue; + + pthread_mutex_lock(&p->mutex); + p->ref_count--; + if(p->ref_count > 0) + { + pthread_mutex_unlock(&p->mutex); + return 0; + } + + pthread_mutex_unlock(&p->mutex); + pthread_mutex_destroy(&p->mutex); + pthread_cond_destroy(&p->cond); + free(p->buffer); + + LOG_I("task_msg_queue_destroy success, name:%s", p->name); + + free(p); + return 0; +} +``` + +- [ ] **Step 2: 编译验证** + +```bash +./release/build.sh +``` +预期: 编译通过。 + +--- + +### Task 3: 修复 `task_event_recv` AND 检查用 `==` 而非位测试 + +**文件**: `src/public/libtask/src/myTask.c:182` + +- [ ] **Step 1: 修改 AND 条件判断** + +将第 182 行从 `==` 改为 `&`: + +```c + if(opt & TASK_EVENT_FLAG_AND) + { +``` + +- [ ] **Step 2: 编译验证** + +```bash +./release/build.sh +``` +预期: 编译通过。 + +--- + +### Task 4: `task_event_send` 改用 `cond_broadcast` + +**文件**: `src/public/libtask/src/myTask.c:145` + +- [ ] **Step 1: 修改 signal 为 broadcast** + +将第 145 行: + +```c + pthread_cond_signal(&p->cond); +``` + +改为: + +```c + pthread_cond_broadcast(&p->cond); +``` + +- [ ] **Step 2: 编译验证** + +```bash +./release/build.sh +``` +预期: 编译通过。 + +--- + +### Task 5: 最终验证 + +- [ ] **Step 1: 全量编译** + +```bash +./release/build.sh +``` +预期: `Build complete`,零错误零警告。 + +- [ ] **Step 2: 更新分析文档** + +在 `mimo/工程/libtask模块分析.md` 的缺点表中更新:标记 use-after-free 已修复、AND 位测试已修复。 + +--- + +## 未纳入修复的已知问题(调用方侧) + +| 问题 | 位置 | 说明 | +|------|------|------| +| 消息队列 send+event 回滚误取 | self_ptl.cpp:317/iec.cpp:336/com_scan.cpp:38 | `send` 成功但 `event_send` 失败时,`try_recv` 从队首取可能误删旧消息。这是调用方逻辑问题,非 libtask 缺陷,需调用方自行处理。 | +| `task_msg_queue_send_timeout` 未被使用 | — | API 已提供但无调用者,三个 send 调用点可考虑切换为 timeout 版本避免队列满丢数据。 | diff --git a/mimo/skills/模块分析文档生成/SKILL.md b/mimo/skills/模块分析文档生成/SKILL.md new file mode 100644 index 0000000..f0c7188 --- /dev/null +++ b/mimo/skills/模块分析文档生成/SKILL.md @@ -0,0 +1,74 @@ +--- +name: 模块分析文档生成 +description: Use when 用户要求读取某个模块源码并形成对应的模块分析文档,或要求分析某个 src/ 下的子模块 +--- + +# 模块分析文档生成 + +## 概述 + +读取项目 `src/` 下指定模块的全部源码,按统一结构生成工程分析文档,存入 `mimo/工程/`。 + +## 工作流程 + +1. 确认模块路径(`src/system//` 或 `src/protocol//` 或 `src/public//`) +2. 读取模块下 **全部** `.c`、`.h`、`.cpp`、`.hpp` 文件(header 和 source 都要) +3. 用自己的理解与分析撰写文档,**不照搬任何现有文档** +4. 输出到 `mimo/工程/<模块名>模块分析.md` + +## 文档结构 + +每篇分析文档必须包含以下章节: + +### 1. 模块定位 +- 该模块在 RTU 整体架构中的角色(系统层/协议层/公共库) +- 与上下层模块的关系(谁调用它、它调用谁) +- 运行方式(独立线程、定时器驱动、事件驱动等) + +### 2. 目录与文件清单 +- 列出模块的目录结构 +- 每个文件的职责一句话说明 + +### 3. 核心数据结构 +- 列出最重要的 struct/class +- 每个字段的用途与含义 +- 数据结构之间的关联关系 + +### 4. 核心流程 +- 初始化流程(从入口到就绪的调用链) +- 运行时主循环/事件处理流程 +- 关键操作的完整流程(如控制命令从发起到完成的路径) + +### 5. 对外接口 +- 列出所有公开 API 函数 +- 每个 API:函数签名、参数说明、返回值、副作用 + +### 6. 线程安全 +- 使用了哪些锁或同步机制 +- 哪些数据被共享、哪些是线程局部的 +- 并发模型说明 + +### 7. 已知问题与注意事项 +- 从源码中发现的设计限制或潜在问题 +- 特殊约定或非标准做法 + +## 代码引用规范 + +- 代码片段必须来自实际源码,不可臆造 +- 文档正文使用中文,代码块保持英文原样 +- 不要添加代码注释(中文注释也不行) + +## 输出路径 + +``` +mimo/工程/<模块名>模块分析.md +``` + +命名示例:`libdatacenter模块分析.md`、`libmms_m模块分析.md` + +## 完成标准 + +- 所有源码文件已读取 +- 文档涵盖全部 7 个章节 +- 代码示例来自实际源码 +- 文件已写入 `mimo/工程/` diff --git a/mimo/工程/libcomm模块分析.md b/mimo/工程/libcomm模块分析.md new file mode 100644 index 0000000..9a12d9d --- /dev/null +++ b/mimo/工程/libcomm模块分析.md @@ -0,0 +1,223 @@ +# libcomm 模块分析 + +**日期**: 2026-06-12 +**基于源码**: `src/public/libcomm/`(8个文件,约2200行) + +--- + +## 1. 模块定位 + +`libcomm` 是 RTU 的**统一通信抽象层**,位于公共库层。它将 TCP/UART/UDP 三种通信方式包装为统一的 C 接口,上层模块只需通过 `comm_id` 句柄操作连接、收发数据,不感知底层传输差异。 + +``` +上层(com_channel / icp67) + ↓ comm_create / comm_send / comm_recv_register +libcomm(本模块) + ↓ dispatch by CommType +TCP client / TCP server / UART / UDP client / UDP server +``` + +## 2. 架构设计:工厂模式 + 虚函数表 + +### 2.1 统一基类 `stru_comm` + +```cpp +typedef struct { + CommType type; + CommInit init; // no / ok + CommDebugShow debug_show; // on / off + void *p_comm; // 指向具体子类(stru_comm_tcp/uart/udp) + int (*comm_connect)(int id); + int (*comm_disconnect)(int id); + int (*comm_run)(int id); + int (*comm_state_register)(int id, comm_state_cb cb); + int (*comm_recv_register)(int id, comm_recv_cb cb); + int (*comm_send)(int id, const char *data, uint16_t len); +} stru_comm; +``` + +### 2.2 子类:TCP / UART / UDP + +每种通信方式有自己的结构体,包含: + +| 结构体 | 特有字段 | 函数指针表 | +|--------|---------|-----------| +| `stru_comm_tcp` | sockfd, sock_listen_fd, client_fd[16], p_para | tcp_client_connect / tcp_server_connect / tcp_send / tcp_close | +| `stru_comm_uart` | uart_fd, p_para | uart_connect / uart_send / uart_close | +| `stru_comm_udp` | sockfd, p_para | udp_client_connect / udp_server_connect / udp_send / udp_close | + +### 2.3 注册与管理 + +创建时分配 `*_create` 填充函数指针表 → `comm_create` 按 `CommType` 分派 → 全局 `g_comm_map`(map): + +```cpp +std::map g_comm_map; +static int g_comm_id = 0; +``` + +所有后续操作通过 `comm_id` 查找 `g_comm_map`,再按 `type` 向下转型调用具体子类的函数指针。 + +## 3. TCP 实现(comm_tcp.cpp,640行) + +### 3.1 客户端模式(`tcp_client_connect`) + +``` +while(1): + socket() → connect(非阻塞) → select(3秒超时) → SO_ERROR 检查 + → tcp_open(SO_REUSEADDR + TCP keepalive) + → 通知 state_cb(connected) + + while(sockfd 有效): + select(sockfd, 100ms超时) + → recv → recv_cb(id, fd, data, len) + → len <= 0 → close → 通知 state_cb(disconnected) → 外层循环重新连接 +``` + +**特点**: 自动断线重连(5秒等待),单连接模型,非阻塞 connect 避免永久阻塞。 + +### 3.2 服务端模式(`tcp_server_connect`) + +``` +tcp_listen → socket + bind + listen +while(1): + select(listen_fd + client_fd[]) + → listen_fd 可读 → accept → 分配 client_fd[16] 空闲槽位 + → client_fd 可读 → recv → recv_cb + → recv <= 0 → close + 通知 disconnected + 释放槽位 +``` + +**特点**: 最多 16 个并发客户端,select 阻塞等待(无超时),每个客户端 fd 分配独立槽位。`send` 失败时自动关闭对应连接。 + +### 3.3 Keepalive 配置 + +```cpp +SO_KEEPALIVE + TCP_KEEPIDLE(30s) + TCP_KEEPINTVL(5s) + TCP_KEEPCNT(3) +// 30秒无活动 → 开始探测 → 每5秒一次 → 3次失败断开 → 最长75秒检测断线 +``` + +### 3.4 send 实现 + +```cpp +send(fd, data, len, MSG_NOSIGNAL) +// 失败时: 关闭 fd → state_cb(disconnected) → 自动触发重连 +``` + +## 4. UART 实现(comm_uart.cpp,692行) + +### 4.1 初始化流程 + +```cpp +uart_connect: + → open(device, O_RDWR | O_NOCTTY) + → tcgetattr + 配置波特率/数据位/停止位/校验位 + → 原始模式: ~ICANON, ~ECHO + → 超时: VTIME=1(100ms), VMIN=0 +``` + +### 4.2 波特率支持 + +600 / 1200 / 2400 / 4800 / 9600 / 19200 / 38400 / 57600 / 115200 / 500000 / 1M / 2.5M + +### 4.3 运行时循环 + +```cpp +while(1): + read(uart_fd, buf, 1024) + → len > 0 → recv_cb(id, fd, buf, len) + → uart_sleep(10ms) +``` + +**当前实现**: 逐次 read → 逐次回调,不做帧拼装。注释掉的旧版本有超时分帧逻辑(20ms 空闲检测 + 动态帧缓冲区),但被废弃。 + +### 4.4 uart_send + +```cpp +write(uart_fd, tx, tx_len) +``` + +## 5. UDP 实现(comm_udp.cpp,298行) + +### 5.1 客户端 + +```cpp +socket(SOCK_DGRAM) → connect 到 remote:port → state_cb(connected) → udp_run +``` + +### 5.2 服务端 + +```cpp +socket → bind local:port → state_cb(connected) → udp_run +``` + +### 5.3 `udp_run` 接收循环 + +```cpp +while(1): + recvfrom(sockfd, buf, 2048) + → recv_cb(id, sockfd, buf, len) + → 出错时仅 continue,不触发重连 +``` + +### 5.4 send + +```cpp +sendto(fd, data, len, remote_ip:remote_port) +// 目标和 recv 使用相同参数,服务端回包无需知道对端地址 +``` + +## 6. fd 诊断工具(comm.cpp 尾部) + +### `get_fd_type(fd)` + +通过 `fstat` + `TIOCGSERIAL` 判断 fd 类型:socket / serial / pipe / file。 + +### `get_fd_info(fd, info)` + +根据类型填充详细信息: +- socket: `getsockname` + `getpeername` → local/remote IP + port +- serial: `readlink /proc/self/fd/N` → 设备路径 + `tcgetattr` → 波特率/数据位/停止位/校验 +- file: `fstat` → 文件大小 + `readlink` → 路径 + +## 7. 调试支持 + +```cpp +enum CommDebugShow { off, on }; + +static void comm_debug_show(str, dir, data, len): + → 方向前缀(rx/tx) + 颜色(绿色) + → 每 64 字节换行的 hex dump +``` + +## 8. 与 icp67 / com_channel 的集成 + +`com_channel.cpp` 通过 `myComm.h` 定义 `stru_tcp_para`、`stru_uart_para` 等参数结构体,调用 `comm_create` 创建通道实例,注册 `comm_recv_register` 和 `comm_state_register` 回调。收到的数据进入 icp67 帧解码。 + +--- + +## 9. 优点 + +| 优点 | 说明 | +|------|------| +| **统一接口** | 三种传输方式一视同仁,上层看不到 socket fd 和串口 fd 的区别 | +| **自动重连** | TCP 客户端断线后带 5 秒退避自动重连,无需上层管理 | +| **调试开关** | 配置级 hex dump + 彩色方向标识,定位通信问题极快 | +| **fd 诊断** | `get_fd_info` 可以运行时查看任何 fd 的完整信息(本地/远端 IP、串口参数等) | +| **TCP keepalive** | 配置了完整的心跳参数(30s idle + 3 次探测),能在 75 秒内检测 TCP 半开连接 | +| **虚函数表模式** | 通过函数指针表实现多态,避免 switch/if-else 类型检查 | +| **服务端多连接** | TCP server 支持最多 16 个并发客户端,每个客户端独立 fd | +| **UART 参数完整** | 波特率覆盖 600~2.5M,支持奇偶校验配置 | + +## 10. 缺点 + +| 缺点 | 严重度 | 说明 | +|------|--------|------| +| **TCP 客户端空轮询** | 中 | `tcp_client_connect` 循环中有 100ms select 超时无休眠,连接空闲时每 100ms 唤醒一次消耗 CPU | +| **TCP 服务端阻塞 select** | 低 | select 无超时,服务端空闲时线程永久阻塞,若需同时处理定时任务则无法在同一线程 | +| **UDP 接收无超时** | 中 | `recvfrom` 直接阻塞在 `udp_run`,无超时无退出机制 | +| **UART send 未完整实现** | 高 | 当前 uart_send 的注释掉代码占 400+ 行(含超时分帧逻辑),实际运行的 send 只是裸 write,无流量控制和错误恢复 | +| **无内存释放** | 中 | `comm_create` 分配的 `p_comm` 和注册的回调没有对应的 destroy 接口,长期运行可能泄漏 | +| **void* 类型擦除** | 中 | `p_comm` 是 `void*` 再强转为具体类型,编译器无法检测类型错误 | +| **uart_connect 直接阻塞** | 低 | 串口打开和配置在工作线程的 `while(1)` 循环中,没有与上层消息分发解耦 | +| **TCP client_fd 槽位管理粗糙** | 低 | 最多 16 个客户端,超出直接拒绝,没有等待队列或优雅降级 | +| **无流量控制** | 低 | send 没有检查 socket 缓冲区余量,高负载下可能 EAGAIN | +| **日志宏不统一** | 低 | 混用 `LOG_E`(C++ 风格)和 `MY_LOG_E`,部分日志无模块前缀 | diff --git a/mimo/工程/libdatacenter模块分析.md b/mimo/工程/libdatacenter模块分析.md new file mode 100644 index 0000000..fc2ed08 --- /dev/null +++ b/mimo/工程/libdatacenter模块分析.md @@ -0,0 +1,408 @@ +# libdatacenter 模块分析 + +**日期**: 2026-06-12 +**基于源码**: `src/system/libdatacenter/`(7个文件,约3000行) + +--- + +## 1. 模块定位 + +`libdatacenter` 是 RTU 的核心数据中枢,负责所有信号的存储、检索、变更检测、值校验和控制流程管理。它是整个系统的数据总线,所有模块(iec61850、self_ptl、web_server、com_channel)都通过它读写信号。 + +**核心职责**: +- 五大类信号的统一注册和存储(out/in/yk/ao/param) +- XXH128 哈希索引的高效信号查找 +- 输出信号增量变更检测(脏队列→去重→比对→回调) +- SBO(选择-执行)控制流程管理 +- 参数配置持久化(param.xml / self_param.xml 双文件机制) +- 事件队列系统(扰动/SOE/故障) +- 18 种数据类型的统一处理 + +## 2. 五张信号表 + +```cpp +static stru_datacenter g_datacenter = { + .signal_out, // 输出信号(遥测/遥信) + .signal_in, // 输入信号(链接到 out) + .signal_yk, // 遥控信号 + .signal_ao, // AO 信号(SP 定值、定值区号) + .signal_param, // 参数信号(多定值区) +}; +``` + +每张表的结构: +```cpp +typedef struct { + std::mutex mtx; // 表级互斥锁 + uint32_t signal_id; // 自增 ID 分配器 + hash_signal_map map_signals; // XXH128 hash → signal 的多重映射 + std::vector id_index; // ID → 指针 O(1) 索引 +} stru_signal_map; +``` + +## 3. XXH128 哈希索引 + +### 3.1 动机 + +信号以 `saddr`(字符串,如 `"PROT/LLN0$SG$sg1$StrVal"`)作为唯一标识。直接字符串比较在大量信号时效率低,因此使用 XXH3-128bits 对 saddr 计算 128 位哈希值,以哈希为键建立 `unordered_multimap`。 + +### 3.2 哈希碰撞处理 + +使用 `unordered_multimap`(而非 `unordered_map`),相同哈希值允许多个条目共存。查找时先通过哈希定位桶,再逐个比对 saddr 字符串精确匹配: + +```cpp +LOCAL stru_signal* dc_find_signal(const std::string &saddr, stru_signal_map &dc_signal_map) { + XXH128_hash_t hash = XXH3_128bits(saddr.c_str(), saddr.length()); + auto range = dc_signal_map.map_signals.equal_range(hash); + for (auto it = range.first; it != range.second; ++it) { + if (it->second.saddr == saddr) return &it->second; + } + return nullptr; +} +``` + +### 3.3 自定义哈希/相等函子 + +```cpp +struct XXH128Hash { + size_t operator()(const XXH128_hash_t& h) const noexcept { + // 低64位 << 17 + 高64位 >> 13,再混合 + const uint64_t mix = h.low64 ^ (h.high64<<17) + (h.high64>>13); + return mix ^ (mix >> 32); + } +}; + +struct XXH128Equal { + // 优先用库的 XXH128_isEqual,否则手动比较 high64 和 low64 +}; +``` + +## 4. 核心信号结构 + +```cpp +typedef struct stru_signal { + uint32_t id; // 自增 ID + XXH128_hash_t hash; // saddr 的 128 位哈希 + std::string saddr; // 短地址(信号唯一标识) + std::string desc; // 描述 + uint8_t data_type; // 数据类型(18 种之一) + std::vector vec_p_data; // 数据指针列表(多条目,如多定值区) + void *p_last_data; // 上次数据指针(用于变更检测) + std::vector vec_p_default_data; // 默认值指针列表 + std::vector link_saddrs; // 关联的 in 信号 saddr + std::unordered_set link_set; // O(1) 查重 + uint8_t ctrl_type; // 控制类型(SBO/Direct) + stru_signal_param param; // 参数元数据(min/max/step/unit) + std::vector> out_change_cb_list; // out 变更回调 + std::vector> change_cb_list; // 通用变更回调 + std::string last_caller_module; // 最后修改者模块(回路阻断) +} stru_signal; +``` + +## 5. 信号注册 API + +### 5.1 输出信号(out) + +```cpp +// 简单注册 +dc_signal_out(saddr, desc, data_type, p_data) +// 注册 + 变更回调 +dc_signal_out_with_callback(saddr, desc, data_type, p_data, cb, module_id) +// 后续绑定(已注册信号追加回调) +dc_signal_out_link_with_callback(saddr, &p_data, cb, module_id) +// 设置值(触发脏标记) +dc_set_out_signal_val(saddr, set_data, module_id) +``` + +### 5.2 输入信号(in) + +```cpp +dc_signal_in(saddr, desc, link_saddr, &p_data) +// link_saddr 指向一个已注册的 out 信号 +// in 信号直接共享 out 信号的数据指针,不上报变更回调 +``` + +### 5.3 遥控信号(yk) + +```cpp +dc_signal_yk(saddr, desc, data_type, ctrl_type, p_data, cb, module_id) +dc_signal_yk_link_with_callback(saddr, &p_data, cb, module_id) +dc_signal_yk_set_status(saddr, step, ctrl, p_data, module_id) +// ctrl_type: SIGNAL_CTRL_TYPE::SBO_NORMAL 或 DIRECT_NORMAL +// step: SELECT → check → DIRECT/CANCEL +``` + +### 5.4 AO 信号 + +```cpp +dc_signal_ao(saddr, desc, data_type, ctrl_type, p_data, p_default_data, cb, module_id) +dc_signal_ao_link_with_callback(saddr, &p_data, cb, module_id) +dc_signal_ao_set_val(saddr, step, ctrl, p_data, module_id) +// step SELECT 时存值到 ctrl.p_data;DIRECT 时写入 p_signal->vec_p_data[0] +``` + +### 5.5 Param 信号(多定值区) + +```cpp +dc_signal_param(saddr, desc, data_type, ctrl_type, pp_data, pp_default_data, data_num, cb, module_id) +dc_signal_param_link_with_callback(saddr, pp_data, data_num, cb, module_id) +dc_signal_param_set_val(saddr, step, ctrl, setting_zone, p_data, module_id) +dc_signal_param_set_val_without_check(saddr, data_type, setting_zone, p_data, module_id) +// setting_zone: 定值区号(0-based),写入 vec_p_data[setting_zone] +``` + +## 6. SBO 控制流程 + +SBO(Select Before Operate)是电力系统遥控的标准流程: + +``` +状态机: READY → SELECT → DIRECT/CANCEL → READY + +dc_check_ctrl_valid(p_signal, step, ctrl, p_data): + SELECT 步骤: + step == SELECT → 允许 + 更新 ctrl.p_data = p_data(暂存选择值) + ctrl.step → SELECT + + DIRECT 步骤: + 仅当 ctrl.step == SELECT 时允许 + dc_check_ctrl_val_valid(ctrl, p_data): + 校验 DIRECT 传入的值与 SELECT 暂存的值完全一致 + 如果不一致 → 拒绝执行 + 写入 p_signal->vec_p_data[0] + ctrl.step → READY + + CANCEL 步骤: + 仅当 ctrl.step == SELECT 时允许 + ctrl.step → READY + + DIRECT_NORMAL 模式(无 SBO): + step == DIRECT → 直接写入,无 SELECT 校验 +``` + +值校验覆盖 18 种数据类型,包括 IP 的 4 字节 memcmp、MAC 的 6 字节 memcmp、字符串的 strcmp 等。 + +## 7. 增量推送机制 + +### 7.1 脏信号队列 + +```cpp +static std::vector g_dirty_out_signals; // 脏队列 +static std::mutex g_dirty_out_mutex; + +static void dc_mark_signal_dirty(stru_signal *p_signal) { + std::lock_guard lock(g_dirty_out_mutex); + g_dirty_out_signals.push_back(p_signal); // 追加到队列 +} +``` + +调用 `dc_set_out_signal_val` 时自动标记脏。` + +### 7.2 变更检测(每 100ms 执行) + +```cpp +void dc_signal_out_change_check() { + // 1. swap 取出脏队列(锁内操作极短) + std::vector dirty; + { std::lock_guard lock(g_dirty_out_mutex); + dirty.swap(g_dirty_out_signals); } + + // 2. 去重:sort + unique + std::sort(dirty.begin(), dirty.end()); + auto last = std::unique(dirty.begin(), dirty.end()); + + // 3. 逐信号比对 + 回调 + for (auto it = dirty.begin(); it != last; ++it) { + if (dc_data_compare(data_type, vec_p_data[0], p_last_data) != 0) { + // 数据确实变化了 + for (auto &[module_id, cb] : out_change_cb_list) { + if (module_id == last_caller_module) continue; // 回路阻断 + cb(saddr, data_type, vec_p_data[0], p_last_data); + } + // 更新 last_data 为当前值 + dc_set_signal_val(p_last_data, data_type, vec_p_data[0]); + } + } +} +``` + +### 7.3 回路阻断 + +`last_caller_module` 记录最后一次修改该信号的模块 ID。变更检测时跳过该模块自己的回调,防止回路触发: + +``` +iec61850m 修改信号 → dc_set_out_signal_val → last_caller_module = "iec61850m" +→ 100ms 后 dc_signal_out_change_check: + 遍历回调: 跳过 module_id == "iec61850m" 的 + → iec61850s 的回调正常触发 +``` + +## 8. 事件队列系统 + +### 8.1 三种事件队列 + +| 队列 | 数据类型 | 用途 | +|------|---------|------| +| `g_disturb_dd_queue` | `stru_disturb_dd` | 扰动录波触发 | +| `g_soe_queue` | `stru_dc_soe` | SOE 事件记录 | +| `g_fault_queue` | `stru_dc_fault` | 故障记录(含 SOE 列表) | + +### 8.2 Push/Pop 模型 + +``` +生产者(任意线程): + dc_disturb_dd_queue_push(dd) + → lock → push → unlock + +消费者(datacenter_run_100ms,每 100ms): + dc_disturb_dd_queue_pop() + → lock → local_queue.swap(g_disturb_dd_queue) → unlock + → 遍历 local_queue → 调用所有注册的 pop_cb +``` + +- 使用 `swap` 替代逐个 pop,减少锁持有时间 +- 事件自动打时标(`gettimeofday`):如果传入 `sec=0, ms=0`,自动填入当前 Unix 时间 + +### 8.3 回调注册 + +```cpp +dc_disturb_dd_register_queue_pop(cb) +dc_event_register_queue_pop(cb) +dc_fault_register_queue_pop(cb) +``` + +各模块(self_ptl、iec61850m 等)注册回调,接收弹出的事件。 + +## 9. 参数配置管理 + +### 9.1 双文件机制 + +``` +param.xml → 参数模板(min/max/step/unit/num/default),不随运行修改 +self_param.xml → 运行时值存储(value),datacenter 自动生成 +``` + +### 9.2 初始化流程(datacenter_init) + +``` +1. 解析 param.xml → 提取元数据 → g_param_metadata(saddr → {min, max, step, unit}) +2. 构造 self_param.xml 路径(同目录,文件名替换) +3. 加载 self_param.xml 中的运行时值 → self_values map +4. 注册 Ao 信号: + - 值优先级: self_param.xml > param.xml value > param.xml default + - dc_signal_ao(..., SBO_NORMAL, p_data, p_default_data) +5. 注册 Param 信号(多定值区): + - 遍历 Item 子元素,每个 Item 一个定值区 + - dc_signal_param(..., pp_data, pp_default_data, num) +6. 标记 cfg_change = true,触发 self_param.xml 生成 +``` + +### 9.3 运行时持久化(dc_param_cfg_check) + +每 1000ms 检查 `g_param_cfg_change` 标志: + +``` +dc_param_cfg_check(path): + → 如果 cfg_change == false → 跳过 + → 遍历 signal_ao 表: 序列化所有 Ao 信号到 XML + → 遍历 signal_param 表: 序列化所有 Param 信号(含各定值区 Item) + → 写入 self_param.xml + → 设置 cfg_change = false +``` + +### 9.4 元数据注入 + +信号注册后自动从 `g_param_metadata` 查找并注入 `param` 元数据: + +```cpp +LOCAL void dc_signal_apply_metadata(stru_signal &signal) { + stru_signal_param param; + if (dc_param_metadata_lookup(signal.saddr, param)) + signal.param = param; // 注入 min/max/step/unit +} +``` + +## 10. 值校验(dc_check_val_valid) + +对写入值进行范围和类型校验,覆盖 18 种数据类型: + +| 类型 | 校验规则 | +|------|---------| +| `DATA_TYPE_B` | 必须为 0 或 1 | +| `DATA_TYPE_S8/U8/S16/U16/S32/U32` | 范围 `[param.min, param.max]` | +| `DATA_TYPE_L64/UL64` | 范围校验 | +| `DATA_TYPE_F32/D64` | 范围校验,直接比较(无 epsilon 容差) | +| `DATA_TYPE_IP/MAC/C8/C32/C64/C128/STR` | strcmp 比较是否变化 | +| `DATA_TYPE_C1` | 单字符直接比较 | + +**设计要点**: 所有类型校验先检查"值是否变化",未变化时拒绝写入(避免无效的持久化和回调触发)。 + +## 11. 数据类型系统 + +支持 18 种数据类型: + +| ID | 名称 | C 类型 | 字节 | +|----|------|--------|------| +| B | 布尔 | uint8_t | 1 | +| S8/U8 | 有/无符号 8 位 | int8_t/uint8_t | 1 | +| S16/U16 | 有/无符号 16 位 | int16_t/uint16_t | 2 | +| S32/U32 | 有/无符号 32 位 | int32_t/uint32_t | 4 | +| L64/UL64 | 有/无符号 64 位 | int64_t/uint64_t | 8 | +| F32/D64 | 浮点/双精度 | float/double | 4/8 | +| IP | IP 地址 | char[4] | 4 | +| MAC | MAC 地址 | char[6] | 6 | +| C1/C8/C32/C64/C128 | 定长字符 | char[N] | N | +| STR | 变长字符串 | char[128] | 128 | + +工具函数: +- `dc_get_data_type_str_by_id(id)` → 类型名称字符串 +- `dc_get_data_type_id_by_str(str)` → 类型 ID +- `dc_get_data_type_len(id)` → 类型字节长度 +- `dc_create_data_ptr_by_type(id)` → 创建新数据指针(new) +- `dc_delete_signal_data(p_data, id)` → 删除数据指针(delete) +- `dc_get_signal_val(p_data, id)` → 格式化为字符串 +- `dc_set_signal_val_from_str(p_data, id, str)` → 从字符串解析并设置 + +## 12. 定时调度 + +`datacenter` 的运行由 `app_sys` 线程驱动: + +``` +datacenter_run_100ms(): + → dc_signal_out_change_check() // 脏信号变更检测 + 回调 + → dc_disturb_dd_queue_pop() // 扰动事件消费 + → dc_event_queue_pop() // SOE 事件消费 + → dc_fault_queue_pop() // 故障事件消费 + +datacenter_run_1000ms(): + → dc_param_cfg_check(g_param_path) // 参数持久化(仅在变更时) +``` + +## 13. 调试命令 + +CLI 命令 `datacenter` 提供信号表查看功能: + +``` +datacenter out → 查看 output 信号 +datacenter in → 查看 input 信号 +datacenter yk → 查看遥控信号 +datacenter ao → 查看 AO 信号 +datacenter param → 查看 Param 信号 +datacenter all → 查看全部五张表 +``` + +支持 Tab 自动补全(out/in/yk/ao/param/all)。 + +每张表的显示包含:id、saddr、desc、type、当前值、关联的 in 信号列表。使用视觉宽度对齐(中文字符占 2 列)。 + +## 14. 线程安全 + +| 机制 | 保护对象 | +|------|---------| +| `stru_signal_map.mtx` | 每张信号表的读写 | +| `g_dirty_out_mutex` | 脏信号队列 `g_dirty_out_signals` | +| `g_param_cfg_change_mutex` | 参数变更标志 | +| 事件队列各 mutex | `g_disturb_dd_queue` 等 | +| 表级锁 + swap 模式 | pop 操作锁内仅 swap,处理在锁外 | + +**设计原则**: 所有锁持有时间极短,仅在操作容器时加锁,回调执行和数据处理在锁外完成。 diff --git a/mimo/工程/libtask模块分析.md b/mimo/工程/libtask模块分析.md new file mode 100644 index 0000000..028a95f --- /dev/null +++ b/mimo/工程/libtask模块分析.md @@ -0,0 +1,248 @@ +# libtask 模块分析 + +**日期**: 2026-06-15(更新) +**基于源码**: `src/public/libtask/src/myTask.c`(1个文件,913行) + +--- + +## 1. 模块定位 + +`libtask` 是 RTU 的**线程间同步原语库**,位于公共库层。它提供三个独立的子系统:事件标志、消息队列、定时器。全部基于 POSIX 标准 API(pthread、timer_create),零第三方依赖,纯 C 实现。 + +RTU 的 9 个应用线程(app_sys、app_cmd、app_comm_channel 等)全部通过 libtask 的原语进行同步和通信。 + +## 2. 三个子系统 + +### 2.1 事件标志(Task Event) + +**用途**: 线程间事件通知,支持多事件同时等待。 + +**核心结构**: +```c +typedef struct { + char name[64]; + uint32_t events; // 当前挂起的事件位掩码 + pthread_mutex_t mutex; + pthread_cond_t cond; + int ref_count; +} stru_task_event; +``` + +**API**: + +| 函数 | 说明 | +|------|------| +| `task_event_create(name)` | 创建事件对象,ref_count=1 | +| `task_event_send(event, bits)` | 设置事件位(OR 操作),signal 唤醒等待者 | +| `task_event_recv(event, set, opt, timeout_ms, &recved)` | 等待事件。opt: AND/OR + CLEAR | +| `task_event_clear(event, bits)` | 手动清除事件位 | +| `task_event_query(event)` | **新增** — 不等待,直接返回当前挂起的事件位 | +| `task_event_destroy(event)` | 引用计数减 1,归零则释放 | + +**等待逻辑**: +``` +task_event_recv(set, opt, timeout): + lock mutex + while 事件未满足: + if timeout == 0: → 返回 -1 (非阻塞) + if timeout == FOREVER: → pthread_cond_wait (永久阻塞) + else: → pthread_cond_timedwait (超时返回 -1) + if 满足: + OR 模式: 检查 (events & mask) == mask + AND 模式: 检查 events 包含所有 bits + if CLEAR flag: events &= ~mask (自动清除已捕获的事件) + unlock mutex +``` + +**关键设计**: 通过 `TASK_EVENT_FLAG_CLEAR` 实现边沿触发语义 —— 唤醒后自动清除,防止重复处理。 + +--- + +### 2.2 消息队列(Task Message Queue) + +**用途**: 线程间数据传递,环形缓冲区 + 长度前缀编码。 + +**核心结构**: +```c +typedef struct { + char name[64]; + uint8_t *buffer; // 环形缓冲区(msg_size × max_msgs) + uint32_t size; // 单条消息的最大大小 + uint32_t max_msgs; // 队列容量 + uint32_t msg_count; // 当前消息数 + uint32_t head; // 读指针 + uint32_t tail; // 写指针 + pthread_mutex_t mutex; + pthread_cond_t cond; + int ref_count; +} stru_task_msg_queue; +``` + +**消息编码格式**: + +每条消息在缓冲区中的格式为: +``` +[4字节 msg_len] [msg_len 字节 payload] +``` + +写入时先写长度再写数据,读时先读长度再读数据。这允许可变长度消息。 + +**API**: + +| 函数 | 说明 | +|------|------| +| `task_msg_queue_create(name, msg_size, msg_num)` | 创建队列,分配 `msg_size × msg_num` 字节缓冲区 | +| `task_msg_queue_send(queue, msg, size)` | 非阻塞写入。队列满时返回 -1 | +| `task_msg_queue_send_timeout(queue, msg, size, timeout_ms)` | **新增** — 写入。队列满时阻塞等待(支持 FOREVER / 定时 / 0=非阻塞) | +| `task_msg_queue_recv(queue, msg, size, timeout_ms)` | 阻塞读取(支持 FOREVER / 定时 / 0=try) | +| `task_msg_queue_try_recv(queue, msg, size)` | 非阻塞读取(内部调用 recv(0)) | +| `task_msg_queue_get_count(queue)` | 返回当前消息数 | +| `task_msg_queue_space(queue)` | 返回剩余空间 | +| `task_msg_queue_destroy(queue)` | 引用计数减 1,归零释放 | + +**关键行为**: +- `send` 满时**直接返回 -1**,不阻塞,不覆盖旧数据。生产者需自行处理 +- `recv` 队列空时 `pthread_cond_wait` 阻塞等待 +- 头部 4 字节长度前缀确保可变长度消息的正确读取 + +--- + +### 2.3 定时器(Task Timer) + +**用途**: 一次性或周期性定时任务。 + +**核心结构**: +```c +typedef struct { + char name[64]; + timer_t timerid; // POSIX timer ID + timer_func_cb fun; // 回调函数 + void *arg; // 回调参数 + uint32_t timeout_ms; // 超时毫秒 + int flags; // TASK_TIMER_FLAG_PERIODIC + int active; // 是否活跃 + pthread_mutex_t mutex; + int ref_count; +} stru_task_timer; +``` + +**定时器机制** ✅ **已优化 (2026-06-15)**: 使用 Linux **timerfd** + **epoll** + **单例管理器线程** 替代原 `SIGEV_THREAD` + +``` +task_timer_create → timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK) 创建 fd + → task_timer_start → timerfd_settime 装备 → epoll_ctl(ADD) 注册 + → epoll_wait 就绪 → read(fd) 清除过期计数 → fun(arg) 调用回调 + → 周期模式: timerfd 自动重触发 + → 单次模式: active=0,回调不再执行 +``` + +**API**: + +| 函数 | 说明 | +|------|------| +| `task_timer_create(name, cb, arg, timeout_ms, flags)` | 创建定时器(未启动) | +| `task_timer_start(timer)` | 启动定时器 | +| `task_timer_stop(timer)` | 停止定时器(设置 its={0,0}) | +| `task_timer_restart(timer, new_timeout)` | 修改超时并重启 | +| `task_timer_is_active(timer)` | 查询是否活跃 | +| `task_timer_destroy(timer)` | 引用计数减 1,归零删除 timer + 释放 | + +--- + +## 3. 引用计数机制 + +三个子系统都使用引用计数(`ref_count`)进行生命周期管理: + +```c +// 创建 +p->ref_count = 1; + +// 获取 → ref_count++ +// 不需要显式 get API,通过外部指针共享隐式增加 + +// 销毁 +lock mutex +p->ref_count--; +if (p->ref_count > 0) { unlock; return 0; } // 仍有使用者,不释放 +unlock mutex +free(p); // 最后一个使用者,释放 +``` + +**特点**: 多线程共享同一个事件/队列/定时器对象时,最后一个使用者释放。但没有显式的 `retain`/`release` API,ref_count 的实际增减依赖外部代码手动管理。 + +--- + +## 4. 辅助函数 + +```c +void task_sleep_ms(uint32_t ms): + // 使用 nanosleep 实现毫秒级休眠,支持 EINTR 中断后自动恢复 + struct timespec ts = {ms/1000, (ms%1000)*1000000}; + while(nanosleep(&ts, &ts) == -1 && errno == EINTR); +``` + +--- + +## 5. 在 RTU 中的应用 + +RTU 使用 libtask 实现线程调度框架 `myTask.h`(位于 `release/inc/`),定义: + +```c +// 在 myTask.h 中定义(不在 libtask 目录内) +#define EV_TIMER1 (1 << 0) // 10ms +#define EV_TIMER2 (1 << 1) // 100ms +#define EV_TIMER3 (1 << 2) // 1000ms +#define EV_TIMER4 (1 << 3) + +#define TASK_EVENT_WAIT_FOREVER ~0 +#define TASK_EVENT_FLAG_OR 0 +#define TASK_EVENT_FLAG_AND 1 +#define TASK_EVENT_FLAG_CLEAR 2 + +#define TASK_TIMER_FLAG_PERIODIC 1 +#define TASK_TIMER_FLAG_ONE_SHOT 0 + +typedef void *(*task_thread_func)(void *arg); +``` + +每个应用线程的主循环模式: +```cpp +while (1) { + task_event_recv(p_event, EV_TIMER1 | EV_TIMER2 | EV_TIMER3, + TASK_EVENT_FLAG_OR | TASK_EVENT_FLAG_CLEAR, + TASK_EVENT_WAIT_FOREVER, &event); + if (event & EV_TIMER1) { /* 10ms 任务 */ } + if (event & EV_TIMER2) { /* 100ms 任务 */ } + if (event & EV_TIMER3) { /* 1000ms 任务 */ } +} +``` + +--- + +## 6. 优点 + +| 优点 | 说明 | +|------|------| +| **纯 POSIX + Linux 原生** | 事件/消息队列用 pthread,定时器用 timerfd+epoll,无第三方库依赖,交叉编译无障碍 | +| **单线程管理所有定时器** | ✅ **已优化 (2026-06-15)** — 一个持久化 epoll 线程管理全部 27 个定时器,消除原 SIGEV_THREAD 每秒 ~1000 次线程创建/销毁 | +| **stop/destroy 零 CPU 同步** | ✅ **已优化 (2026-06-15)** — condvar 替代自旋,`while(running) cond_wait` 阻塞等待无 CPU 开销 | +| **完备的超时支持** | 事件和消息队列都支持三种模式:永久等待 / 指定超时 / 非阻塞立即返回 | +| **双条件事件** | 支持 AND(全部触发)和 OR(任一触发)两种等待模式,灵活匹配不同场景 | +| **边沿触发** | `TASK_EVENT_FLAG_CLEAR` 自动在等待返回时清除事件位,防止重复处理 | +| **可变长度消息** | 消息队列通过 4 字节长度前缀编码,支持不同大小的消息共用同一队列 | +| **完备的队列查询** | `get_count` 和 `space` 提供队列状态查询,生产者可据此做背压决策 | +| **引用计数生命周期** | 多线程共享对象时安全释放,不会出现 use-after-free | +| **代码紧凑** | 全部功能在 913 行 C 代码中,易于审计和理解 | + +## 7. 缺点 + +| 缺点 | 严重度 | 说明 | +|------|--------|------| +| **定时器 destroy use-after-free** | 高 | ✅ **已修复 (2026-06-15)**: `task_timer_destroy` 中原 `free(p)` 后在 LOG 中访问 `p->name`,`free` 移到 LOG 之后 | +| **消息队列 destroy use-after-free** | 高 | ✅ **已修复 (2026-06-15)**: `task_msg_queue_destroy` 同上 | +| **事件 destroy use-after-free** | 高 | ✅ **已修复 (2026-06-15)**: `task_event_destroy` 同上 | +| **事件 recv AND 位测试陷阱** | 中 | ✅ **已修复 (2026-06-15)**: `opt == TASK_EVENT_FLAG_AND` 改为 `opt & TASK_EVENT_FLAG_AND`,避免 AND\|CLEAR 组合被误识别 | +| **事件 send 用 signal 非 broadcast** | 低 | ✅ **已修复 (2026-06-15)**: `pthread_cond_signal` 改为 `pthread_cond_broadcast`,多等待者场景更稳健 | +| **无事件优先级** | 低 | `task_event_recv` 按位掩码匹配,所有事件平等。多事件同时触发无优先级排序 | +| **ref_count 缺乏原子性** | 低 | 引用计数通过 mutex 保护无 CAS,极高频场景下瓶颈(本场景不适用) | +| **头文件不在模块内** | 低 | `myTask.h` 位于 `release/inc/`,新开发者可能找不到 | diff --git a/mimo/问题处理文档.md b/mimo/问题处理文档.md index db56cf6..d22f7f8 100644 --- a/mimo/问题处理文档.md +++ b/mimo/问题处理文档.md @@ -2,6 +2,49 @@ --- +## 2026-06-15 + +### #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 补全子命令前缀丢失 diff --git a/release/inc/myTask.h b/release/inc/myTask.h index 6b1e4ae..c8cc675 100644 --- a/release/inc/myTask.h +++ b/release/inc/myTask.h @@ -38,6 +38,7 @@ int task_event_destroy(stru_task_event_t p_event); int task_event_send(stru_task_event_t p_event, uint32_t event); int task_event_recv(stru_task_event_t p_event, uint32_t set, uint32_t opt, uint32_t timeout_ms, uint32_t *recved); int task_event_clear(stru_task_event_t p_event, uint32_t event); +uint32_t task_event_query(stru_task_event_t p_event); // msg_queue /********************************************************************/ @@ -48,6 +49,7 @@ typedef void *stru_task_msg_queue_t; stru_task_msg_queue_t task_msg_queue_create(const char *name, uint32_t msg_size, uint32_t msg_num); int task_msg_queue_destroy(stru_task_msg_queue_t p_queue); int task_msg_queue_send(stru_task_msg_queue_t p_queue, const void *msg, uint32_t len); +int task_msg_queue_send_timeout(stru_task_msg_queue_t p_queue, const void *msg, uint32_t len, uint32_t timeout_ms); int task_msg_queue_recv(stru_task_msg_queue_t p_queue, void *msg, uint32_t len, uint32_t timeout_ms); int task_msg_queue_try_recv(stru_task_msg_queue_t p_queue, void *msg, uint32_t size); uint32_t task_msg_queue_get_count(stru_task_msg_queue_t p_queue); diff --git a/src/public/libtask/src/myTask.c b/src/public/libtask/src/myTask.c index 84a9a79..561a283 100644 --- a/src/public/libtask/src/myTask.c +++ b/src/public/libtask/src/myTask.c @@ -2,14 +2,14 @@ #include #include #include +#include -#include #include -#include -#include -#include #include #include +#include +#include +#include #include "myLog.h" #include "myTask.h" @@ -36,20 +36,22 @@ typedef struct uint32_t tail; pthread_mutex_t mutex; pthread_cond_t cond; - // int non_block; int ref_count; }stru_task_msg_queue; typedef struct { char name[64]; - timer_t timerid; + int timerfd; timer_func_cb fun; void *arg; uint32_t timeout_ms; int flags; int active; + int running; + int in_epoll; pthread_mutex_t mutex; + pthread_cond_t cond; int ref_count; }stru_task_timer; @@ -122,9 +124,10 @@ int task_event_destroy(stru_task_event_t p_event) pthread_mutex_unlock(&p->mutex); pthread_mutex_destroy(&p->mutex); pthread_cond_destroy(&p->cond); - free(p); LOG_I("task_event_destroy success, name:%s", p->name); + + free(p); return 0; } @@ -140,7 +143,7 @@ int task_event_send(stru_task_event_t p_event, uint32_t event) pthread_mutex_lock(&p->mutex); p->events |= event; - pthread_cond_signal(&p->cond); + pthread_cond_broadcast(&p->cond); pthread_mutex_unlock(&p->mutex); // LOG_I("task_event_send success, name:%s, event:%d", p->name, event); @@ -177,7 +180,7 @@ int task_event_recv(stru_task_event_t p_event, uint32_t set, uint32_t opt, uint3 while(1) { - if(opt == TASK_EVENT_FLAG_AND) + if(opt & TASK_EVENT_FLAG_AND) { matched = set == (p->events & set); } @@ -249,6 +252,23 @@ int task_event_clear(stru_task_event_t p_event, uint32_t event) return 0; } +uint32_t task_event_query(stru_task_event_t p_event) +{ + if(!p_event) + { + LOG_E("task_event_query error, p_event is NULL"); + return 0; + } + + stru_task_event *p = (stru_task_event *)p_event; + + pthread_mutex_lock(&p->mutex); + uint32_t events = p->events; + pthread_mutex_unlock(&p->mutex); + + return events; +} + //////////////////////////////////////////////////////////////////////////////////////// @@ -317,9 +337,10 @@ int task_msg_queue_destroy(stru_task_msg_queue_t p_queue) pthread_mutex_destroy(&p->mutex); pthread_cond_destroy(&p->cond); free(p->buffer); - free(p); LOG_I("task_msg_queue_destroy success, name:%s", p->name); + + free(p); return 0; } @@ -362,6 +383,80 @@ int task_msg_queue_send(stru_task_msg_queue_t p_queue, const void *msg, uint32_t return 0; } +int task_msg_queue_send_timeout(stru_task_msg_queue_t p_queue, const void *msg, uint32_t size, uint32_t timeout_ms) +{ + if(!p_queue || !msg || size == 0) + { + LOG_E("task_msg_queue_send_timeout error, p_queue:%p, msg:%p, size:%d", p_queue, msg, size); + return -1; + } + + stru_task_msg_queue *p = (stru_task_msg_queue *)p_queue; + + struct timespec ts; + int ret = 0; + + pthread_mutex_lock(&p->mutex); + + if(p->msg_count >= p->max_msgs) + { + if(timeout_ms == 0) + { + pthread_mutex_unlock(&p->mutex); + return -1; + } + + if(timeout_ms != TASK_EVENT_WAIT_FOREVER) + { + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += timeout_ms / 1000; + ts.tv_nsec += (timeout_ms % 1000) * 1000000; + if(ts.tv_nsec >= 1000000000) + { + ts.tv_sec++; + ts.tv_nsec -= 1000000000; + } + } + + while(p->msg_count >= p->max_msgs) + { + if(timeout_ms == TASK_EVENT_WAIT_FOREVER) + { + ret = pthread_cond_wait(&p->cond, &p->mutex); + } + else + { + if(ETIMEDOUT == (ret = pthread_cond_timedwait(&p->cond, &p->mutex, &ts))) + { + pthread_mutex_unlock(&p->mutex); + return -1; + } + } + + if(ret != 0) + { + pthread_mutex_unlock(&p->mutex); + return -1; + } + } + } + + uint8_t *p_data = p->buffer + (p->tail * p->size); + + memcpy(p_data, &size, sizeof(size)); + p_data += sizeof(size); + + memcpy(p_data, msg, size); + + p->tail = (p->tail + 1) % p->max_msgs; + p->msg_count++; + + pthread_cond_signal(&p->cond); + pthread_mutex_unlock(&p->mutex); + + return 0; +} + int task_msg_queue_recv(stru_task_msg_queue_t p_queue, void *msg, uint32_t size, uint32_t timeout_ms) { if(!p_queue || !msg || size == 0) @@ -493,29 +588,121 @@ uint32_t task_msg_queue_space(stru_task_msg_queue_t p_queue) //////////////////////////////////////////////////////////////////////////////////////// -static void task_timer_sig_handler(union sigval sv) +static int g_timer_epfd = -1; +static int g_timer_eventfd = -1; +static pthread_t g_timer_mgr_thread; +static pthread_mutex_t g_timer_mgr_mutex = PTHREAD_MUTEX_INITIALIZER; +static int g_timer_mgr_init_done = 0; + + +static void *timer_manager_thread(void *arg) { - stru_task_timer *p_timer = (stru_task_timer *)sv.sival_ptr; + struct epoll_event ev; + struct epoll_event events[64]; - if(p_timer && p_timer->fun) + g_timer_epfd = epoll_create1(0); + if(0 > g_timer_epfd) { - pthread_mutex_lock(&p_timer->mutex); - if(p_timer->active) - { - p_timer->fun(p_timer->arg); - - if(!(p_timer->flags & TASK_TIMER_FLAG_PERIODIC)) - { - p_timer->active = 0; - } - } - pthread_mutex_unlock(&p_timer->mutex); + LOG_E("timer_manager_thread epoll_create1 failed"); + return NULL; } + + g_timer_eventfd = eventfd(0, EFD_NONBLOCK); + if(0 > g_timer_eventfd) + { + LOG_E("timer_manager_thread eventfd failed"); + close(g_timer_epfd); + return NULL; + } + + memset(&ev, 0, sizeof(ev)); + ev.events = EPOLLIN; + ev.data.ptr = NULL; + if(0 != epoll_ctl(g_timer_epfd, EPOLL_CTL_ADD, g_timer_eventfd, &ev)) + { + LOG_E("timer_manager_thread epoll_ctl ADD eventfd failed"); + close(g_timer_eventfd); + close(g_timer_epfd); + return NULL; + } + + while(1) + { + int nfds = epoll_wait(g_timer_epfd, events, 64, -1); + if(0 > nfds) + { + if(EINTR == errno) + { + continue; + } + LOG_E("timer_manager_thread epoll_wait failed"); + break; + } + + for(int i = 0; i < nfds; i++) + { + if(NULL == events[i].data.ptr) + { + uint64_t val; + read(g_timer_eventfd, &val, sizeof(val)); + continue; + } + + stru_task_timer *p_timer = (stru_task_timer *)events[i].data.ptr; + uint64_t expirations; + read(p_timer->timerfd, &expirations, sizeof(expirations)); + + pthread_mutex_lock(&p_timer->mutex); + if(p_timer->active) + { + p_timer->running = 1; + pthread_mutex_unlock(&p_timer->mutex); + + p_timer->fun(p_timer->arg); + + pthread_mutex_lock(&p_timer->mutex); + p_timer->running = 0; + pthread_cond_broadcast(&p_timer->cond); + + if(!(p_timer->flags & TASK_TIMER_FLAG_PERIODIC)) + { + p_timer->active = 0; + } + } + pthread_mutex_unlock(&p_timer->mutex); + } + } + + return NULL; } + +static void timer_manager_ensure_init(void) +{ + if(g_timer_mgr_init_done) + { + return; + } + + pthread_mutex_lock(&g_timer_mgr_mutex); + if(0 == g_timer_mgr_init_done) + { + if(0 != pthread_create(&g_timer_mgr_thread, NULL, timer_manager_thread, NULL)) + { + LOG_E("timer_manager_ensure_init pthread_create failed"); + pthread_mutex_unlock(&g_timer_mgr_mutex); + return; + } + pthread_detach(g_timer_mgr_thread); + g_timer_mgr_init_done = 1; + } + pthread_mutex_unlock(&g_timer_mgr_mutex); +} + + stru_task_timer_t task_timer_create(const char *name, timer_func_cb fun_cb, void *arg, uint32_t timeout_ms, int flags) { - if(!name || !fun_cb || timeout_ms == 0) + if(!name || !fun_cb || 0 == timeout_ms) { LOG_E("task_timer_create error, name:%s, fun_cb:%p, timeout_ms:%d", name, fun_cb, timeout_ms); return NULL; @@ -534,28 +721,23 @@ stru_task_timer_t task_timer_create(const char *name, timer_func_cb fun_cb, void p_timer->timeout_ms = timeout_ms; p_timer->flags = flags; p_timer->active = 0; + p_timer->running = 0; + p_timer->in_epoll = 0; p_timer->ref_count = 1; pthread_mutex_init(&p_timer->mutex, NULL); + pthread_cond_init(&p_timer->cond, NULL); - struct sigevent sev; - memset(&sev, 0, sizeof(sev)); - - sev.sigev_notify = SIGEV_THREAD; - sev.sigev_value.sival_ptr = p_timer; - sev.sigev_notify_function = task_timer_sig_handler; - sev.sigev_notify_attributes = NULL; - - if(timer_create(CLOCK_REALTIME, &sev, &p_timer->timerid) != 0) + p_timer->timerfd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK); + if(0 > p_timer->timerfd) { + pthread_cond_destroy(&p_timer->cond); pthread_mutex_destroy(&p_timer->mutex); free(p_timer); - LOG_E("task_timer_create error, timer_create failed"); + LOG_E("task_timer_create error, timerfd_create failed"); return NULL; } - // LOG_I("task_timer_create success, name:%s, fun_cb:%p, arg:%p, timeout_ms:%d, flags:%d", name, fun_cb, arg, timeout_ms, flags); - return (stru_task_timer_t)p_timer; } @@ -569,6 +751,8 @@ int task_timer_start(stru_task_timer_t p_timer) stru_task_timer *p = (stru_task_timer *)p_timer; + timer_manager_ensure_init(); + struct itimerspec its; pthread_mutex_lock(&p->mutex); @@ -587,19 +771,39 @@ int task_timer_start(stru_task_timer_t p_timer) its.it_interval.tv_nsec = 0; } - if(timer_settime(p->timerid, 0, &its, NULL) != 0) + if(0 != timerfd_settime(p->timerfd, 0, &its, NULL)) { pthread_mutex_unlock(&p->mutex); - LOG_E("task_timer_start error, timer_settime failed"); + LOG_E("task_timer_start error, timerfd_settime failed"); return -1; } + if(0 == p->in_epoll) + { + struct epoll_event ev; + memset(&ev, 0, sizeof(ev)); + ev.events = EPOLLIN; + ev.data.ptr = p; + + pthread_mutex_lock(&g_timer_mgr_mutex); + if(0 != epoll_ctl(g_timer_epfd, EPOLL_CTL_ADD, p->timerfd, &ev)) + { + pthread_mutex_unlock(&g_timer_mgr_mutex); + pthread_mutex_unlock(&p->mutex); + LOG_E("task_timer_start error, epoll_ctl ADD failed"); + return -1; + } + p->in_epoll = 1; + pthread_mutex_unlock(&g_timer_mgr_mutex); + + uint64_t one = 1; + write(g_timer_eventfd, &one, sizeof(one)); + } + p->active = 1; pthread_mutex_unlock(&p->mutex); - // LOG_I("task_timer_start success, p_timer:%p", p_timer); - return 0; } @@ -617,24 +821,28 @@ int task_timer_stop(stru_task_timer_t p_timer) pthread_mutex_lock(&p->mutex); - its.it_value.tv_sec = 0; - its.it_value.tv_nsec = 0; + memset(&its, 0, sizeof(its)); - its.it_interval.tv_sec = 0; - its.it_interval.tv_nsec = 0; - - if(timer_settime(p->timerid, 0, &its, NULL) != 0) - { - pthread_mutex_unlock(&p->mutex); - LOG_E("task_timer_stop error, timer_settime failed"); - return -1; - } + timerfd_settime(p->timerfd, 0, &its, NULL); p->active = 0; - pthread_mutex_unlock(&p->mutex); + while(p->running) + { + pthread_cond_wait(&p->cond, &p->mutex); + } - LOG_I("task_timer_stop success, p_timer:%p", p_timer); + if(p->in_epoll) + { + pthread_mutex_unlock(&p->mutex); + pthread_mutex_lock(&g_timer_mgr_mutex); + epoll_ctl(g_timer_epfd, EPOLL_CTL_DEL, p->timerfd, NULL); + pthread_mutex_unlock(&g_timer_mgr_mutex); + pthread_mutex_lock(&p->mutex); + p->in_epoll = 0; + } + + pthread_mutex_unlock(&p->mutex); return 0; } @@ -668,6 +876,8 @@ int task_timer_destroy(stru_task_timer_t p_timer) stru_task_timer *p = (stru_task_timer *)p_timer; + task_timer_stop(p_timer); + pthread_mutex_lock(&p->mutex); p->ref_count--; if(p->ref_count > 0) @@ -677,12 +887,11 @@ int task_timer_destroy(stru_task_timer_t p_timer) } pthread_mutex_unlock(&p->mutex); - timer_delete(p->timerid); + close(p->timerfd); + pthread_cond_destroy(&p->cond); pthread_mutex_destroy(&p->mutex); free(p); - LOG_I("task_timer_destroy success, p_timer:%p", p_timer); - return 0; } diff --git a/src/system/libweb_server/src/packed_fs.c b/src/system/libweb_server/src/packed_fs.c index a4c5342..f79ba45 100644 --- a/src/system/libweb_server/src/packed_fs.c +++ b/src/system/libweb_server/src/packed_fs.c @@ -3444,11 +3444,11 @@ static const unsigned char v8[] = { }; const struct mg_mem_file mg_packed_files[] = { - {"/css/style.css", v3, sizeof(v3) - 1, 1781072856}, - {"/index.html", v4, sizeof(v4) - 1, 1781073518}, - {"/js/app.js", v5, sizeof(v5) - 1, 1781073909}, - {"/js/monitor.js", v6, sizeof(v6) - 1, 1781072788}, - {"/js/pages.js", v7, sizeof(v7) - 1, 1781073958}, - {"/js/ws-client.js", v8, sizeof(v8) - 1, 1781071573}, + {"/css/style.css", v3, sizeof(v3) - 1, 1781250466}, + {"/index.html", v4, sizeof(v4) - 1, 1781250466}, + {"/js/app.js", v5, sizeof(v5) - 1, 1781250466}, + {"/js/monitor.js", v6, sizeof(v6) - 1, 1781250466}, + {"/js/pages.js", v7, sizeof(v7) - 1, 1781250466}, + {"/js/ws-client.js", v8, sizeof(v8) - 1, 1781250466}, {NULL, NULL, 0, 0} };