<修改> 1、使用mimo优化程序,增加问题处理文档;2、调整优化libtask模块
This commit is contained in:
parent
f4b84506a6
commit
0a7a3c49b3
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
@ -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. 检查文档包含优缺点章节
|
||||
|
|
@ -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 <mqueue.h>`、注释掉的 `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 <mqueue.h>`, `#include <sys/msg.h>`, `#include <signal.h>`, `#include <sched.h>`
|
||||
- 删除 `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)不受影响
|
||||
|
|
@ -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 正常递增
|
||||
|
|
@ -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 版本避免队列满丢数据。 |
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
---
|
||||
name: 模块分析文档生成
|
||||
description: Use when 用户要求读取某个模块源码并形成对应的模块分析文档,或要求分析某个 src/ 下的子模块
|
||||
---
|
||||
|
||||
# 模块分析文档生成
|
||||
|
||||
## 概述
|
||||
|
||||
读取项目 `src/` 下指定模块的全部源码,按统一结构生成工程分析文档,存入 `mimo/工程/`。
|
||||
|
||||
## 工作流程
|
||||
|
||||
1. 确认模块路径(`src/system/<module>/` 或 `src/protocol/<module>/` 或 `src/public/<module>/`)
|
||||
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/工程/`
|
||||
|
|
@ -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<int, stru_comm>):
|
||||
|
||||
```cpp
|
||||
std::map<int, stru_comm> 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`,部分日志无模块前缀 |
|
||||
|
|
@ -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<stru_signal*> 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<void*> vec_p_data; // 数据指针列表(多条目,如多定值区)
|
||||
void *p_last_data; // 上次数据指针(用于变更检测)
|
||||
std::vector<void*> vec_p_default_data; // 默认值指针列表
|
||||
std::vector<std::string> link_saddrs; // 关联的 in 信号 saddr
|
||||
std::unordered_set<std::string> link_set; // O(1) 查重
|
||||
uint8_t ctrl_type; // 控制类型(SBO/Direct)
|
||||
stru_signal_param param; // 参数元数据(min/max/step/unit)
|
||||
std::vector<std::pair<std::string, out_signal_change_cb>> out_change_cb_list; // out 变更回调
|
||||
std::vector<std::pair<std::string, signal_change_cb>> 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<stru_signal*> g_dirty_out_signals; // 脏队列
|
||||
static std::mutex g_dirty_out_mutex;
|
||||
|
||||
static void dc_mark_signal_dirty(stru_signal *p_signal) {
|
||||
std::lock_guard<std::mutex> 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<stru_signal*> dirty;
|
||||
{ std::lock_guard<std::mutex> 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,处理在锁外 |
|
||||
|
||||
**设计原则**: 所有锁持有时间极短,仅在操作容器时加锁,回调执行和数据处理在锁外完成。
|
||||
|
|
@ -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/`,新开发者可能找不到 |
|
||||
|
|
@ -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 补全子命令前缀丢失
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sched.h>
|
||||
#include <pthread.h>
|
||||
#include <mqueue.h>
|
||||
#include <sys/msg.h>
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
#include <sys/timerfd.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/eventfd.h>
|
||||
|
||||
#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,17 +588,82 @@ uint32_t task_msg_queue_space(stru_task_msg_queue_t p_queue)
|
|||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void task_timer_sig_handler(union sigval sv)
|
||||
{
|
||||
stru_task_timer *p_timer = (stru_task_timer *)sv.sival_ptr;
|
||||
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;
|
||||
|
||||
if(p_timer && p_timer->fun)
|
||||
|
||||
static void *timer_manager_thread(void *arg)
|
||||
{
|
||||
struct epoll_event ev;
|
||||
struct epoll_event events[64];
|
||||
|
||||
g_timer_epfd = epoll_create1(0);
|
||||
if(0 > g_timer_epfd)
|
||||
{
|
||||
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;
|
||||
|
|
@ -511,11 +671,38 @@ static void task_timer_sig_handler(union sigval sv)
|
|||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue