写点什么

Linux 设备驱动系列 (14) —— 使用全局工作队列

  • 2024-06-05
    浙江
  • 本文字数:3609 字

    阅读完需:约 12 分钟

Linux设备驱动系列(14) —— 使用全局工作队列

关注微信公众号: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 这类机制:


  • 如果被延迟的任务在执行过程中需要睡眠,选择 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 <feifei@gmail.com>");MODULE_DESCRIPTION("A simple device driver");MODULE_VERSION("1.13");
复制代码


代码编译运行,结果如图所示:




关注微信公众号:Linux 内核拾遗

文章来源:https://mp.weixin.qq.com/s/Nm2cSgffcrplMYdUdUzfiA


发布于: 刚刚阅读数: 6
用户头像

聚沙成塔 2023-01-12 加入

分享Linux内核开发相关的编程语言、开发调试工具链、计算机组成及操作系统内核知识、Linux社区最新资讯等

评论

发布
暂无评论
Linux设备驱动系列(14) —— 使用全局工作队列_Linux内核_Linux内核拾遗_InfoQ写作社区