提示:中断终篇 中断队列中的 并发管理工作队列
文章目录
前言
假设已经了解了共享工作队列,自定义工作队列,突然有个知识点 并发管理工作队列。 从字眼上看就是并发处理相关的知识点吧。
个人理解:当我们熟悉这个知识点后,在实际应用上面和自定义工作队列就是api 上面创建不一样而已。 核心还是在于这样创建 并发管理工作队列 的意义、内核处理方式需要熟悉。
并发管理工作队列,字面上讲 其实是一种队列的类型,和自定义工作队列一样定义,创建方式不一样而已。
一、 参考资料
Linux kernel workqueue机制分析
并发管理的工作队列 (cmwq)
Linux 内核中的工作队列:alloc_ordered_workqueue
讲解并发管理工作队列概念性内容还是需要自己去理解,参考资料可以多看看,自己理解下。 实际上 如前言所讲:和自定义队列就是创建时候api 方法不一样而已,但是对应的内核机制完全不一样。
二、源码驱动程序
源码驱动程序如下,测试验证:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
int irq;
struct workqueue_struct *test_workqueue;
struct work_struct test_workqueue_work;
// 工作项处理函数
void test_work(struct work_struct *work)
{
msleep(1000);
printk("This is test_work\n");
}
// 中断处理函数
irqreturn_t test_interrupt(int irq, void *args)
{
printk("This is test_interrupt\n");
queue_work(test_workqueue, &test_workqueue_work); // 提交工作项到工作队列
return IRQ_RETVAL(IRQ_HANDLED);
}
static int interrupt_irq_init(void)
{
int ret;
irq = gpio_to_irq(101); // 将GPIO映射为中断号
printk("irq is %d\n", irq);
// 请求中断
ret = request_irq(irq, test_interrupt, IRQF_TRIGGER_RISING, "test", NULL);
if (ret < 0)
{
printk("request_irq is error\n");
return -1;
}
// 用于创建和分配一个工作队列
test_workqueue = alloc_workqueue("test_workqueue", WQ_UNBOUND, 0);
INIT_WORK(&test_workqueue_work, test_work); // 初始化工作项
return 0;
}
static void interrupt_irq_exit(void)
{
free_irq(irq, NULL); // 释放中断
cancel_work_sync(&test_workqueue_work); // 取消工作项
flush_workqueue(test_workqueue); // 刷新工作队列
destroy_workqueue(test_workqueue); // 销毁工作队列
printk("bye bye\n");
}
module_init(interrupt_irq_init);
module_exit(interrupt_irq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("fangchen");
实验结果如下:
三、alloc_workqueue与create_workqueue的比较与使用
在Linux内核驱动开发中,alloc_workqueue()和create_workqueue()都是用于创建工作队列的函数,但它们有一些重要区别。
函数对比
特性 | alloc_workqueue() | create_workqueue() |
---|---|---|
引入版本 | 2.6.36+ | 2.6.12+ |
功能 | 更灵活,支持多种标志 | 较简单 |
并发控制 | 支持精细控制 | 固定并发 |
推荐使用 | 新代码推荐 | 旧代码兼容 |
内存回收支持 | 明确通过WQ_MEM_RECLAIM标志 | 自动支持 |
CPU绑定 | 可通过WQ_UNBOUND控制 | 默认绑定 |
alloc_workqueue() 详解
函数原型
struct workqueue_struct *alloc_workqueue(const char *fmt, unsigned int flags, int max_active);
常用标志组合
// 标准工作队列(可睡眠,内存可回收)
alloc_workqueue("name", WQ_MEM_RECLAIM, max_active);
// 高优先级工作队列
alloc_workqueue("name", WQ_MEM_RECLAIM | WQ_HIGHPRI, max_active);
// 非绑定CPU的工作队列(适合CPU密集型任务)
alloc_workqueue("name", WQ_UNBOUND | WQ_MEM_RECLAIM, max_active);
标志说明
- WQ_MEM_RECLAIM: 内存紧张时可回收
- WQ_HIGHPRI: 高优先级工作队列
- WQ_UNBOUND: 不绑定到特定CPU
- WQ_FREEZABLE: 可被系统冻结
- WQ_CPU_INTENSIVE: CPU密集型任务
create_workqueue() 详解
函数原型
struct workqueue_struct *create_workqueue(const char *name);
等效的alloc_workqueue表示
// create_workqueue("name") 等效于:
alloc_workqueue("name", WQ_MEM_RECLAIM, 1);
使用示例- alloc_workqueue - create_workqueue
#include <linux/workqueue.h>
struct workqueue_struct *wq;
struct work_struct work;
void work_handler(struct work_struct *work) {
printk(KERN_INFO "Work handler executing\n");
}
static int __init my_init(void) {
// 创建最大并发数为4的工作队列
wq = alloc_workqueue("my_wq", WQ_MEM_RECLAIM | WQ_UNBOUND, 4);
if (!wq)
return -ENOMEM;
INIT_WORK(&work, work_handler);
queue_work(wq, &work);
return 0;
}
static void __exit my_exit(void) {
flush_workqueue(wq); // 等待所有工作完成
destroy_workqueue(wq); // 销毁工作队列
}
#include <linux/workqueue.h>
struct workqueue_struct *wq;
struct work_struct work;
void work_handler(struct work_struct *work) {
printk(KERN_INFO "Work handler executing\n");
}
static int __init my_init(void) {
wq = create_workqueue("my_old_wq");
if (!wq)
return -ENOMEM;
INIT_WORK(&work, work_handler);
queue_work(wq, &work);
return 0;
}
static void __exit my_exit(void) {
flush_workqueue(wq);
destroy_workqueue(wq);
}
如何选择-创建中断队列
-
新代码:总是使用alloc_workqueue(),它提供更多控制和灵活性
-
维护旧代码:如果已有代码使用create_workqueue(),可以保持不动
-
特殊需求:需要CPU绑定:使用alloc_workqueue()不带WQ_UNBOUND;需要高优先级:使用WQ_HIGHPRI标志;需要精细并发控制:使用alloc_workqueue()的max_active参数
性能考虑
并发数选择:
- I/O密集型:可以设置较高的max_active
- CPU密集型:设置较低的max_active
CPU绑定:
- 绑定CPU(非WQ_UNBOUND)可以减少缓存失效
- 非绑定CPU(WQ_UNBOUND)可以更好利用多核
内存压力:
- 总是设置WQ_MEM_RECLAIM除非有特殊原因
常见问题
Q: 什么时候需要使用flush_workqueue()?
A: 在销毁工作队列前或需要确保所有工作完成时使用。注意:不能在原子上下文中调用。
Q: max_active设置为0是什么意思?
A: 表示使用默认值,通常是每CPU256个worker线程。
四、最佳实践
适合自定义工作队列的场景
- 需要严格隔离的实时驱动
- 必须保证特定优先级的任务
- 运行在较旧内核版本(2.6.36之前)
- 有特殊调度需求的专用硬件
适合CMWQ的场景
- 通用设备驱动
- 新内核版本(2.6.36+)开发
- 需要良好扩展性的高负载场景
- 希望减少系统线程总数的设计
性能对比
特性 | 自定义工作队列 | CMWQ |
---|---|---|
线程管理 | 静态 | 动态 |
系统开销 | 较高 | 较低 |
延迟确定性 | 较好 | 一般 |
多核扩展性 | 差 | 优秀 |
内存回收支持 | 需手动实现 | 内置支持 |
总结
这里进一步了解了终端中自定义队列的并发管理工作队列,后面开发中会大量用到的。