Skip to content

Latest commit

 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

EventScheduling — 基于环形任务队列的简单事件调度器

A Simple Event Scheduler Using a Circular Task Queue

English

特性

  • 多实例支持:不透明句柄模式,可同时创建多个独立的调度器实例
  • 双模式任务管理:静态数组模式(编译期确定容量)和动态分配模式(运行时弹性扩展)
  • 依赖注入:内存分配器通过回调注入,核心模块不依赖 stdlib.h(仅保留 memset/memcpy
  • 入侵式链表:基于 Linux 内核风格的 c-linked-list,类型安全、零额外分配
  • TMOS 友好:非阻塞 TmosPoll 接口适用于协作式多任务调度(无中断保存上下文)
  • 事件区间校验:任务支持连续区间 [start, end) 或回绕区间 start > end
  • 内置时基:注入 tick 源即可获得 1ms 心跳广播(EVT_TICK)与软件定时器(单次/周期),无需应用侧手写 tick 广播样板

架构

┌─────────────────────────────────────────────┐
│                  main.c                     │
│   ┌─────────┐   ┌────────────────────────┐  │
│   │  MGP    │──▶│ EventSchedul_Allocator│  │
│   │ 内存池  │   │  {malloc, free}        │  │
│   └─────────┘   └───────────┬────────────┘  │
├──────────────────────────────┼──────────────┤
│                  EventSchedul               │
│   ┌──────────────┐  ┌───────┴───────────┐   │
│   │  taskPool[]  │  │   ringTaskQueue   │   │
│   │ (静态数组)   │  │   (环形缓冲区)     │   │
│   └──────────────┘  └───────────────────┘   │
│   ┌──────────────────────────────────────┐  │
│   │         c-linked-list                │  │
│   │     (入侵式双向链表,无堆分配)        │  │
│   └──────────────────────────────────────┘  │
└─────────────────────────────────────────────┘

快速开始

#include "EventSchedul.h"

/* 任务回调 */
static void myTask(EventSchedul_EventId evt, void* arg) {
    printf("Received event: 0x%04x\n", evt);
}

int main(void) {
    /* 创建调度器实例,注入 malloc/free */
    EventSchedul_Context* ctx = EventSchedul_Create(
        &(EventSchedul_Allocator){malloc, free});

    /* 注册任务(监听事件区间 [0x0001, 0x0010)) */
    EventSchedul_TaskNode cfg = {
        .pTaskFunc = myTask,
        .pTaskFuncArg = NULL,
        .info = { .eventStart = 0x0001, .eventEnd = 0x0010 }
    };
    EventSchedul_TaskNode* handle = EventSchedul_TaskRegister(ctx, &cfg);

    /* 投递事件 */
    EventSchedul_setEventToTask(ctx, handle, 0x0005);

    /* TMOS 非阻塞轮询 */
    EventSchedul_TmosPoll(ctx); // → myTask(0x0005, NULL)

    /* 清理 */
    EventSchedul_TaskUnRegister(ctx, handle);
    EventSchedul_Destroy(ctx);
    return 0;
}

内置时基(tick 源 + EVT_TICK 广播 + 软件定时器)

调度器内置时基,把"SysTick 心跳 → EVT_TICK 广播 → 软件定时器"整合进组件, 应用不再需要手写 tick 广播样板(参考门禁 tickBroadcast / 键盘 idle 钩子)。

#include "EventSchedul.h"

static volatile EventSchedul_TickCount g_tick_ms = 0;   /* SysTick ISR 递增 */
static EventSchedul_TickCount sysTick_getTick(void) { return g_tick_ms; }

/* 订阅 EVT_TICK: 事件区间包含 EVTSCHEDUL_TICK_EVT 即订阅时基广播 */
static void onTick(EventSchedul_EventId evt, void* arg) {
    /* evt == EVTSCHEDUL_TICK_EVT(1), 每 tick 调用一次 */
}

int main(void) {
    EventSchedul_Context* ctx = EventSchedul_Create(
        &(EventSchedul_Allocator){malloc, free});

    EventSchedul_RegTickSource(ctx, sysTick_getTick);   /* 注入 tick 源 */

    EventSchedul_TaskNode cfg = {
        .pTaskFunc = onTick,
        .info = { .eventStart = EVTSCHEDUL_TICK_EVT,
                  .eventEnd   = EVTSCHEDUL_TICK_EVT + 1 }   /* 订阅广播 */
    };
    EventSchedul_TaskNode* h = EventSchedul_TaskRegister(ctx, &cfg);

    /* 软件定时器内嵌于任务节点(每任务一个):
     * 单次 100 tick 后向 h 投 0x0100; 周期 500 tick 投 0x0200(须分挂不同任务) */
    EventSchedul_TimerStart(ctx, h, 0x0100, 100);
    ...
    EventSchedul_TimerStop(ctx, h);
}
  • ISR 安全:遵循"中断只置标志"原则——tick 计数器由 SysTick ISR 递增, 调度器只在 TmosPoll/MainLoop 主循环上下文中读取并广播,无需加锁。
  • 订阅即区间:任务的事件区间含 EVTSCHEDUL_TICK_EVT(1) 即订阅 EVT_TICK, 无需额外订阅表。
  • 绝对 deadline:定时器基于 tick 源绝对计数,主循环卡顿漏检仍能按期触发。
  • 每任务一个定时器:定时器内嵌于任务节点(不透明指针,计数状态私有), 容量天然 == 任务数;重复启动同一任务定时器会覆盖旧定时器。
  • 周期保相位:周期定时器到期后推进到下一个绝对触发点,即使主循环卡顿跨过 多个相位,也每轮只触发一次(不突发),且下次触发仍对齐绝对相位(不漂移)。

长任务/不均等轮询(协作式调度的时基边界)

时基靠主循环轮询检测推进,若某个任务回调执行时间较长,会使各轮检测到的 tick 差非均等。本组件对这类场景的行为与对策:

场景 行为 对策
单次定时器 绝对 deadline,延迟但不错过 无需处理
周期定时器 每轮最多触发一次,保相位不漂移 无需处理(不突发、不积累)
EVT_TICK 计数 每轮最多广播一次,长任务下"数 EVT_TICK"会少算 改用绝对 tick 分频(见下)
队列满 入队失败静默 保持任务回调尽量短

EVT_TICK 只是唤醒触发,不是精确计数器。需要精确计时的任务应改用 EventSchedul_GetTick() 的绝对计数做分频/超时,即使一轮跨过多个 tick 也准确:

static void task(EventSchedul_EventId evt, void* arg) {
    EventSchedul_Context* ctx = (EventSchedul_Context*)arg;
    static EventSchedul_TickCount last = 0;
    EventSchedul_TickCount now = EventSchedul_GetTick(ctx);
    if (now - last >= 10) {        /* 每 10 tick 处理一次, 卡顿不丢 */
        last = now;
        /* ... */
    }
}

边界说明:主循环卡顿期间任务本身无法被唤醒,做不到"卡顿时立刻响应"; 但一旦唤醒,GetTick 能精确告知流逝了多少 tick。如需严格实时(硬期限 抢占),需改为 RTOS/中断驱动调度,超出本协作式调度组件的范畴。

API 参考

函数 说明
EventSchedul_Create(allocator) 创建调度器实例,注入内存分配器
EventSchedul_Destroy(ctx) 销毁实例,释放所有资源
EventSchedul_TaskRegister(ctx, cfg) 注册任务,返回句柄
EventSchedul_TaskUnRegister(ctx, handle) 注销任务
EventSchedul_setEventToTask(ctx, handle, evt) 向任务投递事件(入队)
EventSchedul_TmosPoll(ctx) 非阻塞取一个事件并执行(TMOS 适用)
EventSchedul_MainLoop(ctx) 阻塞式主循环(需先 RegSleepMethod
EventSchedul_RegSleepMethod(ctx, fn) 注册阻塞主循环的休眠回调
EventSchedul_RegTickSource(ctx, getTick) 注入 tick 源回调(NULL 注销时基)
EventSchedul_GetTick(ctx) 获取当前 tick 计数
EventSchedul_TimerStart(ctx, task, evt, delay) 单次定时器:delay tick 后向 task 投 evt,返回错误码
EventSchedul_TimerStartPeriodic(ctx, task, evt, period) 周期定时器:每 period tick 向 task 投 evt,返回错误码
EventSchedul_TimerStop(ctx, task) 停止 task 的内嵌定时器(幂等)

返回值

typedef enum {
    EVTSCHEDUL_OK         = 0,   // 成功
    EVTSCHEDUL_ERR_FAIL,         // 通用失败
    EVTSCHEDUL_ERR_ARG,          // 参数错误
    EVTSCHEDUL_ERR_MEM,          // 内存不足
    EVTSCHEDUL_ERR_NOTHING,      // 无数据
    EVTSCHEDUL_ERR_EVENT,        // 事件错误(不在区间内)
    EVTSCHEDUL_ERR_TASK,         // 任务错误(未注册)
} EventSchedul_ErrCode;

类型定义

类型 底层 说明
EventSchedul_EventId unsigned short 事件编号 (0x0000–0xFFFF)
EventSchedul_TaskId short 任务编号
EventSchedul_ExecCount unsigned short 执行次数计数器
EventSchedul_Allocator {malloc_fn, free_fn} 注入的内存分配器
EventSchedul_TickCount uint32_t tick 计数(由应用定义 tick→时间映射)
EventSchedul_Timer 不透明 内嵌于任务节点的软件定时器(计数状态私有)

配置宏

宏定义位于 include/EventSchedul.h

默认值 说明
EVTSCHEDUL_TASKS_MODE EVTSCHEDUL_STATIC 任务管理模式:EVTSCHEDUL_STATIC(0) / EVTSCHEDUL_DYNAMIC(1);ll_t 仅动态模式使用
EVTSCHEDUL_TASKS_MAX 8 静态模式最大任务数
EVTSCHEDUL_TASKS_QUEUE_MAX EVTSCHEDUL_TASKS_MAX * 2 环形事件队列深度
EVTSCHEDUL_TIME_BASE_ENABLE 1 内置时基开关(0=全部编译剔除,行为与旧版一致)
EVTSCHEDUL_TICK_EVT 1 保留时基事件 ID(EVT_TICK,可 -D 覆盖)
EVTSCHEDUL_INVALID_EVT 0xFFFF 无效事件号
EVTSCHEDUL_INVALID_TASK_ID -1 无效任务编号

定时器容量恒等于任务数(每任务内嵌一个),无独立定时器池配置宏。

编译

mkdir build && cd build
cmake .. -G Ninja          # 或用 -G "MinGW Makefiles"
cmake --build .

产物在 build/bin/EventScheduling.exe

运行测试

.\build\bin\EventScheduling.exe

内置 15 组测试用例,覆盖所有公共 API(121 个断言)。

第三方库致谢

仓库 用途
ringBuffer PING020903/ringBuffer 环形 FIFO 缓冲区,用于事件队列
MemoryGroundPlus PING020903/MemoryGroundPlus 内存池,动态模式下替代 malloc
c-linked-list embeddedartistry/c-linked-list Linux 内核风格入侵式双向链表

目录结构

EventScheduling/
├── include/
│   ├── EventSchedul.h          # 公共头文件
│   └── DBG_macro.h             # 调试宏
├── sources/
│   ├── EventSchedul.c          # 调度器核心实现
│   ├── main.c                  # 示例入口
│   └── test.c                  # API 测试套件
├── thirdparty/
│   ├── ringBuffer/             # 环形缓冲区
│   ├── memoryGroundPlus/       # 内存池
│   └── c-linked-list/          # 入侵式链表
├── CmakeLists.txt
└── README.md

About

A Simple Event Scheduler Using a Circular Task Queue

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages