一、什么是工作队列
工作队列(work queue)是Linux kernel中将工作推后执行的一种机制。这种机制和BH或Tasklets不同之处在于工作队列是把推后的工作交由一个内核线程去执行,因此工作队列的优势就在于它允许重新调度甚至睡眠。
二、如何使用工作队列
1、创建工作队列结构体(struct workqueue_struct *mfd_wq;)和工作结构体(struct work_struct work;)。
2、使用create_singlethread_workqueue或者create_workqueue创建一个工作队列。
3、使用INIT_WORK初始化工作结构体(将工作函数赋值给工作结构体中的func)。
4、在适当的时候调用queue_work,queue_work会将工作结构体所对应的工作函数激活,然后这个工作函数就可以在内核线程中运行,当工作函数执行完毕,又会进入失效的状态,等待下一次调用queue_work激活。
三、结构体
这一节主要是展示一下两个要用到的结构体的内容,不用太过关注,可以直接跳过。
1、工作队列结构体
源码路径:linux-4.9/kernel/queuework.c。
struct workqueue_struct {
struct list_head pwqs; /* WR: all pwqs of this wq */
struct list_head list; /* PR: list of all workqueues */
struct mutex mutex; /* protects this wq */
int work_color; /* WQ: current work color */
int flush_color; /* WQ: current flush color */
atomic_t nr_pwqs_to_flush; /* flush in progress */
struct wq_flusher *first_flusher; /* WQ: first flusher */
struct list_head flusher_queue; /* WQ: flush waiters */
struct list_head flusher_overflow; /* WQ: flush overflow list */
struct list_head maydays; /* MD: pwqs requesting rescue */
struct worker *rescuer; /* I: rescue worker */
int nr_drainers; /* WQ: drain in progress */
int saved_max_active; /* WQ: saved pwq max_active */
struct workqueue_attrs *unbound_attrs; /* PW: only for unbound wqs */
struct pool_workqueue *dfl_pwq; /* PW: only for unbound wqs */
#ifdef CONFIG_SYSFS
struct wq_device *wq_dev; /* I: for sysfs interface */
#endif
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
char name[WQ_NAME_LEN]; /* I: workqueue name */
struct rcu_head rcu;
/* hot fields used during command issue, aligned to cacheline */
unsigned int flags ____cacheline_aligned; /* WQ: WQ_* flags */
struct pool_workqueue __percpu *cpu_pwqs; /* I: per-cpu pwqs */
struct pool_workqueue __rcu *numa_pwq_tbl[]; /* PWR: unbound pwqs indexed by node */
};
2、工作结构体
源码路径:linux-4.9/include/linux/queuework.h。
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
四、源码分析
struct xx_test
{
int irq;
unsigned char gpio;
struct work_struct work;
struct workqueue_struct *mfd_wq;
};
/* 创建设备data结构体 */
struct xx_test *test = NULL;
/* 中断处理函数 */
irqreturn_t mfd_irq(int irq, void *handle)
{
/* 将工作加入工作队列,工作队列会调度work开始工作 */
queue_work(mfd_wq, &test->work);
}
/* 工作函数 */
void work_handler(struct work_struct *work)
{
/* container_of函数,通过指向结构体成员函数的指针获取指向该结构体的指针 */
struct xx_test *xx = container_of(work, struct changan_mfd, work);
printk("this is a test!\n");
}
int xx_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
/* 为test结构体指针开辟动态内存 */
test = devm_kzalloc(&client->dev, sizeof(struct xx_test), GFP_KERNEL);
/* 创建一个单线程的工作队列,返回值类型为“struct workqueue_struct” */
test->mfd_wq = create_singlethread_workqueue("mfd_wq");
if (!mfd_wq){
err();
return -ENOMEM;
}
/* 初始化work工作结构体,将工作处理函数赋值给work结构体中的func */
INIT_WORK(&test->work, work_handler);
/* 为gpio口申请软件中断号 */
test->irq = gpio_to_irq(test->gpio);
irq_flags = irq_get_trigger_type(ts->irq);
/* 中断的动态申请及注册,这个中断只有顶半函数 */
rc = request_threaded_irq(test->irq, mfd_irq, NULL, irqflags, "mfd", test);
if (rc) {
err();
return rc;
}
}
static const struct of_device_id changan_of_match[] = {
{ .compatible = "MFD", },
{},
};
static const struct i2c_device_id changan_mfd_idtable[] =
{
{ "MFD", 0 },
{ }
};
static struct i2c_driver changan_mfd_driver =
{
.driver = {
.name = "MFD",
.of_match_table = of_match_ptr(changan_of_match),
.pm = &pm_ops,
},
.id_table = xx_idtable,
.probe = xx_probe,
.remove = xx_remove,
};
module_i2c_driver(mfd_driver);
可以看到,这份伪代码属于i2c设备驱动的一部分。
1、module_i2c_driver
首先,我们看到,79行的代码“module_i2c_driver(mfd_driver)”。看到这里,可能有一些朋友不太了解,这是什么?按理来说,这里不应该是“module_init”之类的函数吗?其实,“module_i2c_driver”是i2c.h所定义的宏,宏定义如下。
#define module_i2c_driver(__i2c_driver) module_driver(__i2c_driver, i2c_add_driver, i2c_del_driver)
而“module_driver”也是宏,它在device.h中定义,宏定义如下。
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
因此,将“module_i2c_driver(mfd_driver)”展开如下:
static int __init mfd_driver_init(void)
{
return i2c_add_driver(&mfd_driver);
}
module_init(mfd_driver_init);
static void __exit mfd_driver_exit(void)
{
i2c_del_driver(&mfd_driver);
}
module_exit(mfd_driver_exit);
其实,和普通的i2c设备驱动入口没什么不同,只是这里换成了宏而已。
2、create_singlethread_workqueue
我们继续看代码。
为了不浪费时间,我们直接进入probe函数,看到示例代码34行的“create_singlethread_workqueue(mfd_wq)”。
create_singlethread_workqueue定义在workqueue.h中,也是一个宏,定义如下。
#define create_singlethread_workqueue(name) alloc_ordered_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, name)
该宏函数返回一个类型为struct workqueue_struct的指针变量,这个指针变量所指向的内存地址在函数内部调用kzalloc动态生成,所以driver在不再使用该work queue的情况下应调用“void destroy_workqueue(struct workqueue_struct *wq)”来释放此处的内存地址。
3、create_singlethread_workqueue与create_workqueue的区别
使用create_singlethread_workqueue创建工作队列,即使对于多CPU系统,内核也只负责在一个cpu上创建一个worker_thread内核线程;而使用create_workqueue创建工作队列对于多CPU系统,内核将会在每个CPU上创建一个worker_thread内核线程,使得线程处理的事务能够并行化。
虽然使用create_workqueue可以使得线程处理的事务能够并行化,但这同时也会大量占用我们的cpu。如果不是有特别需求,一般而言,我们都只使用create_singlethread_workqueue在一个cpu上创建一个worker_thread内核线程。
4、INIT_WORK
继续回到示例代码,看到41行。INIT_WORK在workqueue.h中定义,是一个宏函数,宏定义如下。
#define INIT_WORK(_work, _func) __INIT_WORK((_work), (_func), 0)
#ifdef CONFIG_LOCKDEP
#define __INIT_WORK(_work, _func, _onstack) \
do { \
static struct lock_class_key __key; \
\
__init_work((_work), _onstack); \
(_work)->data = (atomic_long_t) WORK_DATA_INIT(); \
lockdep_init_map(&(_work)->lockdep_map, #_work, &__key, 0); \
INIT_LIST_HEAD(&(_work)->entry); \
(_work)->func = (_func); \
} while (0)
#else
#define __INIT_WORK(_work, _func, _onstack) \
do { \
__init_work((_work), _onstack); \
(_work)->data = (atomic_long_t) WORK_DATA_INIT(); \
INIT_LIST_HEAD(&(_work)->entry); \
(_work)->func = (_func); \
} while (0)
#endif
宏函数中其实需要我们关注也不多。
“(_work)->data = (atomic_long_t) WORK_DATA_INIT();”初始化私有数据。
“INIT_LIST_HEAD(&(_work)->entry);”初始化链表。
“(_work)->func = (_func);”将工作函数赋值给工作结构体的func。
5、queue_work
queue_work出现在示例代码16行,可以看到它是在中断函数中被调用的。
queue_work的源码(workqueue.h和workqueue.c)比较绕,我们就不分析了。
对queue_work的功能可以这么理解:queue_work会将工作结构体所对应的工作函数激活,然后这个工作函数会在create_singlethread_workqueue创建的内核线程中运行,当工作函数执行完毕,又会进入失效的状态,等待下一次激活。
五、总结
关于工作队列就分析到这里了,希望对大家有所帮助,有不懂的欢迎在评论区留言。