[Linux内核驱动]工作队列

工作队列

工作队列的使用方法和tasklet类似,但是工作队列的执行上下文是内核线程,可以调度和睡眠。

使用工作队列构造下面数据表,并通过设备读取

time delta inirq pid cpu command
 8890 0 0 3813 1 cat
 8890 0 0 108 0 kworker/u14:4
 8890 0 0 108 0 kworker/u14:4
 8890 0 0 108 0 kworker/u14:4
 8890 0 0 108 0 kworker/u14:4

struct work_struct

struct work_struct 是一个用来描述一个可延迟执行的工作的结构体。

使用 INIT_WORK(_work, _func) 宏可以初始化一个 work_struct 实例并与处理函数绑定。

工作队列API

alloc_workqueue

创建一个新的工作队列并返回一个指向新创建的workqueue_struct结构的指针。

struct workqueue_struct *alloc_workqueue(const char *name, int flags, int max_active,...);
  • name:这是一个格式字符串,用于指定工作队列的名称。它可以包含%s、%d等格式化占位符,后跟相应的可变参数(…)。
  • flags:用于配置工作队列的标志位。常见的标志包括:

  • WQ_UNBOUND:工作队列中的工作可以在任何CPU上执行,而不是仅限于创建工作队列的CPU。
  • WQ_MEM_RECLAIM:允许工作队列在内存不足时回收内存。
  • WQ_HIGHPRI:创建一个高优先级的工作队列,其中的工作会比其他工作队列中的工作更频繁地运行。
  • WQ_CPU_INTENSIVE:创建一个CPU密集型的工作队列,该队列中的工作会倾向于在相同的CPU上连续运行。
  • WQ_FREEZABLE:允许工作队列在冻结时暂停执行工作。

  • max_active:工作队列中可以同时运行的最大工作数。如果设置为0,则没有限制。

queue_work

这个函数用于将一个工作(work_struct)添加到工作队列中。当工作队列的调度器线程(或线程池中的某个线程)变得可用时,它会执行这个工作。这个函数接受一个指向workqueue_struct的指针和一个指向work_struct的指针作为参数。

int queue_work(struct workqueue_struct *wq, struct work_struct *work);

destroy_workqueue()

这个函数用于销毁一个工作队列。在销毁之前,它会确保队列中的所有工作都已完成。这个函数接受一个指向workqueue_struct结构的指针作为参数。

void destroy_workqueue(struct workqueue_struct *wq);

代码

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/sched.h>

#include <linux/workqueue.h>

#define WORK_QUEUE_MAJOR 0
#define WORK_QUEUE_MINOR 0
#define WORK_QUEUE_NR_DEVS 1

static int work_queue_major = WORK_QUEUE_MAJOR;
static int work_queue_minor = WORK_QUEUE_MINOR;
static int work_queue_nr_devs = WORK_QUEUE_NR_DEVS;

struct work_queue_dev
{
 struct cdev cdev;
 struct device* cls_dev;
 wait_queue_head_t wq;

 char* buf;
 int loops;
 unsigned long pre_jiffies;
 struct work_struct work;
 struct workqueue_struct* work_queue;
};

static struct class* class = NULL;
static struct work_queue_dev* work_queue_devp;

static int work_queue_open_func(struct inode* inode, struct file* filp)
{
 struct work_queue_dev* dev = container_of(inode->i_cdev, struct work_queue_dev, cdev);
 filp->private_data = dev;
 return 0;
}

static int work_queue_release_func(struct inode* inode, struct file* filp)
{
 return 0;
}

static void work_queue_handeler(struct work_struct* work)
{
 struct work_queue_dev* dev = container_of(work, struct work_queue_dev, work);
 dev->buf += sprintf(dev->buf,
 "%9li %3li %i %6i %i %s\n",
 jiffies, jiffies - dev->pre_jiffies,
 in_interrupt() ? 1 : 0, current->pid,
 smp_processor_id(), current->comm);

 dev->loops--;
 if (dev->loops > 0)
 {
 queue_work(dev->work_queue, work);
 dev->pre_jiffies = jiffies;
 }
 else
 wake_up_interruptible(&dev->wq);
}
static ssize_t work_queue_read_func(struct file* filp, char __user* buf, size_t count, loff_t* f_pos)
{
 int ret = 0;
 char* tmp_buf;
 int cnt;
 struct work_queue_dev* dev = filp->private_data;
 if (*f_pos > 0)
 return 0;

 dev->work_queue = alloc_workqueue("work_queue", WQ_UNBOUND, 0);

 tmp_buf = kzalloc(300, GFP_KERNEL);
 if (!tmp_buf)
 return -ENOMEM;
 dev->loops = 4;
 dev->buf = tmp_buf;
 dev->buf += sprintf(dev->buf, " time delta inirq pid cpu command\n");
 dev->buf += sprintf(dev->buf,
 "%9li %3li %i %6i %i %s\n",
 jiffies, 0L, in_interrupt() ? 1 : 0,
 current->pid, smp_processor_id(), current->comm);
 dev->pre_jiffies = jiffies;

 INIT_WORK(&dev->work, work_queue_handeler);
 queue_work(dev->work_queue, &dev->work);

 wait_event_interruptible(dev->wq, !dev->loops);
 destroy_workqueue(dev->work_queue);

 cnt = dev->buf - tmp_buf;
 if (copy_to_user(buf, tmp_buf, cnt))
 {
 ret = -EFAULT;
 goto out;
 }

 *f_pos += cnt;
 ret = cnt;
out:
 kfree(tmp_buf);
 return ret;
}

struct file_operations work_queue_fops = {
 .owner = THIS_MODULE,
 .read = work_queue_read_func,
 .open = work_queue_open_func,
 .release = work_queue_release_func,
};

static int __init work_queue_init_module(void)
{
 int ret = 0, i;
 dev_t devno = MKDEV(work_queue_major, work_queue_minor);

 if (work_queue_major)
 ret = register_chrdev_region(devno, work_queue_nr_devs, "work_queue");
 else
 {
 ret = alloc_chrdev_region(&devno, work_queue_minor, work_queue_nr_devs, "work_queue");
 work_queue_major = MAJOR(devno);
 }

 if (ret < 0)
 {
 printk(KERN_ALERT "register_chrdev_region failed with %d\n", ret);
 goto out_1;
 }

 work_queue_devp = kzalloc(sizeof(struct work_queue_dev) * work_queue_nr_devs, GFP_KERNEL);
 if (!work_queue_devp)
 {
 ret = -ENOMEM;
 printk(KERN_ALERT "Out of memory\n");
 goto out_2;
 }

 class = class_create(THIS_MODULE, "work_queue_class");
 if (IS_ERR(class))
 {
 ret = PTR_ERR(class);
 printk(KERN_ALERT "class_create failed with %d\n", ret);
 goto out_3;
 }

 for (i = 0; i < work_queue_nr_devs; i++)
 {
 cdev_init(&work_queue_devp[i].cdev, &work_queue_fops);
 work_queue_devp[i].cdev.owner = THIS_MODULE;
 ret = cdev_add(&work_queue_devp[i].cdev, MKDEV(work_queue_major, work_queue_minor + i), 1);
 if (ret < 0)
 printk(KERN_ALERT "cdev_add failed with %d\n", ret);
 else
 {
 work_queue_devp[i].cls_dev = device_create(class, NULL, MKDEV(work_queue_major, work_queue_minor + i), NULL, "work_queue%d", i);
 if (IS_ERR(work_queue_devp[i].cls_dev))
 printk(KERN_ALERT "device_create failed with %d\n", ret);
 }
 init_waitqueue_head(&work_queue_devp[i].wq);
 }
 return 0;
out_3:
 kfree(work_queue_devp);
out_2:
 unregister_chrdev_region(devno, work_queue_nr_devs);
out_1:
 return ret;
}

static void __exit work_queue_exit_module(void)
{
 int i;
 for (i = 0; i < work_queue_nr_devs; i++)
 {
 device_destroy(class, MKDEV(work_queue_major, work_queue_minor + i));
 cdev_del(&work_queue_devp[i].cdev);
 }
 class_destroy(class);
 kfree(work_queue_devp);
 unregister_chrdev_region(MKDEV(work_queue_major, work_queue_minor), work_queue_nr_devs);
}

module_param(work_queue_major, int, S_IRUGO);
module_param(work_queue_minor, int, S_IRUGO);
module_param(work_queue_nr_devs, int, S_IRUGO);

module_init(work_queue_init_module);
module_exit(work_queue_exit_module);

MODULE_AUTHOR("lidonghang-02");
MODULE_LICENSE("GPL");