1. 概念
k_work_schedule 是 Zephyr 内核提供的一种延时执行 机制,属于可延迟工作项 (k_work_delayable)的一部分。它的核心特点是:
-
非阻塞:调用后立即返回,不挂起当前线程。
-
可取消 :在任务执行前可通过
k_work_cancel_delayable撤销。 -
后台执行:任务在系统工作队列线程中运行,不占用调用线程或中断上下文。
-
单次触发:每次调度只执行一次,如需周期执行需在回调中重新调度。
2. API 速览
| API | 功能 | 关键参数 |
|---|---|---|
k_work_init_delayable(&work, handler) |
初始化延时工作项,绑定回调函数 handler |
handler 原型:void handler(struct k_work *work) |
k_work_schedule(&work, timeout) |
调度工作项在 timeout 后执行 |
timeout 可使用 K_MSEC()、K_SECONDS() 等 |
k_work_cancel_delayable(&work) |
取消尚未执行的延时工作项 | 返回 true 表示成功取消,false 表示未在等待 |
k_work_delayable_from_work(work) |
从 struct k_work* 反推 struct k_work_delayable*(在回调中常用) |
配合 CONTAINER_OF 获取包含结构体 |
3. 典型应用场景与代码示例(含取消操作)
3.1 超时监控(Timeout Watchdog)
场景 :发起 BLE 连接、HTTP 请求等异步操作,若超时未完成则执行错误处理。
关键:发起操作时调度延时工作,操作成功时取消该工作。
struct my_device {
struct k_work_delayable timeout_work;
bool connected;
};
/* 超时处理函数 */
static void timeout_handler(struct k_work *work)
{
struct my_device *dev = CONTAINER_OF(work, struct my_device, timeout_work.work);
if (!dev->connected) {
printk("Connection timeout! Disconnect and retry.\n");
/* 执行断开、重试等 */
}
}
/* 发起连接 */
void start_connection(struct my_device *dev)
{
dev->connected = false;
/* 实际发起连接(如 bt_conn_le_create)... */
/* 调度 10 秒超时监控 */
k_work_schedule(&dev->timeout_work, K_SECONDS(10));
}
/* 连接成功回调 */
void on_connect_success(struct my_device *dev)
{
dev->connected = true;
/* 连接成功,取消超时监控 */
if (k_work_cancel_delayable(&dev->timeout_work)) {
printk("Timeout work cancelled.\n");
}
}
3.2 防抖动(Debouncing)
场景 :处理按键、GPIO 中断等高频触发事件,只在信号稳定后响应一次。
关键:每次触发时取消前一个任务,重新调度。
static struct k_work_delayable debounce_work;
/* 按键处理回调(稳定后执行) */
static void key_handler(struct k_work *work)
{
printk("Key pressed (debounced)\n");
}
/* 中断服务程序(ISR) */
void button_isr(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
/* 取消之前待执行的任务(如果有) */
k_work_cancel_delayable(&debounce_work);
/* 重新调度,50ms 后执行 */
k_work_schedule(&debounce_work, K_MSEC(50));
}
/* 初始化工作项(在 main 或模块 init 中) */
void debounce_init(void)
{
k_work_init_delayable(&debounce_work, key_handler);
}
3.3 硬件上电稳定延时(Power-On Settling)
场景 :外设上电后需要等待内部稳定,再发送初始化命令。
关键:上电函数立即返回,延时工作项负责初始化;若上电后取消(如关机),则取消未执行的初始化任务。
static struct k_work_delayable init_work;
static bool power_on_flag = false;
/* 外设初始化回调 */
static void peripheral_init(struct k_work *work)
{
if (power_on_flag) {
printk("Peripheral powered on, initializing...\n");
/* 配置 I2C、SPI 等 */
} else {
printk("Init cancelled, skip.\n");
}
}
/* 上电函数(非阻塞) */
void power_on_peripheral(void)
{
power_on_flag = true;
/* 实际硬件上电(拉高使能引脚) */
// gpio_pin_set(enable_pin, 1);
/* 调度 100ms 后执行初始化 */
k_work_schedule(&init_work, K_MSEC(100));
}
/* 关机函数——取消尚未执行的初始化 */
void power_off_peripheral(void)
{
power_on_flag = false;
/* 实际硬件下电 */
// gpio_pin_set(enable_pin, 0);
/* 取消待执行的初始化工作项 */
if (k_work_cancel_delayable(&init_work)) {
printk("Pending init work cancelled.\n");
}
}
/* 模块初始化 */
void peripheral_module_init(void)
{
k_work_init_delayable(&init_work, peripheral_init);
}
3.4 重试与退避(Retry with Backoff)
场景 :操作失败后,等待一段时间再重试,且重试次数有限。
关键:在重试回调中再次调度自身,并增加间隔。
#define MAX_RETRIES 5
#define RETRY_INTERVAL_MS 1000
static struct k_work_delayable retry_work;
static uint8_t retry_count = 0;
/* 重试处理函数 */
static void retry_handler(struct k_work *work)
{
if (retry_count >= MAX_RETRIES) {
printk("Max retries reached, giving up.\n");
return;
}
if (/* 尝试操作,成功则返回 */) {
printk("Operation succeeded after %d retries.\n", retry_count);
retry_count = 0;
return;
}
/* 失败,增加计数,再次调度 */
retry_count++;
printk("Retry %d failed, scheduling next try.\n", retry_count);
k_work_schedule(&retry_work, K_MSEC(RETRY_INTERVAL_MS));
}
/* 启动重试流程(第一次调用) */
void start_retry(void)
{
retry_count = 0;
k_work_schedule(&retry_work, K_NO_WAIT); /* 立即执行第一次尝试 */
}
3.5 周期性轮询(非关键)
场景 :每隔几秒检查一次状态,但允许轻微延迟。
关键:在回调末尾再次调度自身。
static struct k_work_delayable poll_work;
static void poll_handler(struct k_work *work)
{
/* 执行轮询操作 */
// check_sensor();
/* 再次调度,5 秒后继续 */
k_work_schedule(&poll_work, K_SECONDS(5));
}
void start_polling(void)
{
k_work_schedule(&poll_work, K_SECONDS(5));
}
4. 取消操作的重要性和注意事项
4.1 何时需要取消?
-
操作已成功完成,无需超时处理(如连接成功)。
-
条件发生改变,原本的延时任务已失去意义(如关机、断开)。
-
系统进入低功耗状态,需要清空待办任务。
4.2 使用 k_work_cancel_delayable 的返回值
-
返回
true:表示工作项尚在等待队列中,已被成功取消。 -
返回
false:表示工作项未在等待(已执行、已取消或从未调度)。
通常,我们不强制检查返回值,但有时需据此做额外处理(如打印日志)。
4.3 注意事项
-
只能在非中断上下文中调用?
k_work_cancel_delayable可以在中断和线程中安全调用。 -
取消后能否重新调度?
完全可以,取消后再次调用
k_work_schedule会重新计时。 -
若回调正在执行,取消无效 :
如果工作项已经出队并在执行中,
k_work_cancel_delayable会返回false,此时应通过内部状态标志(如power_on_flag)让回调跳过操作。
5. 与 k_sleep、k_timer 的对比
| 特性 | k_work_schedule |
k_sleep |
k_timer |
|---|---|---|---|
| 阻塞调用线程 | 否(非阻塞) | 是(阻塞) | 否(定时器回调运行在系统定时器上下文) |
| 取消能力 | ✅ 可取消 | ❌ 不可取消(除非唤醒) | ✅ 可停止 |
| 执行上下文 | 系统工作队列(线程) | 调用线程(本身) | 定时器回调(可能 ISR 上下文,但通常为线程上下文) |
| 周期触发 | 需在回调中再次调度 | 不支持 | 内置周期模式 |
| 适用场景 | 单次延时任务、超时、重试、防抖、上电延时 | 顺序阻塞等待 | 高精度周期任务 |
| 推荐使用 | 事件驱动、非阻塞延时 | 简单顺序流程 | 严格周期要求(如 1ms 采样) |
6. 最佳实践总结
-
始终在模块初始化时 调用
k_work_init_delayable注册工作项。 -
调度前考虑取消:若工作项可能已在等待,先取消再重新调度(如防抖场景)。
-
在取消时善用返回值:根据返回值决定是否执行补充逻辑。
-
避免在回调中长时间阻塞:因为回调运行在系统工作队列,长时间阻塞会影响其他工作项。
-
组合状态标志 :如
power_on_flag可确保即使工作项被意外触发,也能根据状态跳过操作。 -
调试时打印取消信息:便于追踪任务生命周期。
7. 完整工程集成示例
#include <zephyr/kernel.h>
/* 定义一个包含工作项的结构体 */
struct my_ctx {
struct k_work_delayable init_work;
bool ready;
};
static struct my_ctx ctx;
/* 初始化回调 */
static void init_callback(struct k_work *work)
{
if (ctx.ready) {
printk("Init done.\n");
} else {
printk("Init skipped (cancelled).\n");
}
}
/* 启动初始化流程,带 2 秒延时 */
void start_initialization(void)
{
ctx.ready = true;
k_work_schedule(&ctx.init_work, K_SECONDS(2));
}
/* 取消初始化 */
void cancel_initialization(void)
{
ctx.ready = false;
if (k_work_cancel_delayable(&ctx.init_work)) {
printk("Init work cancelled.\n");
}
}
void main(void)
{
k_work_init_delayable(&ctx.init_work, init_callback);
/* 其他初始化... */
}
k_work_schedule 是 Zephyr 事件驱动编程的核心组件,熟练掌握它将极大提升异步任务管理的灵活性和效率。在实际项目中,结合取消操作,您可以构建出健壮的超时、重试、防抖等非阻塞逻辑,让系统响应更迅速、资源利用更高效。