RTU/mimo/工程/libtask模块分析.md

9.5 KiB
Raw Blame History

libtask 模块分析

日期: 2026-06-15更新
基于源码: src/public/libtask/src/myTask.c1个文件913行


1. 模块定位

libtask 是 RTU 的线程间同步原语库,位于公共库层。它提供三个独立的子系统:事件标志、消息队列、定时器。全部基于 POSIX 标准 APIpthread、timer_create零第三方依赖纯 C 实现。

RTU 的 9 个应用线程app_sys、app_cmd、app_comm_channel 等)全部通过 libtask 的原语进行同步和通信。

2. 三个子系统

2.1 事件标志Task Event

用途: 线程间事件通知,支持多事件同时等待。

核心结构:

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

用途: 线程间数据传递,环形缓冲区 + 长度前缀编码。

核心结构:

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

用途: 一次性或周期性定时任务。

核心结构:

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)进行生命周期管理:

// 创建
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 APIref_count 的实际增减依赖外部代码手动管理。


4. 辅助函数

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/),定义:

// 在 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);

每个应用线程的主循环模式:

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_countspace 提供队列状态查询,生产者可据此做背压决策
引用计数生命周期 多线程共享对象时安全释放,不会出现 use-after-free
代码紧凑 全部功能在 913 行 C 代码中,易于审计和理解

7. 缺点

缺点 严重度 说明
定时器 destroy use-after-free 已修复 (2026-06-15): task_timer_destroy 中原 free(p) 后在 LOG 中访问 p->namefree 移到 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/,新开发者可能找不到