工作队列
工作队列
📚 本节导读
学习时长: 约 20 分钟
难度级别: ⭐⭐⭐☆☆
前置知识: 任务管理、信号量、中断管理
工作队列(Workqueue)是 OneOS 中用于延迟执行工作的机制。它将工作项提交到工作队列中,由专门的工作线程(worker thread)异步执行。这类似于 Linux 内核中的 workqueue,是 Bottom-Half 处理的经典实现方式。
🎯 学习目标
- 理解工作队列的核心概念:延迟执行、异步处理
- 掌握工作队列和工作项的创建与初始化
- 掌握工作项的提交(带延迟)和取消操作
- 了解系统工作队列的使用
- 理解工作项的三个阶段状态
一、工作队列概念
1.1 什么是工作队列
工作队列由一个工作线程和一个工作项链表组成。当有工作项被提交时,工作线程被唤醒,从链表中取出工作项并执行其回调函数。
┌─────────────────────────────────────────────────────────────────┐
│ 工作队列模型 │
│ │
│ ┌──────────┐ os_work_run() ┌──────────────────────────┐ │
│ │ 任务 A │ ────────────────→ │ 工作队列 │ │
│ │(提交者) │ 提交工作项 │ │ │
│ └──────────┘ │ ┌──────┐ ┌──────┐ │ │
│ │ │Work 1│→│Work 2│ │ │
│ ┌──────────┐ os_work_run() │ └──────┘ └──────┘ │ │
│ │ ISR │ ────────────────→ │ ↑ │ │
│ │(中断) │ │ 工作线程取出并执行 │ │
│ └──────────┘ │ │ │
│ │ ┌──────────────────┐ │ │
│ ┌──────────┐ │ │ Worker Thread │ │ │
│ │ 任务 B │ │ │ (优先级可配置) │ │ │
│ │(提交者) │ │ │ while(1) { │ │ │
│ └──────────┘ │ │ 取出工作项 │ │ │
│ │ │ 执行 work->func │ │ │
│ │ │ } │ │ │
│ │ └──────────────────┘ │ │
│ └──────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘1.2 工作项的三个阶段
工作项从提交到执行经历三个阶段:
┌──────────────┐ 提交(带延迟) ┌──────────────┐ 延迟到期 ┌──────────────┐
│ IDLE │ ──────────────────→ │ DELAY │ ──────────────→ │ PENDING │
│ 空闲 │ │ 延迟等待 │ │ 等待执行 │
│ │ │ (定时器等待) │ │ (在队列中) │
└──────────────┘ └──────────────┘ └──────┬───────┘
│
┌──────────────┐ │ 工作线程执行
│ IDLE │ ←─────────┘
│ (回调执行完) │
└──────────────┘| 阶段 | 宏 | 说明 |
|---|---|---|
| IDLE | OS_WORK_STAGE_IDLE (0x0) | 工作项空闲,未提交或已完成 |
| DELAY | OS_WORK_STAGE_DELAY (0x1) | 工作项已提交带延迟,等待延迟到期 |
| PENDING | OS_WORK_STAGE_PENDING (0x2) | 工作项在队列中等待工作线程执行 |
1.3 使用场景
一句话核心:工作队列用于把耗时的、非实时的、需要延迟执行的工作项,异步地交给一个专门的工作线程去执行。它让调用者(尤其是中断)"提交即返回",避免在中断或任务上下文中长时间占用 CPU。它内部用一个信号量同步(见 1.5),本质是"生产者提交 + 单工作线程串行消费"。
与普通任务/中断处理的核心区别:
| 机制 | 执行上下文 | 是否异步 | 适合 |
|---|---|---|---|
| 直接调函数 | 调用者上下文 | 否 | 极短操作 |
| 中断上半部 | 中断上下文 | 否(同步) | 极短、必须立即响应 |
| 新建任务 | 任务上下文 | 是 | 持续性的工作 |
| 工作队列 | 工作线程上下文 | 是 | 耗时的、可延迟的、低频的一次性工作 |
典型场景一:Bottom-Half 处理(中断下半部,最典型)
中断上半部只做"快速收数据、置标志",把耗时的数据处理提交给工作队列:
中断ISR(上半部) 工作线程(下半部)
├─ 读硬件数据(快速) ├─ 从队列取出工作项
├─ os_sys_work_run(work, 0) ←─提交──► ├─ 执行 work->work_func()(耗时)
└─ 立即返回(中断被占用时间极短) └─ 处理完继续取下一个为什么用工作队列:中断处理必须越短越好,否则阻塞其他中断、影响实时性。把耗时操作(如协议解析、大数据拷贝、日志写入)丢给工作队列,中断立即返回。这是 Linux workqueue 的经典 Bottom-Half 用法。
典型场景二:延迟执行
需要在指定时间后执行某项操作(如延时上报、延时关灯、周期性清理):
任务 ──os_work_run(queue, work, 5000)──► [工作项进入 DELAY 阶段,定时器倒计时]
│ 5000 ticks 后
▼
[进入 PENDING 阶段,工作线程执行]为什么用工作队列:delay_time 参数让工作项先由定时器延迟,到期后再进入队列执行,实现"定时/延时"语义,无需手动创建定时器任务。
典型场景三:异步操作
不需要等待结果的操作(如发日志、写缓存),提交后立即返回:
任务 ──os_work_run(queue, work, 0)──► 立即提交 → 返回 OS_EOK(不等结果)
↓(异步)
工作线程稍后执行 work_func()为什么用工作队列:调用者提交后不阻塞、不等结果,继续做自己的事,工作线程异步处理,提高吞吐。
典型场景四:串行化处理
多个任务/中断提交工作项,工作队列用单工作线程保证所有工作项串行执行,天然避免数据竞争:
任务A ──┐
任务B ──┼── 提交工作项 ──► [工作队列链表] ──► 单工作线程串行执行
ISR ──┘为什么用工作队列:如果多个任务并发修改共享数据,需加锁;但把修改操作打包成工作项提交到单线程工作队列,就天然串行,无需额外加锁。
什么时候不该用工作队列(选型边界)
| 场景 | 该用什么 | 为什么 |
|---|---|---|
| 需要立即响应、实时性极高 | 中断/高优先级任务 | 工作队列有排队延迟 |
| 工作项执行频率很高 | 专用任务 + 队列 | 工作队列信号量唤醒有开销 |
| 需要严格周期执行 | 定时器 / 周期任务 | 工作队列执行时间不定 |
| 需要等待工作完成后再继续 | 信号量/事件同步 | 工作队列默认异步返回 |
| 工作项之间必须保序且有优先级 | 自定义任务 | 工作队列是 FIFO 串行 |
结合 OneOS 源码的使用注意点
- 依赖信号量:工作队列内部用二值信号量同步(源码
#ifndef OS_USING_SEMAPHORE #error),使用前必须启用OS_USING_SEMAPHORE。 - 启用宏:需
OS_USING_WORKQUEUE;系统工作队列还需OS_USING_SYSTEM_WORKQUEUE。 - 工作项不能重复提交:工作项已在队列(PENDING)或延迟中(DELAY)时再次提交返回
OS_BUSY;需先os_work_stop取消。 - 一个工作项不能提交到多个队列:
work->workqueue只能绑定一个队列,否则返回OS_FAILURE。 - 工作项回调里不能长时间阻塞:工作线程是串行的,一个工作项长时间阻塞会堵住后面所有工作项。
1.4 工作队列控制块(os_workqueue_t)字段详解
工作队列内部控制块 os_workqueue_t(即 struct os_workqueue)定义在 kernel/source/os_prototypes.h 中:
来源:kernel/source/os_prototypes.h
struct os_workqueue
{
os_list_node_t work_list_head;
os_work_t *work_current; /* Work in progress on workqueue. */
dummy_task_t worker_task;
os_spinlock_t lock; /* Spin lock. */
dummy_semaphore_t sem_id; /* Semaphore for synchronization. */
uint8_t object_inited; /* If os_workqueue is inited, value is OS_KOBJ_INITED */
};
typedef struct os_workqueue os_workqueue_t;各字段含义如下:
| 字段 | 类型 | 说明 |
|---|---|---|
work_list_head | os_list_node_t | 工作项链表头:所有待执行的 PENDING 工作项挂在这里 |
work_current | os_work_t * | 当前正在执行的工作项指针(无则 OS_NULL) |
worker_task | dummy_task_t | 工作线程控制块:工作队列专有的 worker 线程 |
lock | os_spinlock_t | 保护工作队列链表操作的自旋锁 |
sem_id | dummy_semaphore_t | 二值信号量:用于唤醒工作线程(有工作项时 post) |
object_inited | uint8_t | 是否已初始化(OS_KOBJ_INITED / OS_KOBJ_DEINITED) |
关键点 —— 工作队列与"工作项链表"的关系:
work_list_head是工作队列内部的链表头(每个队列各 1 个),不是全局资源链表——工作队列没有挂到系统的资源链表(与信号量/互斥锁/邮箱不同)。- 工作队列依赖两种同步原语:信号量(
sem_id,唤醒工作线程)和自旋锁(lock,保护链表操作)。 worker_task是工作队列自己创建的工作线程,其入口是_k_workqueue_task_entry(见 2.2 内部分步详解)。
1.5 工作项控制块(os_work_t)字段详解
工作项内部控制块 os_work_t(即 struct os_work)定义在 kernel/source/os_prototypes.h 中:
来源:kernel/source/os_prototypes.h
struct os_work
{
os_list_node_t work_node;
void *work_func; /* Work function pointer. */
void *data; /* Private data for callback function. */
dummy_timer_t timer; /* Using for delayed work. */
struct os_workqueue *volatile workqueue; /* Workqueue used to execute work. */
uint8_t flag;
uint8_t object_inited; /* If os_work is inited, value is OS_KOBJ_INITED */
};
typedef struct os_work os_work_t;各字段含义如下:
| 字段 | 类型 | 说明 |
|---|---|---|
work_node | os_list_node_t | 链表节点:把工作项挂到工作队列的 work_list_head 上 |
work_func | void * | 回调函数指针:工作线程执行它(参数为 data) |
data | void * | 回调参数:传给 work_func 的私有数据 |
timer | dummy_timer_t | 延迟定时器:delay_time > 0 时用它倒计时,到期后 _k_work_timeout 把工作项提交入队 |
workqueue | os_workqueue_t *(volatile) | 所属工作队列:工作项绑定到的队列(提交时设置,阻止一个工作项进多个队列) |
flag | uint8_t | 工作阶段状态:OS_WORK_STAGE_IDLE(0x0) / DELAY(0x1) / PENDING(0x2) |
object_inited | uint8_t | 是否已初始化(OS_KOBJ_INITED) |
字段与核心概念对应表:
| 工作项概念 | 对应字段 |
|---|---|
| 回调执行逻辑 | work_func |
| 回调参数 | data |
| 挂到工作队列链表 | work_node |
| 延迟执行(倒计时) | timer |
| 阶段状态(IDLE/DELAY/PENDING) | flag |
| 绑定所属队列(防多队列提交) | workqueue |
| 生命周期 | object_inited |
关键点 —— flag 三个阶段的迁移:
| 阶段 | flag 值 | 何时进入 | 何时离开 |
|---|---|---|---|
OS_WORK_STAGE_IDLE | 0x0 | 初始化/执行完/取消后 | 提交时(os_work_run) |
OS_WORK_STAGE_DELAY | 0x1 | os_work_run 且 delay_time > 0 | 定时器到期(_k_work_timeout) |
OS_WORK_STAGE_PENDING | 0x2 | 提交入队(_k_work_commit_to_queue_*) | 工作线程取出执行时置回 IDLE |
二、API 详解
以下 API 定义在 kernel/include/os_workqueue.h 中:
#include <os_workqueue.h>注意:使用工作队列需要在
oneos_config.h中启用OS_USING_WORKQUEUE宏。系统工作队列还需要OS_USING_SYSTEM_WORKQUEUE。
2.1 静态定义宏
静态创建工作队列/工作项时,需要用户提供控制块。这些通过 os_workqueue.h 的宏定义。完整宏定义(kernel/include/os_workqueue.h,与项目一致,含原始英文注释):
/*
* Here, actually, it is an array of workqueue control structure size.
* The purpose is to serve as a workqueue control block for
* static create of workqueue.
*/
#define OS_WORKQUEUE_DEFINE(name) os_workqueue_dummy_t name
/*
* Here, actually, it is an array of work control structure size.
* The purpose is to serve as a work control block for
* static create of workqueue.
*/
#define OS_WORK_DEFINE(name) os_work_dummy_t name各宏详细解释:
OS_WORKQUEUE_DEFINE(name) —— 定义工作队列控制块
#define OS_WORKQUEUE_DEFINE(name) os_workqueue_dummy_t name- 展开为一条变量声明:
os_workqueue_dummy_t name。 os_workqueue_dummy_t是给用户看的占位类型(typedef struct dummy_workqueue),内存布局与内核真正的os_workqueue_t完全一致。- 用户用它定义一个全局变量
name,这块静态内存将来被内核当作工作队列控制块使用。 - 用法示例:
OS_WORKQUEUE_DEFINE(g_my_wq); /* 展开为:os_workqueue_dummy_t g_my_wq; */OS_WORK_DEFINE(name) —— 定义工作项控制块
#define OS_WORK_DEFINE(name) os_work_dummy_t name- 展开为一条变量声明:
os_work_dummy_t name。 os_work_dummy_t是占位类型(typedef struct dummy_work),与内核真正的os_work_t大小一致。- 用法示例:
OS_WORK_DEFINE(g_my_work); /* 展开为:os_work_dummy_t g_my_work; */关键点:这两个宏与信号量/邮箱的 OS_*_DEFINE 一致——都是"定义一块与内核控制块等大的静态内存",供内核 OS_TYPE_CONVERT 强转后使用。静态定义的控制块无需手动释放(静态内存),动态创建的才需 free。
2.2 创建工作队列
os_workqueue_id os_workqueue_create(os_workqueue_dummy_t *queue_cb,
const char *name,
void *stack_begin,
uint32_t stack_size,
uint8_t priority,
int32_t cpu_index);| 参数 | 说明 |
|---|---|
queue_cb | 工作队列控制块指针。静态创建时传入 OS_WORKQUEUE_DEFINE 定义的变量地址;动态创建时传 OS_NULL |
name | 工作队列名称 |
stack_begin | 工作线程栈起始地址。传 OS_NULL 则自动分配 |
stack_size | 工作线程栈大小 |
priority | 工作线程优先级 |
cpu_index | 工作线程绑定的 CPU 索引(SMP 相关)。传 -1 表示不绑定 |
| 返回值 | 成功返回工作队列 ID,失败返回 OS_NULL |
完整源码(kernel/source/os_workqueue.c,与项目一致):
os_workqueue_id os_workqueue_create(os_workqueue_dummy_t *queue_cb,
const char *name,
void *stack_begin,
uint32_t stack_size,
uint8_t priority,
int32_t cpu_index)
{
os_bool_t flag;
os_workqueue_t *queue;
uint8_t alloc_type;
uint32_t ret;
OS_ASSERT(stack_size > 0);
OS_ASSERT(priority < OS_TASK_PRIORITY_MAX);
OS_ASSERT(cpu_index >= -1);
OS_ASSERT(OS_FALSE == os_is_irq_active());
#ifndef OS_USING_HEAP
OS_ASSERT(OS_NULL != queue_cb);
OS_ASSERT(OS_NULL != stack_begin);
#endif
flag = OS_TRUE;
if (OS_NULL != queue_cb)
{
queue = OS_TYPE_CONVERT(os_workqueue_t *, queue_cb);
alloc_type = OS_ALLOC_TYPE_STATIC;
}
#ifdef OS_USING_HEAP
else
{
queue = (os_workqueue_t *)OS_KERNEL_MALLOC(sizeof(os_workqueue_t));
if (OS_NULL == queue)
{
OS_KERN_LOG(KERN_ERROR, WORKQ_TAG, "Malloc workqueue(%s) failed", name);
flag = OS_FALSE;
}
else
{
alloc_type = OS_KOBJ_ALLOC_TYPE_DYNAMIC;
}
}
#endif
#ifdef OS_USING_HEAP
if ((OS_NULL == stack_begin) && (OS_TRUE == flag))
{
stack_size = OS_ALIGN_UP(stack_size, OS_ARCH_STACK_ALIGN_SIZE);
stack_begin = OS_KERNEL_MALLOC_ALIGN(OS_ARCH_STACK_ALIGN_SIZE, stack_size);
if (OS_NULL == stack_begin)
{
OS_KERN_LOG(KERN_ERROR, WORKQ_TAG, "Malloc stack_begin(%s) failed", name);
if (OS_KOBJ_ALLOC_TYPE_DYNAMIC == alloc_type)
{
OS_KERNEL_FREE(queue);
queue = OS_NULL;
}
flag = OS_FALSE;
}
else
{
alloc_type |= OS_KDATA_ALLOC_TYPE_DYNAMIC;
}
}
#endif
if (OS_TRUE == flag)
{
ret = _k_workqueue_init(queue, name, stack_begin, stack_size, priority, cpu_index);
if (OS_SUCCESS != ret)
{
#ifdef OS_USING_HEAP
if (OS_KOBJ_ALLOC_TYPE_DYNAMIC == (OS_KOBJ_ALLOC_TYPE_DYNAMIC & alloc_type))
{
OS_KERNEL_FREE(queue);
}
if (OS_KDATA_ALLOC_TYPE_DYNAMIC == (OS_KDATA_ALLOC_TYPE_DYNAMIC & alloc_type))
{
OS_KERNEL_FREE(stack_begin);
}
#endif
queue = OS_NULL;
}
}
return OS_TYPE_CONVERT(os_workqueue_id, queue);
}分步详解:
第一步:参数校验(条件:栈大小/优先级/CPU 索引合法,且非中断上下文;非堆模式下控制块和栈必须由用户提供)—— 不满足则断言崩溃
OS_ASSERT(stack_size > 0);
OS_ASSERT(priority < OS_TASK_PRIORITY_MAX);
OS_ASSERT(cpu_index >= -1);
OS_ASSERT(OS_FALSE == os_is_irq_active());
#ifndef OS_USING_HEAP
OS_ASSERT(OS_NULL != queue_cb);
OS_ASSERT(OS_NULL != stack_begin);
#endif进入本步的条件:调用 os_workqueue_create() 时无条件执行(入口硬性校验,不满足条件直接断言崩溃)。
详细解释:
stack_size > 0:工作线程栈大小必须非 0。priority < OS_TASK_PRIORITY_MAX:优先级必须合法。cpu_index >= -1:CPU 索引合法(-1表示不绑定)。os_is_irq_active() == OS_FALSE:不能在中断中创建(创建会OS_KERNEL_MALLOC、创建任务)。- 条件(非堆
OS_USING_HEAP未启用时)queue_cb != OS_NULL且stack_begin != OS_NULL:未启用堆时无法动态分配,控制块和栈都必须由用户静态提供。
第二步:分配控制块(条件:queue_cb 是否非空)—— 静态用用户内存,动态从堆分配
flag = OS_TRUE;
if (OS_NULL != queue_cb)
{
queue = OS_TYPE_CONVERT(os_workqueue_t *, queue_cb);
alloc_type = OS_ALLOC_TYPE_STATIC;
}
#ifdef OS_USING_HEAP
else
{
queue = (os_workqueue_t *)OS_KERNEL_MALLOC(sizeof(os_workqueue_t));
if (OS_NULL == queue)
{
OS_KERN_LOG(KERN_ERROR, WORKQ_TAG, "Malloc workqueue(%s) failed", name);
flag = OS_FALSE;
}
else
{
alloc_type = OS_KOBJ_ALLOC_TYPE_DYNAMIC;
}
}
#endif进入本步的条件:第一步断言通过。
- 子分支 A:条件
queue_cb != OS_NULL(静态创建)→queue = (os_workqueue_t*)queue_cb用用户提供的静态内存,alloc_type = OS_ALLOC_TYPE_STATIC。 - 子分支 B:条件
queue_cb == OS_NULL(动态创建,需OS_USING_HEAP)→OS_KERNEL_MALLOC(sizeof(os_workqueue_t))从堆分配;失败则打日志、flag = OS_FALSE;成功则alloc_type = OS_KOBJ_ALLOC_TYPE_DYNAMIC。
第三步:分配工作线程栈(条件:stack_begin == OS_NULL 且 flag == OS_TRUE,需堆)—— 自动分配栈
#ifdef OS_USING_HEAP
if ((OS_NULL == stack_begin) && (OS_TRUE == flag))
{
stack_size = OS_ALIGN_UP(stack_size, OS_ARCH_STACK_ALIGN_SIZE);
stack_begin = OS_KERNEL_MALLOC_ALIGN(OS_ARCH_STACK_ALIGN_SIZE, stack_size);
if (OS_NULL == stack_begin)
{
OS_KERN_LOG(KERN_ERROR, WORKQ_TAG, "Malloc stack_begin(%s) failed", name);
if (OS_KOBJ_ALLOC_TYPE_DYNAMIC == alloc_type)
{
OS_KERNEL_FREE(queue);
queue = OS_NULL;
}
flag = OS_FALSE;
}
else
{
alloc_type |= OS_KDATA_ALLOC_TYPE_DYNAMIC;
}
}
#endif进入本步的条件:stack_begin == OS_NULL(用户没提供栈)且 flag == OS_TRUE(前面控制块已就绪),且 OS_USING_HEAP 已启用。
stack_size按架构栈对齐边界向上取整。OS_KERNEL_MALLOC_ALIGN分配对齐的栈空间。- 子分支:条件栈分配失败→ 若控制块也是动态的(
alloc_type == OS_KOBJ_ALLOC_TYPE_DYNAMIC)则回滚释放queue;置flag = OS_FALSE。 - 栈分配成功则
alloc_type |= OS_KDATA_ALLOC_TYPE_DYNAMIC(标记栈是动态分配的,失败时需释放)。
关键点:alloc_type 用位或组合"控制块类型"和"栈类型"两个标志——OS_KOBJ_ALLOC_TYPE_DYNAMIC 表示控制块动态,OS_KDATA_ALLOC_TYPE_DYNAMIC 表示栈动态。失败回滚时分别按对应标志释放。
第四步:初始化工作队列(条件:flag == OS_TRUE,所有资源就绪)—— 初始化链表/信号量/工作线程
if (OS_TRUE == flag)
{
ret = _k_workqueue_init(queue, name, stack_begin, stack_size, priority, cpu_index);
if (OS_SUCCESS != ret)
{
#ifdef OS_USING_HEAP
if (OS_KOBJ_ALLOC_TYPE_DYNAMIC == (OS_KOBJ_ALLOC_TYPE_DYNAMIC & alloc_type))
{
OS_KERNEL_FREE(queue);
}
if (OS_KDATA_ALLOC_TYPE_DYNAMIC == (OS_KDATA_ALLOC_TYPE_DYNAMIC & alloc_type))
{
OS_KERNEL_FREE(stack_begin);
}
#endif
queue = OS_NULL;
}
}
return OS_TYPE_CONVERT(os_workqueue_id, queue);进入本步的条件:flag == OS_TRUE(前面控制块/栈都已成功就绪)。
_k_workqueue_init(queue, name, stack_begin, stack_size, priority, cpu_index):初始化工作队列(见下方_k_workqueue_init详解),返回ret。- 子分支:条件
ret != OS_SUCCESS(初始化失败)→ 按alloc_type分别释放动态分配的控制块和栈,queue = OS_NULL。
最终返回值:return OS_TYPE_CONVERT(os_workqueue_id, queue)——成功返回队列 ID;失败 queue == OS_NULL,返回 OS_NULL。
os_workqueue_create 的 if-else 条件走向总结:
os_workqueue_create() 条件走向总结:
├── OS_ASSERT 参数校验(stack_size/priority/cpu_index 合法 / 非中断上下文 / 非堆模式下控制块和栈非空)任一失败 → 断言崩溃
├── flag = OS_TRUE
├── if (queue_cb != OS_NULL)(静态创建)
│ └── queue = (os_workqueue_t*)queue_cb;alloc_type = OS_ALLOC_TYPE_STATIC
├── else if (queue_cb == OS_NULL)(动态创建,需 OS_USING_HEAP)
│ ├── 若 malloc 控制块成功 → alloc_type = OS_KOBJ_ALLOC_TYPE_DYNAMIC
│ └── 若 malloc 失败 → 打日志,flag = OS_FALSE
├── if (stack_begin == OS_NULL 且 flag == OS_TRUE)(自动分配栈,需堆)
│ ├── 若栈分配成功 → alloc_type |= OS_KDATA_ALLOC_TYPE_DYNAMIC
│ └── 若栈分配失败 → 回滚释放动态控制块,flag = OS_FALSE
└── if (flag == OS_TRUE)(所有资源就绪)
├── 若 _k_workqueue_init 成功 → 返回队列 ID
└── 若 _k_workqueue_init 失败 → 按 alloc_type 释放控制块/栈,queue = OS_NULL,返回 OS_NULL内部辅助函数 _k_workqueue_init()(kernel/source/os_workqueue.c,与项目一致):
static os_err_t _k_workqueue_init(os_workqueue_t *queue,
const char *name,
void *stack_begin,
uint32_t stack_size,
uint8_t priority,
int32_t cpu_index)
{
os_err_t ret;
os_task_id tid;
queue->work_current = OS_NULL;
queue->object_inited = OS_KOBJ_INITED;
os_list_init(&queue->work_list_head);
os_spin_lock_init(&queue->lock);
ret = OS_SUCCESS;
/*binary semaphore*/
if (OS_NULL != os_semaphore_create(&queue->sem_id, OS_NULL, 0, 1))
{
tid = os_task_create(&queue->worker_task,
stack_begin,
stack_size,
name,
_k_workqueue_task_entry,
queue,
priority);
if (tid)
{
#ifdef OS_USING_SMP
ret = os_task_set_cpu_affinity(&queue->worker_task, cpu_index);
if (ret == OS_SUCCESS)
{
ret = os_task_startup(&queue->worker_task);
}
#else
ret = os_task_startup(&queue->worker_task);
#endif
}
else
{
ret = OS_FAILURE;
}
}
else
{
ret = OS_FAILURE;
}
return ret;
}_k_workqueue_init 分步详解:
初始化基础字段(无条件执行):链表、自旋锁、状态
queue->work_current = OS_NULL;
queue->object_inited = OS_KOBJ_INITED;
os_list_init(&queue->work_list_head);
os_spin_lock_init(&queue->lock);work_current = OS_NULL(无执行中的工作项)、object_inited = OS_KOBJ_INITED、初始化工作链表头、初始化自旋锁。
创建二值信号量(条件:信号量创建成功)—— 失败则返回 OS_FAILURE
ret = OS_SUCCESS;
/*binary semaphore*/
if (OS_NULL != os_semaphore_create(&queue->sem_id, OS_NULL, 0, 1))
{
/* 信号量创建成功,进入下一步:创建工作线程 */
}
else
{
ret = OS_FAILURE;
}os_semaphore_create(&queue->sem_id, OS_NULL, 0, 1) 创建最大计数值为 1 的二值信号量(初始值 0)。条件信号量创建成功才进入 if 分支继续创建工作线程;失败则走 else 分支 ret = OS_FAILURE。if 分支内的任务创建逻辑见下一步详解。
创建工作线程(条件:信号量创建成功)—— 创建 worker_task
tid = os_task_create(&queue->worker_task, stack_begin, stack_size, name,
_k_workqueue_task_entry, queue, priority);进入本步的条件:上一步信号量创建成功(if 分支内)。os_task_create 用 _k_workqueue_task_entry 作为入口、queue 作为参数,创建工作线程 worker_task,返回任务 ID tid。
绑定 CPU 并启动工作线程(条件:任务创建成功;SMP 下先设亲和性)
#ifdef OS_USING_SMP
ret = os_task_set_cpu_affinity(&queue->worker_task, cpu_index);
if (ret == OS_SUCCESS)
{
ret = os_task_startup(&queue->worker_task);
}
#else
ret = os_task_startup(&queue->worker_task);
#endif- SMP 下:先
os_task_set_cpu_affinity绑定cpu_index,成功后再os_task_startup启动工作线程。 - 非 SMP 下:直接
os_task_startup启动。
使用示例:
OS_WORKQUEUE_DEFINE(g_my_wq);
os_workqueue_id wq_id = os_workqueue_create(&g_my_wq, "my_wq",
OS_NULL, /* 自动分配栈(需堆) */
1024, /* 栈大小 */
20, /* 优先级 */
-1); /* 不绑定 CPU */
if (wq_id == OS_NULL)
{
os_kprintf("create workqueue failed\r\n");
return;
}2.3 初始化工作项
os_work_id os_work_init(os_work_dummy_t *work, void (*func)(void *data), void *data);| 参数 | 说明 |
|---|---|
work | 工作项控制块指针(OS_WORK_DEFINE 定义的变量地址) |
func | 工作项回调函数,工作线程执行时调用 |
data | 传递给回调函数的参数 |
| 返回值 | 成功返回工作项 ID,失败返回 OS_NULL |
完整源码(kernel/source/os_workqueue.c,与项目一致):
os_work_id os_work_init(os_work_dummy_t *work, void (*func)(void *data), void *data)
{
os_work_t *os_work;
OS_ASSERT(work);
OS_ASSERT(func);
os_work = (os_work_t *)work;
os_work->work_func = func;
os_work->data = data;
os_work->workqueue = OS_NULL;
os_work->flag = OS_WORK_STAGE_IDLE;
os_work->object_inited = OS_KOBJ_INITED;
os_list_init(&os_work->work_node);
os_timer_create(&os_work->timer, OS_NULL, _k_work_timeout, work, 1, OS_TIMER_FLAG_ONE_SHOT);
return work;
}分步详解:
第一步:参数校验(条件:work 和 func 均非空)—— 不满足则断言崩溃
OS_ASSERT(work);
OS_ASSERT(func);进入本步的条件:调用 os_work_init() 时无条件执行(入口硬性校验)。work 非空(控制块存在)、func 非空(必须提供回调函数)。
第二步:赋值工作项基础字段(无条件执行)—— 记录回调/参数/所属队列/阶段/状态
os_work = (os_work_t *)work;
os_work->work_func = func;
os_work->data = data;
os_work->workqueue = OS_NULL;
os_work->flag = OS_WORK_STAGE_IDLE;
os_work->object_inited = OS_KOBJ_INITED;进入本步的条件:第一步断言通过。
work_func = func:记录回调函数指针。data = data:记录回调参数。workqueue = OS_NULL:初始不属于任何工作队列(提交时os_work_run才绑定,见 2.5)。flag = OS_WORK_STAGE_IDLE:初始为空闲阶段。object_inited = OS_KOBJ_INITED:标记已初始化。
第三步:初始化链表节点 + 创建延迟定时器(无条件执行)—— 为入队和延迟做准备
os_list_init(&os_work->work_node);
os_timer_create(&os_work->timer, OS_NULL, _k_work_timeout, work, 1, OS_TIMER_FLAG_ONE_SHOT);
return work;进入本步的条件:第二步完成。
os_list_init(&os_work->work_node):初始化链表节点(初始不挂任何队列)。os_timer_create(&os_work->timer, OS_NULL, _k_work_timeout, work, 1, OS_TIMER_FLAG_ONE_SHOT):创建一次性定时器,超时回调_k_work_timeout,参数是work。当delay_time > 0时用它倒计时。
关键点 —— flag 初始值:初始化后 flag = IDLE(0x0),这是"可提交"状态。只有 IDLE 状态的工作项才能被 os_work_run 提交(见 2.5)。
使用示例:
OS_WORK_DEFINE(g_my_work);
static void my_work_func(void *data)
{
int *p = (int *)data;
(*p)++;
os_kprintf("work executed, counter=%d\r\n", *p);
}
os_work_id work_id = os_work_init(&g_my_work, my_work_func, &g_counter);
if (work_id == OS_NULL)
{
os_kprintf("init work failed\r\n");
}2.4 反初始化工作项
void os_work_deinit(os_work_id work_id);| 参数 | 说明 |
|---|---|
work_id | 要反初始化的工作项 ID |
| 返回值 | 无 |
完整源码(kernel/source/os_workqueue.c,与项目一致):
void os_work_deinit(os_work_id work_id)
{
os_work_t *work = (os_work_t *)work_id;
OS_ASSERT(work);
OS_ASSERT(OS_KOBJ_INITED == work->object_inited);
work->object_inited = OS_KOBJ_DEINITED;
os_list_del(&work->work_node);
os_timer_destroy(&work->timer);
return;
}分步详解:
第一步:参数校验(条件:work 非空且已初始化)—— 不满足则断言崩溃
OS_ASSERT(work);
OS_ASSERT(OS_KOBJ_INITED == work->object_inited);进入本步的条件:调用 os_work_deinit() 时无条件执行。work 非空、且必须处于已初始化状态(防重复反初始化)。
第二步:标记去初始化(无条件执行)—— 置 DEINITED
work->object_inited = OS_KOBJ_DEINITED;进入本步的条件:第一步断言通过。把工作项标记为去初始化,此后不可再使用。
第三步:摘除链表节点 + 销毁定时器(无条件执行)—— 清理资源
os_list_del(&work->work_node);
os_timer_destroy(&work->timer);进入本步的条件:第二步完成。
os_list_del(&work->work_node):从工作队列链表摘除该工作项节点(若它正挂在队列上)。os_timer_destroy(&work->timer):销毁延迟定时器。
关键点:反初始化会清理工作项的所有关联资源(链表节点、定时器)。反初始化后工作项不可再用,需重新 os_work_init。建议:反初始化前先 os_work_stop 取消未执行的工作项,避免误操作。
使用示例:
os_work_stop(work_id, OS_TRUE); /* 先取消未执行的工作项 */
os_work_deinit(work_id); /* 再反初始化 */2.5 提交工作项
os_err_t os_work_run(os_workqueue_id queue_id, os_work_id work_id, os_tick_t delay_time);| 参数 | 说明 |
|---|---|
queue_id | 工作队列 ID |
work_id | 工作项 ID |
delay_time | 延迟时间(tick 数)。0 表示立即提交,> 0 表示延迟指定 tick 后执行 |
| 返回值 | OS_EOK 成功;OS_BUSY 工作项已在队列中(PENDING/DELAY);OS_FAILURE 工作项已提交到别的队列 |
完整源码(kernel/source/os_workqueue.c,与项目一致):
os_err_t os_work_run(os_workqueue_id queue_id, os_work_id work_id, os_tick_t delay_time)
{
os_ubase_t irq_key;
os_err_t ret;
os_workqueue_t *queue;
os_work_t *work;
queue = (os_workqueue_t *)queue_id;
work = (os_work_t *)work_id;
OS_ASSERT(queue);
OS_ASSERT(work);
OS_ASSERT(OS_KOBJ_INITED == queue->object_inited);
OS_ASSERT(OS_KOBJ_INITED == work->object_inited);
OS_ASSERT(delay_time < (OS_TICK_MAX / 2));
ret = OS_SUCCESS;
os_spin_lock_irqsave(&queue->lock, &irq_key);
/*Not allowing a work to commit to more than one queue.Limiting concurrent running*/
if (work->workqueue && (queue != work->workqueue))
{
os_spin_unlock_irqrestore(&queue->lock, irq_key);
OS_KERN_LOG(KERN_WARNING, WORKQ_TAG, "Submitting work to more than one workqueue is not allowed");
ret = OS_FAILURE;
}
else
{
if (work->flag == OS_WORK_STAGE_IDLE)
{
work->workqueue = queue;
if (delay_time)
{
work->flag = OS_WORK_STAGE_DELAY;
(void)os_timer_set_timeout_ticks(&work->timer, delay_time);
(void)os_timer_start(&work->timer);
}
else
{
_k_work_commit_to_queue_tail(queue, work);
}
ret = OS_SUCCESS;
}
else
{
OS_KERN_LOG(KERN_WARNING, WORKQ_TAG, "Work(%p) had been submitted to workqueue(%p)", work, queue);
ret = OS_BUSY;
}
os_spin_unlock_irqrestore(&queue->lock, irq_key);
}
return ret;
}辅助函数 _k_work_commit_to_queue_tail() —— 把工作项入队(队尾)(kernel/source/os_workqueue.c,与项目一致):
OS_INLINE void _k_work_commit_to_queue_tail(os_workqueue_t *queue, os_work_t *work)
{
work->flag = OS_WORK_STAGE_PENDING;
os_list_add_tail(&queue->work_list_head, &work->work_node);
/*Optimization: reduce bin semaphore release*/
if (OS_NULL == queue->work_current)
{
(void)os_semaphore_post(&queue->sem_id);
}
}work->flag = OS_WORK_STAGE_PENDING:工作项进入等待执行阶段。os_list_add_tail:把工作项挂到工作队列链表尾部(FIFO)。- 条件
work_current == OS_NULL(工作线程当前空闲,无执行中的工作项)→os_semaphore_post(&queue->sem_id)释放二值信号量唤醒工作线程;否则不释放(优化:工作线程正在忙,等下一条时自己会再取,避免重复唤醒)。
分步详解:
第一步:准备与参数校验(条件:队列/工作项非空、都已初始化、delay_time 合法)—— 不满足则断言崩溃
OS_ASSERT(queue);
OS_ASSERT(work);
OS_ASSERT(OS_KOBJ_INITED == queue->object_inited);
OS_ASSERT(OS_KOBJ_INITED == work->object_inited);
OS_ASSERT(delay_time < (OS_TICK_MAX / 2));进入本步的条件:调用 os_work_run() 时无条件执行(入口硬性校验)。
queue != OS_NULL、work != OS_NULL:非空。object_inited == OS_KOBJ_INITED(两者):队列和工作项都已初始化。delay_time < OS_TICK_MAX/2:延迟时间合法。
第二步:防"一个工作项提交到多个队列"(条件:work->workqueue 非空且不是当前队列)—— 返回 OS_FAILURE
os_spin_lock_irqsave(&queue->lock, &irq_key);
if (work->workqueue && (queue != work->workqueue))
{
os_spin_unlock_irqrestore(&queue->lock, irq_key);
OS_KERN_LOG(KERN_WARNING, WORKQ_TAG, "Submitting work to more than one workqueue is not allowed");
ret = OS_FAILURE;
}进入本步的条件:第一步断言通过,加锁后进入。work->workqueue 非空且 queue != work->workqueue(工作项已绑定到另一个队列,现在又要提交到本队列)。
- 打印警告日志,返回
OS_FAILURE。
为什么这样限制:一个工作项只能绑定一个工作队列。若同一工作项被提交到两个队列,两个工作线程会并发执行同一个 work_func,造成数据竞争。work->workqueue 记录其所属队列,阻止重复绑定。
第三步:工作项空闲时提交(条件:work->flag == OS_WORK_STAGE_IDLE)—— 立即入队或延迟入队
if (work->flag == OS_WORK_STAGE_IDLE)
{
work->workqueue = queue;
if (delay_time)
{
work->flag = OS_WORK_STAGE_DELAY;
(void)os_timer_set_timeout_ticks(&work->timer, delay_time);
(void)os_timer_start(&work->timer);
}
else
{
_k_work_commit_to_queue_tail(queue, work);
}
ret = OS_SUCCESS;
}进入本步的条件:第二步条件不成立(工作项未绑定到其他队列),即 work->flag == OS_WORK_STAGE_IDLE(工作项空闲,可提交)。
work->workqueue = queue:把工作项绑定到当前队列(记录归属)。- 子分支 A:条件
delay_time > 0(延迟提交):work->flag = OS_WORK_STAGE_DELAY:进入延迟阶段;os_timer_set_timeout_ticks(&work->timer, delay_time):设置定时器超时;os_timer_start(&work->timer):启动定时器。到期后由_k_work_timeout回调把工作项入队(见下方)。
- 子分支 B:条件
delay_time == 0(立即提交):_k_work_commit_to_queue_tail(queue, work)直接入队(见上方辅助函数)。 - 返回
OS_SUCCESS。
第四步:工作项非空闲(条件:work->flag != OS_WORK_STAGE_IDLE,即 PENDING/DELAY)—— 返回 OS_BUSY
else
{
OS_KERN_LOG(KERN_WARNING, WORKQ_TAG, "Work(%p) had been submitted to workqueue(%p)", work, queue);
ret = OS_BUSY;
}
os_spin_unlock_irqrestore(&queue->lock, irq_key);进入本步的条件:work->flag != OS_WORK_STAGE_IDLE(工作项处于 PENDING 等待执行 或 DELAY 延迟等待)。
- 打印警告日志,返回
OS_BUSY。
关键点:工作项不能重复提交——已在队列(PENDING)或延迟中(DELAY)时再次 os_work_run 返回 OS_BUSY。若想重新提交,需先 os_work_stop 取消(置回 IDLE)。
辅助函数 _k_work_timeout() —— 延迟定时器到期回调(kernel/source/os_workqueue.c,与项目一致):
static void _k_work_timeout(void *parameter)
{
os_ubase_t irq_key;
struct os_work *work;
os_workqueue_t *queue;
work = (struct os_work *)parameter;
queue = work->workqueue;
os_spin_lock_irqsave(&queue->lock, &irq_key);
/*If the timer can't cancel,can be cancelled by os_cancel_work used work->flag */
if (OS_WORK_STAGE_DELAY == work->flag)
{
_k_work_commit_to_queue_tail(work->workqueue, work);
}
os_spin_unlock_irqrestore(&queue->lock, irq_key);
}分步详解:
延迟到期时把工作项入队(条件:work->flag == OS_WORK_STAGE_DELAY)—— 从 DELAY 转到 PENDING
if (OS_WORK_STAGE_DELAY == work->flag)
{
_k_work_commit_to_queue_tail(work->workqueue, work);
}进入本步的条件:work->flag == OS_WORK_STAGE_DELAY(工作项正处于延迟等待阶段,定时器到期)。
- 调用
_k_work_commit_to_queue_tail(work->workqueue, work)把工作项从 DELAY 阶段转入 PENDING 阶段(置 flag 为 PENDING、挂到队列尾部、必要时唤醒工作线程)。
关键点:_k_work_timeout 是定时器超时回调,在定时器上下文执行。它把 DELAY 的工作项提交到队列,实现"延迟一段时间后执行"。若此时 flag 已不是 DELAY(如被 os_work_stop 提前取消置为 IDLE),则不执行入队(配合取消逻辑)。
os_work_run 的 if-else 条件走向总结:
os_work_run() 条件走向总结:
├── OS_ASSERT 参数校验(queue/work 非空 / 已初始化 / delay_time 合法)任一失败 → 断言崩溃
├── if (work->workqueue 非空 且 queue != work->workqueue)(提交到别的队列)
│ └── 打印警告,返回 OS_FAILURE
└── else(未绑定其他队列)
├── if (work->flag == OS_WORK_STAGE_IDLE,空闲可提交)
│ ├── work->workqueue = queue(绑定当前队列)
│ ├── if (delay_time > 0,延迟提交)
│ │ └── flag=DELAY + 设置并启动定时器 → 到期由 _k_work_timeout 入队 → 返回 OS_SUCCESS
│ └── else(delay_time == 0,立即提交)
│ └── _k_work_commit_to_queue_tail 直接入队(flag=PENDING + 入队 + 必要时唤醒线程)→ 返回 OS_SUCCESS
└── else(flag != IDLE,即 PENDING/DELAY 已提交)
└── 打印警告,返回 OS_BUSY使用示例:
/* 立即提交 */
os_err_t ret = os_work_run(wq_id, work_id, 0);
if (ret != OS_EOK)
{
os_kprintf("submit failed: %d\r\n", ret);
}
/* 延迟 1000 ticks 提交 */
ret = os_work_run(wq_id, work_id, 1000);
if (ret == OS_BUSY)
{
os_kprintf("work already submitted\r\n"); /* 工作项未执行完,不能重复提交 */
}2.6 取消工作项
os_err_t os_work_stop(os_work_id work_id, os_bool_t sync);| 参数 | 说明 |
|---|---|
work_id | 工作项 ID |
sync | OS_TRUE:同步取消,等待工作项执行完毕(如果正在执行);OS_FALSE:异步取消 |
| 返回值 | OS_EOK 取消成功;OS_BUSY 工作项正在执行(异步时);OS_FAILURE 工作项从未提交过 |
os_work_stop 的完整实现(kernel/source/os_workqueue.c,与项目一致)——它只是转发到内部函数 _k_work_stop:
os_err_t os_work_stop(os_work_id work_id, os_bool_t sync)
{
return _k_work_stop(work_id, sync);
}内部函数 _k_work_stop 的完整源码(kernel/source/os_workqueue.c,与项目一致):
static os_err_t _k_work_stop(os_work_id work_id, os_bool_t sync)
{
os_ubase_t key;
os_err_t ret;
os_workqueue_t *queue;
OS_WORK_DEFINE(wait_work);
os_work_id wait_work_id;
OS_SEMAPHORE_DEFINE(wait_sem);
os_semaphore_id wait_sem_id;
int32_t work_runing;
os_work_t *work;
work = (os_work_t *)work_id;
OS_ASSERT(work);
OS_ASSERT(OS_KOBJ_INITED == work->object_inited);
ret = OS_SUCCESS;
work_runing = 0;
/*The work has not been committed since it was initialized.*/
if (OS_NULL == work->workqueue)
{
ret = OS_FAILURE;
}
else
{
queue = work->workqueue;
if (sync)
{
wait_sem_id = os_semaphore_create(&wait_sem, OS_NULL, 0, 1);
wait_work_id = os_work_init(&wait_work, _k_work_wait, wait_sem_id);
}
os_spin_lock_irqsave(&queue->lock, &key);
if (OS_WORK_STAGE_DELAY == work->flag)
{
(void)os_timer_stop(&work->timer);
work->flag = OS_WORK_STAGE_IDLE;
}
else if (OS_WORK_STAGE_PENDING == work->flag)
{
os_list_del(&work->work_node);
work->flag = OS_WORK_STAGE_IDLE;
}
else
{
/*Work is in progress*/
if (queue->work_current == work)
{
work_runing = 1;
if (sync)
{
_k_work_commit_to_queue_head(work->workqueue, (os_work_t *)&wait_work);
}
else
{
OS_KERN_LOG(KERN_WARNING, WORKQ_TAG, "Work(%p) is running", work);
ret = OS_BUSY;
}
}
}
os_spin_unlock_irqrestore(&queue->lock, key);
if (sync)
{
if (work_runing)
{
(void)os_semaphore_wait(wait_sem_id, OS_WAIT_FOREVER);
}
(void)os_semaphore_destroy(wait_sem_id);
os_work_deinit(wait_work_id);
}
}
return ret;
}分步详解:
第一步:参数校验(条件:work 非空且已初始化)—— 不满足则断言崩溃
work = (os_work_t *)work_id;
OS_ASSERT(work);
OS_ASSERT(OS_KOBJ_INITED == work->object_inited);进入本步的条件:调用 os_work_stop() 时无条件执行。work 非空、且必须已初始化。
第二步:未提交过的工作项(条件:work->workqueue == OS_NULL)—— 返回 OS_FAILURE
ret = OS_SUCCESS;
work_runing = 0;
if (OS_NULL == work->workqueue)
{
ret = OS_FAILURE;
}进入本步的条件:第一步断言通过。work->workqueue == OS_NULL(工作项从未被提交到任何队列,没有可取消的东西)。
- 返回
OS_FAILURE(源码注释:"The work has not been committed since it was initialized.")。
第三步:同步模式准备工作(条件:sync == OS_TRUE)—— 创建等待信号量和等待工作项
else
{
queue = work->workqueue;
if (sync)
{
wait_sem_id = os_semaphore_create(&wait_sem, OS_NULL, 0, 1);
wait_work_id = os_work_init(&wait_work, _k_work_wait, wait_sem_id);
}进入本步的条件:work->workqueue != OS_NULL(工作项已提交过)。若 sync == OS_TRUE(同步取消):
os_semaphore_create(&wait_sem, OS_NULL, 0, 1):创建等待信号量;os_work_init(&wait_work, _k_work_wait, wait_sem_id):初始化一个"等待工作项"(回调_k_work_wait,会 post 信号量)。
这两行的作用 —— "哨兵工作项 + 完成信号量"机制:
它们的目的是:为同步取消准备一个通知机制,让调用者能感知"正在执行的工作项是否已跑完"。
wait_sem(完成信号量):二值信号量,初始计数值0。计数值0表示"工作项还没执行完",被 post 后计数值变1表示"已执行完"。wait_work(哨兵工作项):回调为_k_work_wait,参数为wait_sem_id。它的回调会 post 信号量:它被排到当前工作项之后,只有当前工作项执行完,工作线程才会轮到它,从而 post 信号量。static void _k_work_wait(void *parameter) { (void)os_semaphore_post((os_semaphore_id)parameter); /* post wait_sem */ }
为什么需要这个机制:当工作项正在执行(queue->work_current == work)时无法强制中断,只能等它跑完。但工作项的回调是任意的(work_func),不会主动通知调用者。wait_work 插队到当前工作项之后,wait_sem 被 post 的时刻精确对应"当前工作项执行完毕"——调用者 os_semaphore_wait(wait_sem, OS_WAIT_FOREVER) 阻塞在此,从而精确等待完成(见第五步)。
完整流程(同步取消正在执行的工作项):
① 创建 wait_sem(计数值=0)+ wait_work(回调=_k_work_wait)
② 发现 work 正在执行(queue->work_current == work)
→ _k_work_commit_to_queue_head 把 wait_work 插到工作队列【队首】
→ 它排在当前 work 之后、其他排队工作项之前
③ 调用者 os_semaphore_wait(wait_sem, OS_WAIT_FOREVER) → 阻塞
④ 工作线程:执行完当前 work_func → 取出 wait_work 并执行其回调
→ _k_work_wait → os_semaphore_post(wait_sem) → 计数值变 1
⑤ 调用者被信号量唤醒 → os_semaphore_wait 返回
⑥ 调用者确认"当前工作项已执行完" ✔
⑦ 清理临时资源:os_semaphore_destroy(wait_sem) + os_work_deinit(wait_work)
⑧ 返回 OS_SUCCESS关键点:
- 因为
wait_work插入队首,它必然在当前工作项之后被执行,所以wait_sem被 post 的时刻精确对应"当前工作项跑完"。这就是sync == OS_TRUE能"确保取消后工作项不再执行"的底层机制。 - ④之后(⑤~⑧)的收尾:
wait_sem和wait_work是临时创建的一次性资源,只服务于这一次同步取消。调用者被唤醒、确认完成后,必须os_semaphore_destroy销毁信号量、os_work_deinit反初始化哨兵工作项,否则会造成资源泄漏(信号量句柄、定时器残留)。由于wait_work定义在_k_work_stop函数栈上,os_work_deinit只清理、不free(栈内存由函数返回自动回收)。
第四步:按工作项当前阶段处理(条件:flag 是 DELAY / PENDING / 正在执行)—— 三种情况
os_spin_lock_irqsave(&queue->lock, &key);
if (OS_WORK_STAGE_DELAY == work->flag)
{
(void)os_timer_stop(&work->timer);
work->flag = OS_WORK_STAGE_IDLE;
}
else if (OS_WORK_STAGE_PENDING == work->flag)
{
os_list_del(&work->work_node);
work->flag = OS_WORK_STAGE_IDLE;
}
else
{
/*Work is in progress*/
if (queue->work_current == work)
{
work_runing = 1;
if (sync)
{
_k_work_commit_to_queue_head(work->workqueue, (os_work_t *)&wait_work);
}
else
{
OS_KERN_LOG(KERN_WARNING, WORKQ_TAG, "Work(%p) is running", work);
ret = OS_BUSY;
}
}
}进入本步的条件:第三步后加锁,按 work->flag 分三种情况:
- 子分支 A:条件
flag == OS_WORK_STAGE_DELAY(工作项还在延迟等待)→os_timer_stop(&work->timer)停止定时器,flag = IDLE(取消延迟,工作项不会入队了)。 - 子分支 B:条件
flag == OS_WORK_STAGE_PENDING(工作项已在队列中等待执行)→os_list_del(&work->work_node)从队列摘除,flag = IDLE。 - 子分支 C:条件
flag == IDLE但正在执行(queue->work_current == work,工作线程正在跑这个回调):- 置
work_runing = 1(标记"正在执行"); - 子子分支:条件
sync == OS_TRUE(同步)→ 把"等待工作项"wait_work插到队列头(_k_work_commit_to_queue_head),等当前工作项执行完,工作线程会执行wait_work回调(post 信号量); - 子子分支:条件
sync == OS_FALSE(异步)→ 打印警告,返回OS_BUSY(工作项正在执行,无法异步取消)。
- 置
为什么"正在执行的工作项"无法异步取消?—— 两层原因
原因一:没有"可操作的东西"了。 os_work_stop 能做的只有两种取消操作,而正在执行的工作项两者都不适用:
| 阶段 | 取消操作 | 正在执行时为何不适用 |
|---|---|---|
| DELAY(延迟中) | os_timer_stop 停定时器 | 定时器早已停了(延迟到期才入队执行) |
| PENDING(队列中) | os_list_del 摘节点 | 节点早已从链表摘除(工作线程取出后才开始执行) |
正在执行的工作项 flag 已被工作线程置回 IDLE、work_node 已摘除,它已经脱离了队列的控制,正在 CPU 上跑 work_func 回调。os_work_stop 没有任何"队列层面的东西"可取消。
原因二:无法安全地中断另一个线程正在执行的代码。 即使异步取消想"强制中断" work_func,也做不到:
- OneOS 没有"线程取消点"机制(如 POSIX 的
pthread_cancel),无法在安全点暂停/杀掉工作线程正在执行的代码; work_func可能已分配内存、已修改共享数据、已加锁。若强行中断会留下半执行状态(内存泄漏、数据不一致、锁未释放),比"让它跑完"更危险。
对比同步取消:同步(sync == OS_TRUE)不是中断工作项,而是"排队等它自然跑完"——插一个哨兵工作项 wait_work 排在它后面,调用者 os_semaphore_wait 阻塞自己,等 work_func 执行完、工作线程执行 wait_work 回调 post 信号量再被唤醒。所以同步能保证"取消后不再执行",异步却做不到对正在执行的工作项。
关键点:取消的核心是把 flag 置回 IDLE——无论延迟中(停定时器)还是队列中(摘节点),只要 flag 回到 IDLE,工作项就不会被执行。正在执行的工作项无法强制中断,只能等它跑完(同步)或返回 OS_BUSY(异步)。
第五步:同步模式等待工作项执行完毕(条件:sync == OS_TRUE 且 work_runing == 1)—— 等待信号量
os_spin_unlock_irqrestore(&queue->lock, key);
if (sync)
{
if (work_runing)
{
(void)os_semaphore_wait(wait_sem_id, OS_WAIT_FOREVER);
}
(void)os_semaphore_destroy(wait_sem_id);
os_work_deinit(wait_work_id);
}
}
return ret;进入本步的条件:第四步解锁后。若 sync == OS_TRUE:
- 条件
work_runing == 1(工作项正在执行)→os_semaphore_wait(wait_sem_id, OS_WAIT_FOREVER)阻塞等待,直到wait_work被执行(其回调_k_work_waitpost 信号量)——即当前工作项执行完毕; - 条件
work_runing == 0(工作项是 DELAY 或 PENDING,已被取消,未在运行)→ 跳过os_semaphore_wait(无需等待),直接进入清理。
wait_work post 信号量之后的完整收尾(对应第三步流程的 ⑤~⑧):
⑤ 调用者被信号量唤醒 → os_semaphore_wait(wait_sem_id, OS_WAIT_FOREVER) 返回
(此刻工作线程已执行完当前 work_func,并执行了 wait_work 回调 post 了 wait_sem)
⑥ 调用者确认"当前工作项已执行完",可以安全返回
⑦ 清理临时资源:
- os_semaphore_destroy(wait_sem_id):销毁临时完成信号量
- os_work_deinit(wait_work_id):反初始化临时哨兵工作项
⑧ _k_work_stop 返回 ret(OS_SUCCESS),os_work_stop 返回给调用者关键点 —— 为什么必须清理(为什么会有 ⑦):
wait_sem和wait_work是临时创建的一次性资源,只服务于这一次同步取消。- 在调用者确认完成后(
os_semaphore_wait返回后)销毁/反初始化,因为此刻wait_work已被工作线程执行过、不再需要。 - 不清理会导致资源泄漏(信号量句柄、工作项定时器残留)。
wait_work定义在_k_work_stop函数栈上(OS_WORK_DEFINE(wait_work)),所以os_work_deinit只做清理、不free(栈内存由函数返回自动回收)。
关键点 —— sync == OS_TRUE 的完成语义:无论工作项是被"提前取消"(未运行)还是"等待运行完",os_work_stop(work, OS_TRUE) 返回后都保证"该工作项不会再执行"——这正是同步取消与异步取消(OS_BUSY)的本质区别。
关键点:同步取消(sync == OS_TRUE)的语义是"确保取消完成后,工作项一定不会再执行"——即使工作项正在跑,也会等待它跑完再返回。异步取消(sync == OS_FALSE)只取消"未执行"的工作项,正在执行时返回 OS_BUSY。
os_work_stop / _k_work_stop 的 if-else 条件走向总结:
_k_work_stop() 条件走向总结:
├── OS_ASSERT 参数校验(work 非空 / 已初始化)任一失败 → 断言崩溃
├── if (work->workqueue == OS_NULL)(从未提交过)
│ └── 返回 OS_FAILURE
└── else(已提交到某队列)
├── 若 sync == OS_TRUE → 创建等待信号量 + 等待工作项(wait_work,回调 _k_work_wait)
├── if (flag == OS_WORK_STAGE_DELAY,延迟中)
│ └── os_timer_stop 停定时器;flag = IDLE → 取消成功
├── else if (flag == OS_WORK_STAGE_PENDING,队列中)
│ └── os_list_del 摘节点;flag = IDLE → 取消成功
└── else(flag == IDLE 但正在执行,即 queue->work_current == work)
├── 若 sync == OS_TRUE → 把 wait_work 插入队首,等当前执行完再返回
└── 若 sync == OS_FALSE → 打警告,返回 OS_BUSY(无法异步取消)
└── 若 sync == OS_TRUE:
├── 若 work_runing → os_semaphore_wait 等待执行完
└── 清理 wait_sem / wait_work,返回 OS_SUCCESS使用示例:
/* 异步取消:取消未执行的工作项 */
os_err_t ret = os_work_stop(work_id, OS_FALSE);
if (ret == OS_BUSY)
{
os_kprintf("work is running, cannot async cancel\r\n");
}
/* 同步取消:确保取消后工作项不再执行 */
ret = os_work_stop(work_id, OS_TRUE);
if (ret == OS_EOK)
{
os_kprintf("work guaranteed stopped\r\n");
}2.7 系统工作队列
os_err_t os_sys_work_run(os_work_id work_id, os_tick_t delay_time);将工作项提交到系统预置的工作队列(gs_sys_workq)。不需要用户自己创建工作队列,适用于简单场景。
需要启用
OS_USING_SYSTEM_WORKQUEUE。
完整源码(kernel/source/os_workqueue.c,与项目一致):
static OS_TASK_STACK_DEFINE(gs_sys_worker_task_stack, OS_SYSTEM_WORKQUEUE_STACK_SIZE);
OS_WORKQUEUE_DEFINE(gs_sys_workq);
static os_workqueue_id gs_sys_workq_id;
os_err_t os_sys_work_run(os_work_id work_id, os_tick_t delay_time)
{
OS_ASSERT(work_id);
OS_ASSERT(delay_time < (OS_TICK_MAX / 2));
return os_work_run(gs_sys_workq_id, work_id, delay_time);
}分步详解:
第一步:参数校验(条件:work_id 非空、delay_time 合法)—— 不满足则断言崩溃
OS_ASSERT(work_id);
OS_ASSERT(delay_time < (OS_TICK_MAX / 2));进入本步的条件:调用 os_sys_work_run() 时无条件执行。work_id 非空、delay_time 合法。
第二步:转发到系统工作队列(无条件执行)—— 调用 os_work_run(gs_sys_workq_id, work_id, delay_time)
return os_work_run(gs_sys_workq_id, work_id, delay_time);进入本步的条件:第一步断言通过。直接调用 os_work_run,队列固定为系统预置的 gs_sys_workq_id。返回 os_work_run 的结果(OS_EOK / OS_BUSY / OS_FAILURE)。
系统工作队列的初始化(os_sys_workqueue_init,kernel/source/os_workqueue.c,与项目一致):
os_err_t os_sys_workqueue_init(void)
{
os_err_t ret = OS_SUCCESS;
gs_sys_workq_id = os_workqueue_create(&gs_sys_workq,
"sys_work",
OS_TASK_STACK_BEGIN_ADDR(gs_sys_worker_task_stack),
OS_TASK_STACK_SIZE(gs_sys_worker_task_stack),
OS_SYSTEM_WORKQUEUE_PRIORITY,
-1);
if (!gs_sys_workq_id)
{
OS_KERN_LOG(KERN_ERROR, WORKQ_TAG, "Create system workqueue failed");
ret = OS_FAILURE;
}
return ret;
}
OS_INIT_CALL(os_sys_workqueue_init, OS_INIT_LEVEL_POST_KERNEL, OS_INIT_SUBLEVEL_HIGH);- 系统工作队列在内核初始化后自动创建(
OS_INIT_CALL),名为"sys_work",优先级为OS_SYSTEM_WORKQUEUE_PRIORITY。 - 用户只需调用
os_sys_work_run提交,无需自己创建工作队列。
使用示例:
OS_WORK_DEFINE(g_sys_work);
static void sys_work_func(void *data)
{
os_kprintf("system workqueue: %s\r\n", (char *)data);
}
os_work_id work_id = os_work_init(&g_sys_work, sys_work_func, "hello");
os_err_t ret = os_sys_work_run(work_id, 500); /* 500 ticks 后由系统工作队列执行 */
if (ret != OS_EOK)
{
os_kprintf("sys work submit failed: %d\r\n", ret);
}📝 本节小结
核心要点回顾
本节围绕工作队列机制展开,核心要点如下:
1. 工作队列的本质
- 工作队列是延迟执行/异步处理机制:工作项提交到队列后,由专门的工作线程(worker)异步执行其回调。
- 内部用二值信号量(
sem_id)唤醒工作线程 + 自旋锁(lock)保护链表,依赖信号量(需启用OS_USING_SEMAPHORE)。
2. 两个核心对象
| 对象 | 控制块 | 关键字段 |
|---|---|---|
| 工作队列 | os_workqueue_t | work_list_head(链表)、work_current(执行中工作项)、worker_task(工作线程)、sem_id(信号量)、lock |
| 工作项 | os_work_t | work_func(回调)、data(参数)、timer(延迟定时器)、flag(阶段状态)、workqueue(所属队列) |
3. 工作项的三个阶段
| 阶段 | flag 值 | 说明 |
|---|---|---|
IDLE | 0x0 | 空闲(初始化/执行完/取消后) |
DELAY | 0x1 | 延迟等待(定时器倒计时) |
PENDING | 0x2 | 在队列中等待执行 |
4. 提交(os_work_run)的行为
| 情况 | 行为 |
|---|---|
delay_time == 0 | 立即入队(_k_work_commit_to_queue_tail) |
delay_time > 0 | 进入 DELAY,定时器到期后 _k_work_timeout 入队 |
| 已提交到别的队列 | 返回 OS_FAILURE |
| 已在队列/延迟中 | 返回 OS_BUSY |
5. 取消(os_work_stop)的行为
sync 值 | 行为 |
|---|---|
OS_FALSE(异步) | 只取消未执行的(DELAY 停定时器 / PENDING 摘节点);正在执行返回 OS_BUSY |
OS_TRUE(同步) | 若正在执行,等待其执行完再返回,确保取消后不再执行 |
6. 系统工作队列
os_sys_work_run提交到系统预置队列gs_sys_workq(内核初始化自动创建,sys_work,优先级OS_SYSTEM_WORKQUEUE_PRIORITY)。
关键易错点速查
| 易错点 | 正确做法 |
|---|---|
未启用 OS_USING_SEMAPHORE 就使用工作队列 | 源码 #error "Make sure semaphore is enable",必须先启用信号量 |
| 工作项未初始化就提交 | 必须先 os_work_init(断言 object_inited == OS_KOBJ_INITED) |
| 重复提交同一工作项 | 工作项非 IDLE 时 os_work_run 返回 OS_BUSY;需先 os_work_stop 取消 |
| 一个工作项提交到多个队列 | 返回 OS_FAILURE(work->workqueue 只能绑定一个队列) |
| 工作项回调里长时间阻塞 | 工作线程是串行的,一个阻塞会堵住后续所有工作项 |
| 中断里创建/销毁工作队列 | 禁止(os_is_irq_active() 断言),中断只能提交工作项 |
| 反初始化前未取消工作项 | 先 os_work_stop 再 os_work_deinit,避免误操作未执行的工作项 |
忽略 os_work_run/os_work_stop 返回值 | OS_BUSY/OS_FAILURE 需处理,误判为成功会导致逻辑错误 |
下一步
工作队列用于延迟执行和 Bottom-Half 处理。接下来请学习下一小节:
- 4.4 内存管理:了解如何用内存池/内存堆管理动态内存