关注微信公众号:Linux内核拾遗
前面介绍系统中断的文章中提到过,中断处理通常分为上半部(Top Half)和下半部(Bottom Half)。系统接收到中断时,首先执行上半部,处理时间敏感的工作,如确认中断源或读取数据,然后将复杂的任务推迟到下半部处理,确保系统能够快速响应其他中断请求。
中断下半部作为处理中断的一种机制,用于延迟执行较复杂或耗时的任务,以提高系统的中断处理效率。下半部常通过软中断(softirq)、任务队列(tasklet)或工作队列(workqueue)等方式实现,适用于网络包处理、定时器任务和外设数据处理等场景。
本文将对工作队列的工作原理和基本用法作详细的介绍。
1 Workqueue介绍
Linux工作队列(Workqueue)是内核中处理延迟任务的一种机制,在v2.6版本内核中引入。工作队列将任务的处理延迟到内核线程中,并且在进程上下文中执行,因此使得任务处理过程可以睡眠、被调度或者执行复杂/耗时的操作。
工作队列通常应用在中断处理的下半部,对比softirq/tasklet这类机制:
- 如果被延迟的任务在执行过程中需要睡眠,选择workqueue。
- 如果被延迟的任务执行过程中不会睡眠,选择softirq/tasklet。
Linux内核中提供了两类工作队列的实现方式:
- 使用全局工作队列(静态/动态)。
- 创建自定义的工作队列。
2 使用全局workqueue
这种实现方式下,开发者无需创建任何workqueue或者worker线程,只需要初始化任务(work)即可。
2.1 初始化work
Linux内核提供了两种初始化work的方式:静态方式和动态方式。
2.1.1 静态方式
// 例子:DECLARE_WORK(workqueue, workqueue_fn);
DECLARE_WORK(name, void (*func)(void *))
上面DECLARE_WORK宏将创建一个名为name的struct work_struct
类型的变量,用于表示具体的任务(work),并且绑定了相应的处理函数func。该任务在工作队列中被调度执行时将会调用func函数进行任务处理。
2.1.2 动态方式
// 例子:INIT_WORK(&workqueue, workqueue_fn);
INIT_WORK(work, work_fn)
INIT_WORK宏也是初始化一个work结构,但是这里work是一个struct work_struct
类型的指针,指向要初始化的目标任务,它应该是一个已经声明好的变量。
2.2 调度work
Linux内核中提供了多种方法来将一个初始化好的work调度到特定的workqueue中。
2.2.1 schedule_work
int schedule_work( struct work_struct *work );
schedule_work将work放入内核全局workqueue中:如果当前work不在workqueue中,则将其加入workqueue;否则保持其在内核全局workqueue中的相同位置。
2.2.2 scheduled_delayed_work
int scheduled_delayed_work( struct delayed_work *dwork, unsigned long delay );
相比于schedule_work,函数scheduled_delayed_work等待一段时间后将work加入workqueue中。
2.2.3 schedule_work_on
int schedule_work_on( int cpu, struct work_struct *work );
schedule_work_on将work调度到特定的处理器(cpu)上执行。
2.2.4 scheduled_delayed_work_on
int scheduled_delayed_work_on(int cpu, struct delayed_work *dwork, unsigned long delay );
相比于schedule_work_on,函数scheduled_delayed_work_on等待一段时间后将work加入workqueue中,并且调度到特定的处理器(cpu)上执行。
2.3 从workqueue中删除work
还可以使用一些辅助函数来刷新或取消workqueue中的work:
- flush_work:刷新特定的work并等待其执行完成,该调用会导致阻塞。
- flush_scheduled_work:刷新内核全局workqueue上的所有work。
int flush_work( struct work_struct *work );
void flush_scheduled_work( void );
2.4 从workqueue中取消work
允许取消workqueue中尚未调用handler进行处理的work:
- cancel_work_sync:取消workqueue中尚未调度执行的work,或者如果该work以及在执行过程中,那么当前调用将阻塞直到handler执行完成。
- cancel_delayed_work_sync:作用类似于cancel_work_sync,但是它针对的是延迟入队的工作(delayed work)。
int cancel_work_sync( struct work_struct *work );
int cancel_delayed_work_sync( struct delayed_work *dwork );
2.5 检查workqueue
还可以通过以下的函数来检查workqueue中某项work是否处于挂起状态,尚未被调度执行:
work_pending( work );
delayed_work_pending( work );
3 全局workqueue代码演示
下面代码演示了全局workqueue的静态和动态两种使用方式,其中静态方式创建的work调度到任意的cpu上执行,而动态方式创建的work固定调度到CPU[3]上执行。
kernel_driver.c
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/sysfs.h>
#include <linux/kobject.h>
#include <linux/interrupt.h>
#include <asm/io.h>
#include <linux/err.h>
#include <linux/smp.h>
#define IRQ_NO 63
static void static_wq_fn(struct work_struct *work)
{
pr_info("Static workqueue function called on CPU[%d]\n", smp_processor_id());
}
static DECLARE_WORK(static_work, static_wq_fn);
static void dynanic_wq_fn(struct work_struct *work)
{
pr_info("Dynamic workqueue function called on CPU[%d]\n", smp_processor_id());
}
static struct work_struct dynamic_work;
static irqreturn_t irq_handler(int irq, void *dev_id)
{
pr_info("Shared IRQ[%d]: Interrupt Occurred\n", irq);
schedule_work(&static_work);
schedule_work_on(3, &dynamic_work);
return IRQ_HANDLED;
}
volatile int my_value = 0;
dev_t dev = 0;
static struct class *dev_class;
static struct cdev my_cdev;
struct kobject *kobj_ref;
static ssize_t sysfs_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%d", my_value);
}
static ssize_t sysfs_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
sscanf(buf, "%d", &my_value);
return count;
}
struct kobj_attribute my_attr = __ATTR(my_value, 0660, sysfs_show, sysfs_store);
static ssize_t my_read(struct file *filp,
char __user *buf, size_t len, loff_t *off)
{
generic_handle_irq(IRQ_NO);
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.read = my_read,
};
static int __init my_driver_init(void)
{
if ((alloc_chrdev_region(&dev, 0, 1, "my_dev")) < 0)
return -1;
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;
my_cdev.ops = &fops;
if ((cdev_add(&my_cdev, dev, 1)) < 0)
goto r_class;
if (IS_ERR(dev_class = class_create(THIS_MODULE, "my_class")))
goto r_class;
if (IS_ERR(device_create(dev_class, NULL, dev, NULL, "my_device")))
goto r_device;
kobj_ref = kobject_create_and_add("my_sysfs", kernel_kobj);
if (sysfs_create_file(kobj_ref, &my_attr.attr))
goto r_sysfs;
if (request_irq(IRQ_NO, irq_handler, IRQF_SHARED,
"my_device", (void *)(irq_handler)))
goto irq;
INIT_WORK(&dynamic_work, dynanic_wq_fn);
return 0;
irq:
free_irq(IRQ_NO, (void *)(irq_handler));
r_sysfs:
kobject_put(kobj_ref);
sysfs_remove_file(kernel_kobj, &my_attr.attr);
r_device:
device_destroy(dev_class, dev);
class_destroy(dev_class);
r_class:
unregister_chrdev_region(dev, 1);
cdev_del(&my_cdev);
return -1;
}
static void __exit my_driver_exit(void)
{
free_irq(IRQ_NO, (void *)(irq_handler));
kobject_put(kobj_ref);
sysfs_remove_file(kernel_kobj, &my_attr.attr);
device_destroy(dev_class, dev);
class_destroy(dev_class);
cdev_del(&my_cdev);
unregister_chrdev_region(dev, 1);
}
module_init(my_driver_init);
module_exit(my_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("feifei <[email protected]>");
MODULE_DESCRIPTION("A simple device driver");
MODULE_VERSION("1.13");
代码编译运行,结果如图所示:
关注微信公众号:Linux内核拾遗