xt-sdk 文档 v1.0.3
xt-sdk 嵌入式 SDK API 参考
载入中...
搜索中...
未找到
xt_task - 协程式任务调度

版本:0.2 | 日期:2026-06-26 | 路径: components/xt_task/xt_task.h


一、概述

xt_task 是 xt-sdk 的协程式任务调度模块,基于 timer2 和 event 实现的轻量级协程。通过 LC(Local Continuation)行号跳转方案,在单线程内实现任务的挂起/恢复,无需 OS 线程支持。

1.1 设计原则

  • 静态数组管理 :所有 task 槽位在 xt_task_array[XT_TASK_NUM] 中预分配,无动态内存
  • 单线程协作 :所有 task 在同一线程执行,无临界区保护
  • 父子任务两级 :支持父任务调用子任务并等待完成,不支持孙子任务
  • 事件/定时器双通道唤醒 :task 可同时等待事件和超时,先到先得
  • 幂等初始化 : xt_task_setup() 多次调用安全,仅首次生效

1.2 依赖

依赖 用途
components/xt_task/xt_timer2.h 软件定时器:delay 和 wait_until 的超时管理
components/xt_task/xt_event.h 事件系统:subscribe + publish + handler
components/xt_std/xt_list.h 双向链表:timer2 工作链表
xtiny.h 基础类型、assert 宏、错误码

二、核心概念

2.1 LC(Local Continuation)机制

LC 通过行号跳转实现协程的挂起/恢复。每个 task 有一个 lc 字段( volatile int ):

  • lc == 0 — 初始状态或已完成
  • lc != 0 — 挂起点行号,resume 时跳转到对应 case 标签

LC 机制基于 switch-case + _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\\"\") 实现,无需汇编或 setjmp/longjmp。

2.2 任务对象

task 对象内嵌定时器和事件等待字段,支持双通道唤醒:

2.3 挂起与恢复

协程通过宏挂起(yield),通过回调恢复:

  1. 挂起:设置 lc 行号 → return 退出函数
  2. 恢复:timer2 超时回调或 event handler 重新调用 task entry → lc resume 跳转到挂起点

yield 后局部变量丢失 :除 XT_TASK_YIELD_FLAG 外,所有局部变量在 yield 后不保留。跨 yield 的状态应通过 user_data 或全局/静态变量传递。


三、API 参考

以下为函数签名大纲,完整的参数说明、返回值、注意事项请查看 xt_task(协程式任务调度) 。

3.1 系统管理

函数签名 说明
xt_err_t xt_task_setup(void) 初始化 task 子系统(幂等,清零数组+初始化 event/timer2)
void xt_task_sys_reset(void) 重置 task 子系统(仅测试用,不触碰 event/timer2)

3.2 任务生命周期

函数签名 说明
xt_task_t *xt_task_start(xt_task_entry_t entry, void *user_data) 启动协程任务(分配槽位+首次调用)
xt_task_t *xt_task_running(void) 获取当前正在运行的 task(NULL=无)
bool xt_task_is_running(xt_task_t *task) 检查 task 是否在运行(含 yield 等待中)

3.3 协程框架宏

宏签名 说明
XT_TASK_BEGIN() 协程任务函数开头(声明 yield 标志+恢复执行点)
XT_TASK_END() 协程任务函数结尾(闭合 switch+重置 lc)

3.4 挂起宏

宏签名 说明
xt_task_delay(ms) 延时等待毫秒后继续执行
xt_task_wait_event(event_id, event_param) 等待指定事件(永不超时)
xt_task_wait_until(event_id, timeout) 等待指定事件或超时(先到先得)

xt_task_wait_until 唤醒后通过 xt_event_get_current_event_id() 判断唤醒原因:非 INVALID=事件唤醒,INVALID=超时唤醒。

使用示例:

static void wait_task(void *user_data) {
xt_task_wait_until(EVT_DATA_READY, 5000);
printf("data received\n");
} else {
printf("timeout!\n");
}
}
#define XT_EVENT_INVALID_ID
无效事件 ID,兼作空闲槽位标记
xt_event_id_t xt_event_get_current_event_id(void)
获取当前正在处理的事件 ID
#define XT_TASK_END()
协程任务函数结尾
#define XT_TASK_BEGIN()
协程任务函数开头
#define xt_task_wait_until(event_id, timeout)
等待指定事件或超时

3.5 子任务宏

宏签名 说明
xt_task_call_subtask(entry, user_data) 调用子任务并等待其完成(不支持孙任务)

使用示例:

static void subtask(void *user_data) {
printf("subtask start\n");
xt_task_delay(1); /* 必须至少 yield 一次 */
printf("subtask done\n");
}
static void parent_task(void *user_data) {
printf("before subtask\n");
xt_task_call_subtask(subtask, NULL);
printf("after subtask\n");
}
#define xt_task_call_subtask(entry, user_data)
调用子任务并等待其完成
#define xt_task_delay(ms)
延时等待若干毫秒后继续执行

3.6 内部 API(用户不应直接调用)

函数签名 说明
void xt_task_timer_start(uint32_t timeout_ms) 启动当前 task 的定时器(由 delay/wait_until 间接调用)
void xt_task_event_subscribe(xt_event_id_t event_id, uint32_t timeout_ms) 为当前 task 订阅事件(由 wait_event/wait_until 间接调用)
void xt_task_event_handler(xt_event_id_t event_id) 事件到达分发入口(由 xt_event_handler 调用)

四、事件+超时协作机制

xt_task_wait_until 同时启动定时器和订阅事件,两个通道均可唤醒 task,但只会触发一次——先到先得。

4.1 事件先到达

timeline:
t=0 task 调用 xt_task_wait_until(EVT_A, 200)
→ 订阅 EVT_A + 启动定时器 200ms
→ lc yield,task 挂起
t=50 xt_event_publish(EVT_A) + xt_event_handler()
→ xt_task_event_handler(EVT_A)
→ xt_timer2_stop(&task->timer) // 停止定时器
→ task->wait_event = INVALID // 清除等待
→ _xt_task_call(task) // 恢复 task
→ lc resume 跳转到 case 标签后
→ xt_event_get_current_event_id() != INVALID → 事件唤醒

4.2 超时先到达

timeline:
t=0 task 调用 xt_task_wait_until(EVT_A, 200)
→ 订阅 EVT_A + 启动定时器 200ms
t=200 timer2 超时回调 _xt_task_timer_cb()
→ task->wait_event = INVALID // 清除等待
→ _xt_task_call(task) // 恢复 task
→ lc resume 跳转到 case 标签后
→ xt_event_get_current_event_id() == INVALID → 超时唤醒
t=250 xt_event_publish(EVT_A) 到达 // 此时 task 已不在等待
→ xt_task_event_handler 遍历,wait_event 不匹配,忽略

4.3 互斥保证

关键点: 事件和超时服务函数都会先清除互斥状态再唤醒 task 。

唤醒路径 操作
事件到达 xt_timer2_stop 停止定时器 → 清除 wait_event → resume
超时到达 回调中清除 wait_event (定时器节点已自动摘下)→ resume

因此两个唤醒源不可能同时触发同一个 task 的 resume。

4.4 纯事件等待(xt_task_wait_event)

xt_task_wait_event 传入 XT_TASK_WAIT_FOREVER ,不启动定时器。task 只能由事件唤醒。


五、父子任务机制

5.1 工作流程

1. 父任务调用 xt_task_call_subtask(subtask, data)
├─ xt_task_start(subtask, data) 分配槽位
│ ├─ s_xt_task_current = parent_task
│ ├─ parent_id != INLIVAD? 检查孙任务限制
│ └─ 设置 child.parent_id = parent.task_id
│
├─ _xt_task_call(&child)
│ ├─ ptr_parent_task = s_xt_task_current (保存父任务)
│ ├─ s_xt_task_current = &child
│ ├─ child.entry(child.user_data) // 执行子任务
│ │ 子任务执行到 yield → return 挂起
│ └─ s_xt_task_current = ptr_parent_task (恢复)
│
└─ 父任务 call_subtask 宏中 XT_TASK_YIELD_FLAG == 0 → return 挂起
2. 子任务完成后(_xt_task_call 中)
├─ if (child.lc == 0)
│ ├─ uint8_t parent_id = child.parent_id
│ ├─ xt_timer2_stop(&child.timer)
│ ├─ memset(child, 0, sizeof(xxxx)) // 回收子任务槽位
│ ├─ s_xt_task_current = &xt_task_array[parent_id] // 切到父任务
│ └─ parent.entry(parent.user_data) // 恢复父任务执行
│ ├─ 若 parent.lc == 0 → 父任务也完成 → 回收
│ └─ 若 parent.lc != 0 → 父任务在 yield 中 → resume 继续

5.2 关键规则

  1. 子任务必须 yield :若子任务立即完成(无 delay/wait),在 _xt_task_call 内部会递归调用父任务。这是设计上允许但不推荐的行为,可能导致调用栈增长。
  2. 不支持孙任务 : _xt_task_alloc 中检查 parent_id != XT_TASK_INLIVAD_ID ,触发 XT_GRANDCHILD_NOT_SUPPORT assert。
  3. 两级 cleanup :子任务完成 → 回收子任务槽位 → 恢复父任务。若父任务也完成(lc==0),同样回收父任务槽位。

六、常见模式

6.1 主循环模板

#include "xt_task.h"
#include "xt_event.h"
#include "xt_timer2.h"
void xt_main(void)
{
/* 1. 初始化 */
/* 2. 注册事件回调(接收外部事件) */
xt_event_subscribe(XT_EVENT_UART_RX, uart_rx_callback, NULL);
/* 3. 启动常驻任务 */
xt_task_start(io_task, NULL);
xt_task_start(ui_task, NULL);
/* 4. 主循环 */
while (1) {
/* 先处理事件队列(含 task 唤醒) */
/* 再处理定时器(含 task delay 唤醒) */
if (remain == UINT32_MAX) {
/* 无活跃定时器,等待事件 */
osDelay(1000);
} else if (remain > 0) {
/* 等待最近超时 */
osDelay(remain);
}
/* remain == 0:刚处理完到期定时器,立即重调 handler */
}
}
xt_err_t xt_event_subscribe(xt_event_id_t event_id, xt_event_cb_t event_cb, void *user_data)
订阅事件(去重匹配)
xt_err_t xt_event_handler(void)
处理事件队列中的所有事件
xt_task_t * xt_task_start(xt_task_entry_t entry, void *user_data)
启动一个协程任务
xt_err_t xt_task_setup(void)
初始化 task 子系统
xt_tick2_t xt_timer2_handler(void)
定时器主循环处理函数,需在主循环中周期性调用
uint32_t xt_tick2_t
tick 类型,32 位无符号
事件发布订阅模块接口(去重匹配、dispatch 上下文栈、临界区保护)
协程式任务调度模块接口(基于 timer2 和 event 的轻量级协程)
新版软件定时器接口(对象化、单链表、new/delete 生命周期)

6.2 完整任务示例

/* ---- 延时任务 ---- */
static void led_blink_task(void *user_data)
{
while (1) {
led_on();
led_off();
}
}
/* ---- 事件驱动任务 ---- */
static void button_task(void *user_data)
{
void *param;
while (1) {
xt_task_wait_event(EVT_BTN_PRESS, param);
handle_button_press((int)(uintptr_t)param);
}
}
/* ---- 带超时等待 ---- */
static void wait_ack_task(void *user_data)
{
send_command();
xt_task_wait_until(EVT_ACK_RECEIVED, 3000);
handle_ack();
} else {
handle_timeout();
}
}
/* ---- 状态机序列 ---- */
static void seq_task(void *user_data)
{
void *param;
step_start();
step_one();
xt_task_wait_event(EVT_STEP2_DONE, param);
step_two((int)(uintptr_t)param);
step_finish();
}
#define xt_task_wait_event(event_id, event_param)
等待指定事件(永不超时)

6.3 子任务示例

/* 子任务:发送数据并等待 ACK */
static void send_data_subtask(void *user_data)
{
uint8_t *data = (uint8_t *)user_data;
uart_send(data, sizeof(data));
xt_task_delay(1); /* 必须 yield,防止递归重入 */
xt_task_wait_until(EVT_UART_TX_DONE, 5000);
printf("send ok\n");
}
}
/* 父任务:依次发送多条数据 */
static void master_task(void *user_data)
{
uint8_t buf[16];
xt_task_call_subtask(send_data_subtask, buf);
printf("first send complete\n");
xt_task_call_subtask(send_data_subtask, buf + 8);
printf("second send complete\n");
}

七、限制与注意事项

限制 说明
不支持孙任务 子任务中不能再调用 xt_task_call_subtask (触发 XT_GRANDCHILD_NOT_SUPPORT assert)
子任务必须 yield 子任务必须至少一次 delay/wait_event/wait_until(推荐 xt_task_delay(1) ),否则立即完成时递归调用父任务
单线程协作 所有 task 在同一线程执行,无临界区保护。长时间计算会阻塞所有其他 task
task 数组满 默认 XT_TASK_NUM = 10 ,超限后 xt_task_start assert 并返回 NULL
栈大小 每次 yield → resume 都通过函数返回/重入实现,无独立栈。task 内部栈变量在 yield 后丢失(除 XT_TASK_YIELD_FLAG 外)
递归深度 无 yield 的立即完成子任务会递归调用父任务,两层可接受,三层不支持
lc 变量 volatile int lc 存储在 task 结构体中,跨 yield 保持。用户不应修改
任务函数内局部变量 yield 后不保留,需跨 yield 的状态应定义在外部(全局/静态变量或通过 user_data 传递)
-Wimplicit-fallthrough lc 机制的 switch-case 会触发此警告,可通过 #pragma 抑制
timeout 上限 不能超过 XT_TIMER2_TIMEOUT_MAX (INT32_MAX),超限触发 assert
事件参数访问 仅在 xt_task_wait_event resume 后或 xt_task_wait_until 事件唤醒后可安全访问,超时唤醒时无有效事件上下文

八、测试

测试套件位于 tests/xt_task/ 和 tests/xt_event_and_timer/ :

cd tests/xt_task
xt --target windows/simulator fullclean build
xt --target windows/simulator run