关注微信公众号:Linux 内核拾遗
文章来源:https://mp.weixin.qq.com/s/Nm2cSgffcrplMYdUdUzfiA
前面介绍系统中断的文章中提到过,中断处理通常分为上半部(Top Half)和下半部(Bottom Half)。系统接收到中断时,首先执行上半部,处理时间敏感的工作,如确认中断源或读取数据,然后将复杂的任务推迟到下半部处理,确保系统能够快速响应其他中断请求。
Linux设备驱动系列(12) —— 系统中断概述
Linux设备驱动系列(13) —— 系统中断编程
中断下半部作为处理中断的一种机制,用于延迟执行较复杂或耗时的任务,以提高系统的中断处理效率。下半部常通过软中断(softirq)、任务队列(tasklet)或工作队列(workqueue)等方式实现,适用于网络包处理、定时器任务和外设数据处理等场景。
本文将对工作队列的工作原理和基本用法作详细的介绍。
1 Workqueue 介绍
Linux 工作队列(Workqueue)是内核中处理延迟任务的一种机制,在 v2.6 版本内核中引入。工作队列将任务的处理延迟到内核线程中,并且在进程上下文中执行,因此使得任务处理过程可以睡眠、被调度或者执行复杂/耗时的操作。
工作队列通常应用在中断处理的下半部,对比 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:
int flush_work( struct work_struct *work );
void flush_scheduled_work( void );
复制代码
2.4 从 workqueue 中取消 work
允许取消 workqueue 中尚未调用 handler 进行处理的 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 <feifei@gmail.com>");
MODULE_DESCRIPTION("A simple device driver");
MODULE_VERSION("1.13");
复制代码
代码编译运行,结果如图所示:
关注微信公众号:Linux 内核拾遗
文章来源:https://mp.weixin.qq.com/s/Nm2cSgffcrplMYdUdUzfiA
评论