掘金 后端 ( ) • 2024-06-06 11:18

关注微信公众号: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 <[email protected]>");
MODULE_DESCRIPTION("A simple device driver");
MODULE_VERSION("1.13");

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

image-20240605230634380

image-20240605230651745

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

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