Linux内核 -- INIT_WORK 使用与注意事项
Linux内核 – INIT_WORK 使用与注意事项
一、概述
在 Linux 内核中,workqueue
(工作队列)机制用于将任务从中断上下文中异步转移到进程上下文中执行,降低中断处理负担。INIT_WORK
是工作队列机制的核心之一,用于初始化普通工作项(struct work_struct
)。
二、INIT_WORK 简介
#define INIT_WORK(_work, _func) \__INIT_WORK((_work), (_func), 0)
INIT_WORK()
用于初始化一个工作项 struct work_struct
并指定回调函数 _func
,此工作项随后可通过 schedule_work()
或 queue_work()
提交到默认或指定工作队列中执行。
三、使用方法
3.1 示例代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/workqueue.h>static struct work_struct my_work;static void work_handler(struct work_struct *work)
{pr_info("Workqueue executed in process context\n");
}static int __init my_module_init(void)
{INIT_WORK(&my_work, work_handler);schedule_work(&my_work);pr_info("Work scheduled\n");return 0;
}static void __exit my_module_exit(void)
{cancel_work_sync(&my_work);pr_info("Module exited cleanly\n");
}module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
3.2 关键 API 说明
接口 | 说明 |
---|---|
INIT_WORK() | 初始化普通工作项 |
schedule_work() | 提交工作项至默认队列 system_wq |
queue_work(wq, w) | 提交到自定义队列 |
cancel_work_sync() | 等待工作项完成或取消 |
flush_work() | 等待工作项执行完毕(不适合模块卸载) |
work_pending() | 检查工作项是否已调度 |
四、注意事项
4.1 同一 work_struct
不能重复调度
- 若某个工作项已被调度但尚未执行完毕,再次调用
queue_work()
无效。 work_struct
结构中有一个PENDING
位标志,防止被重复添加至队列。
if (!work_pending(&my_work))queue_work(wq, &my_work);
4.2 回调函数中可重新调度自身
static void my_work_fn(struct work_struct *work)
{// do task ...queue_work(my_wq, work); // 再次调度
}
适用于链式任务或轮询逻辑。但要加条件判断,避免死循环。
4.3 模块卸载前必须同步取消工作项
必须使用 cancel_work_sync()
来确保:
- 若任务在队列中,则等待其完成或取消;
- 避免模块卸载后,工作函数引用释放内存导致 UAF。
4.4 多个任务建议使用多个 work_struct
实例
#define MAX_WORKS 8
struct work_struct works[MAX_WORKS];for (int i = 0; i < MAX_WORKS; i++) {INIT_WORK(&works[i], my_work_fn);queue_work(my_wq, &works[i]);
}
避免同一任务被并发调度失败的问题。
五、进阶建议
5.1 使用 delayed_work 实现周期调度
struct delayed_work my_delayed_work;
INIT_DELAYED_WORK(&my_delayed_work, my_delayed_fn);
schedule_delayed_work(&my_delayed_work, msecs_to_jiffies(1000));
适合定时任务、轮询扫描、状态检查等场景。
5.2 创建自定义工作队列
struct workqueue_struct *my_wq;
my_wq = alloc_workqueue("my_queue", WQ_UNBOUND | WQ_HIGHPRI, 1);
queue_work(my_wq, &my_work);
卸载时使用 destroy_workqueue(my_wq);
。
六、调试建议
工具 | 用途 |
---|---|
ftrace | 跟踪工作函数调度和执行 |
cat /proc/workqueue | 查看工作队列状态 |
kmemleak | 检查 work_struct 生命周期泄漏 |
七、总结
问题 | 是否允许 |
---|---|
上一次工作执行完毕,再调度 | ✅ 允许 |
上一次工作还在队列中 | ❌ 不会调度 |
上一次工作正在执行 | ❌ 不会调度 |
回调函数中再次调度自己 | ✅ 可行 |
多个并发任务使用同一个 work_struct | ❌ 不推荐,需多个实例 |
八、参考建议
- 避免
work_struct
UAF,使用cancel_work_sync
配合模块生命周期; - 使用
INIT_DELAYED_WORK
替代INIT_WORK
实现定时、周期性处理; - 调试期间可加上
WARN_ON(work_pending(...))
避免重复调度。
如需进一步深入,可以参考:
- 《Linux Kernel Development》(Robert Love)
- 《Linux Device Drivers, Third Edition》
- Linux 源码目录:
kernel/workqueue.c
,include/linux/workqueue.h