> 文章列表 > itop-3568开发板驱动学习笔记(19)内核工作队列

itop-3568开发板驱动学习笔记(19)内核工作队列

itop-3568开发板驱动学习笔记(19)内核工作队列

《【北京迅为】itop-3568开发板驱动开发指南.pdf》 学习笔记

文章目录

  • 工作队列简介
  • 共享工作队列
    • 工作结构体
    • 初始化 work_struct
    • 调度工作队列函数
    • 共享工作队列实验
  • 自定义工作队列
    • 创建工作队列函数
    • 调度和取消调度工作队列
    • 刷新工作队列函数
    • 删除工作队列函数
  • 内核延时工作队列
    • 延时工作结构体
    • 初始化延时工作函数
    • 调度延时工作函数
    • 取消调度延时工作函数
  • 工作队列传参
  • 并发管理工作队列
    • 创建一个并发管理工作队列

工作队列简介

工作队列是实现中断下半部分的机制之一,是一种将工作推后执行的形式,工作队列和同为中断下半部分的 tasklet 的区别在于 tasklet 不能休眠(且以原子模式执行),而工作队列可以休眠(不必原子执行)。
内核工作队列分为共享工作队列和自定义工作队列两种。

共享工作队列

共享工作队列是内核提供的默认工作队列,共享意味着不能长时间独占该队列,既不能长时间休眠,且我们的任务可能需要等待更长的时间才能被执行。

工作结构体

#include <linux/workqueue.h>struct work_struct
{atomic_long_t data;struct list_head entry;work_func_t func;
};

该结构体最重要的成员为 func 函数指针,该函数指针原型为:

typedef void (*work_func_t)(struct work_struct *work);

初始化 work_struct

INIT_WORK()DECLARE_WORK() 用来初始化一个 work_struct 结构体,前者为动态初始化,后者为静态初始化,函数定义为:

INIT_WORK(_work, _func);
DECLARE_WORK(n, f);

它们的第一个参数为 work_struct 结构体指针,第二个参数为工作函数指针。

调度工作队列函数

函数原型:

static inline bool schedule_work(struct work_struct *work);

如果想在中断下文执行工作函数,则需要在中断处理函数中调用该函数。

共享工作队列实验

实验代码

#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/workqueue.h>
#include <linux/delay.h>int irq;
struct work_struct my_work; //工作函数
void my_work_func(struct work_struct *work)
{printk("my work func.\\n");msleep(1000);printk("msleep finish.\\n");
}//中断服务函数
irqreturn_t my_interrupt(int irq, void *args)
{printk("my interrupt handler.\\n");	// 调度工作队列schedule_work(&my_work);return IRQ_RETVAL(IRQ_HANDLED);
}static int interrupt_irq_init(void)
{int ret = 0;// 获取中断号irq = gpio_to_irq(101);printk("irq is %d\\n", irq);// 申请中断ret = request_irq(irq, my_interrupt, IRQF_TRIGGER_RISING, "inttrupt_test", NULL);if(ret < 0){printk("request irq error.\\n");return 0;}// 初始化工作队列INIT_WORK(&my_work, my_work_func);return 0;	
}static void interrupt_irq_exit(void)
{printk("interrupt irq exit.\\n");// 注销中断free_irq(irq, NULL);
}module_init(interrupt_irq_init);
module_exit(interrupt_irq_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("xiaohui");

实验结果

实验效果和 tasklet 类似,只是工作函数可以支持延时操作。

在这里插入图片描述

自定义工作队列

共享工作队列是内核提供的一个公共的工作队列,我们也可以自己创建工作队列。

创建工作队列函数

create_workqueue() 和 create_singlethread_workqueue() 可以用来创建自定义工作队列,它们的定义如下(都是宏函数):

create_workqueue(name);
create_singlethread_workqueue(name);

create_workqueue() 可以给每个 CPU 都创建一个工作队列,name 为工作队列名,创建成功返回 workqueue_struct 结构体指针,失败返回 NULL。

create_singlethread_workqueue() 只给一个 CPU 创建工作队列。

调度和取消调度工作队列

queue_work_on() 用来调度自定义工作队列,cancel_work_sync() 用来取消已经调度的工作,并且会等待其完成再返回。

bool queue_work_on(int cpu, struct workqueue_struct *wq, struct work_struct *work);
bool cancel_work_sync(struct work_struct *work);

刷新工作队列函数

告知内核尽快处理工作队列的工作。

void flush_workqueue(struct workqueue_struct *wq);

删除工作队列函数

删除一个自定义的工作队列。

void destroy_workqueue(struct workqueue_struct *wq);

实验代码

#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/workqueue.h>
#include <linux/delay.h>int irq;
struct work_struct my_work;
struct workqueue_struct *my_workqueue; //工作函数
void my_work_func(struct work_struct *work)
{printk("my work func.\\n");msleep(1000);printk("msleep finish.\\n");
}//中断服务函数
irqreturn_t my_interrupt(int irq, void *args)
{printk("my interrupt handler.\\n");	// 调度自定义工作队列queue_work(my_workqueue, &my_work);return IRQ_RETVAL(IRQ_HANDLED);
}static int interrupt_irq_init(void)
{int ret = 0;// 获取中断号irq = gpio_to_irq(101);printk("irq is %d\\n", irq);// 申请中断ret = request_irq(irq, my_interrupt, IRQF_TRIGGER_RISING, "inttrupt_test", NULL);if(ret < 0){printk("request irq error.\\n");return 0;}// 创建自定义工作队列my_workqueue = create_workqueue("my_workqueue");// 初始化工作队列INIT_WORK(&my_work, my_work_func);return 0;	
}static void interrupt_irq_exit(void)
{printk("interrupt irq exit.\\n");// 注销中断free_irq(irq, NULL);// 取消自定义工作队列调度cancel_work_sync(&my_work);// 刷新工作队列flush_workqueue(my_workqueue);// 删除工作队列destroy_workqueue(my_workqueue);
}module_init(interrupt_irq_init);
module_exit(interrupt_irq_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("xiaohui");

实验结果

实验效果和共享工作队列一致。

在这里插入图片描述

内核延时工作队列

延时工作结构体

struct delayed_work
{struct work_struct work;struct timer_list timer;
}

work 成员为之前提到的工作结构体,延时工作结构体只比工作结构体多了内核定时器。

初始化延时工作函数

初始化分为静态初始化和动态初始化,DECLARE_DELAYED_WORK(n, f) 用来静态初始化延时工作结构体,INIT_DELAYED_WORK(_work, _func) 用来动态初始化延时工作。

调度延时工作函数

schedule_delayed_work() 的作用是调度共享工作队列上的延时工作,queue_delayed_work() 则用来调度自定义工作队列上的延时工作。

static inline bool schedule_delayed_work(struct delayed_work *dwork, unsinged long delay);
static inline bool queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *dwork, unsigned long delay);

dwork 为延时工作结构体变量,delay 为要延时的时间,单位为节拍,queue_delayed_work() 的第一个参数为自定义工作队列结构体指针。

取消调度延时工作函数

用来取消已经调度的延时工作。

bool cancel_delayed_work_sync(struct delayed_work *dwork);

实验代码

#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/workqueue.h>
#include <linux/delay.h>int irq;
struct delayed_work my_work; 
struct workqueue_struct *my_workqueue;//工作函数
void my_work_func(struct work_struct *work)
{printk("my work func.\\n");msleep(1000);printk("msleep finish.\\n");
} //中断服务函数
irqreturn_t my_interrupt(int irq, void *args)
{printk("my interrupt handler.\\n");	// 调度延时自定义工作队列,延时 3 秒queue_delayed_work(my_workqueue, &my_work, 3 * HZ);return IRQ_RETVAL(IRQ_HANDLED);
}static int interrupt_irq_init(void)
{int ret = 0;// 获取中断号irq = gpio_to_irq(101);printk("irq is %d\\n", irq);// 申请中断ret = request_irq(irq, my_interrupt, IRQF_TRIGGER_RISING, "inttrupt_test", NULL);if(ret < 0){printk("request irq error.\\n");return 0;}// 创建自定义工作队列my_workqueue = create_workqueue("my_workqueue");// 初始化工作队列INIT_DELAYED_WORK(&my_work, my_work_func);return 0;	
}static void interrupt_irq_exit(void)
{printk("interrupt irq exit.\\n");// 注销中断free_irq(irq, NULL);// 取消延时自定义工作队列cancel_delayed_work_sync(&my_work);// 刷新工作队列flush_workqueue(my_workqueue);// 删除工作队列destroy_workqueue(my_workqueue);
}module_init(interrupt_irq_init);
module_exit(interrupt_irq_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("xiaohui");

实验结果

相较前面的实验,延时工作工作函数会在中断触发后,延后一段时间再执行。

在这里插入图片描述

工作队列传参

工作处理函数的参数为 work_struct,即工作函数运行时可以使用对应的工作结构体变量,如果我们定义一个结构体,并将 work_struct 作为它的成员,那么就能在工作函数中访问我们自定义的结构体变量了。

具体实现需要用到 container_of() 宏函数(函数功能:从结构体某个成员的首地址获取整个结构体的首地址)

实验代码

#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/workqueue.h>
#include <linux/delay.h>int irq;//自定义数据结构体
struct work_data
{struct work_struct my_work;int a;int b;
};struct work_data my_work_data;
struct workqueue_struct *my_workqueue;//工作函数
void my_work_func(struct work_struct *work)
{	struct work_data *pdata;pdata = container_of(work, struct work_data, my_work);//printk("my work func.\\n");printk("data a is %d.\\n", pdata->a);printk("data b is %d.\\n", pdata->b);
} //中断服务函数
irqreturn_t my_interrupt(int irq, void *args)
{printk("my interrupt handler.\\n");	// 调度自定义工作队列queue_work(my_workqueue, &my_work_data.my_work);return IRQ_RETVAL(IRQ_HANDLED);
}static int interrupt_irq_init(void)
{int ret = 0;// 获取中断号irq = gpio_to_irq(101);printk("irq is %d\\n", irq);// 申请中断ret = request_irq(irq, my_interrupt, IRQF_TRIGGER_RISING, "inttrupt_test", NULL);if(ret < 0){printk("request irq error.\\n");return 0;}// 创建自定义工作队列my_workqueue = create_workqueue("my_workqueue");// 初始化工作队列INIT_WORK(&my_work_data.my_work, my_work_func);// 自定义数据结构体成员初始化my_work_data.a = 5;my_work_data.b = 6;return 0;	
}static void interrupt_irq_exit(void)
{printk("interrupt irq exit.\\n");// 注销中断free_irq(irq, NULL);// 取消自定义工作队列cancel_work_sync(&my_work_data.my_work);// 刷新工作队列flush_workqueue(my_workqueue);// 删除工作队列destroy_workqueue(my_workqueue);
}module_init(interrupt_irq_init);
module_exit(interrupt_irq_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("xiaohui");

实验结果

成功实现工作函数的传参:

在这里插入图片描述

并发管理工作队列

并发管理工作队列(CMWQ)设计的目的为:试图保持最小的资源消耗,但要保证足够的并发性。CMWQ使用最小的资源来发挥它的全部能力。

创建一个并发管理工作队列

alloc_workqueue(fmt, flags, max_active);

fmt 为要创建工作队列的名称,max_active 为线程池里最大的线程数量(默认填 0),flags 可取值包括:

flags 简介
WQ_UNBOUND ungound 队列不绑定指定 CPU,不会参与并发管理(不会出现并发冲突问题)
WQ_FREEZABLE 在该队列上工作的工作不会被执行
WQ_MEM_RECLAIM 所有有可能运行在内存回收流程中的工作队列都需要设置该标记
WQ_HIGHPRI highpri 队列上的工作会被指定的 CPU 上的线程池来处理
WQ_CPU_INTENSIVE CPU 密集型工作队列

本笔记只用到了 WQ_UNBOUND 标志,之前介绍的工作队列,每个工作队列只运行在特定的 CPU 上,如果多个工作队列需要并发运行时,创建 unbound 队列可以使内核线程在多个处理器之间迁移。

实验代码

#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/workqueue.h>
#include <linux/delay.h>int irq;
struct work_struct my_work;
struct workqueue_struct *my_workqueue; //工作函数
void my_work_func(struct work_struct *work)
{printk("my work func.\\n");msleep(1000);printk("msleep finish.\\n");
}//中断服务函数
irqreturn_t my_interrupt(int irq, void *args)
{printk("my interrupt handler.\\n");	// 调度自定义工作队列queue_work(my_workqueue, &my_work);return IRQ_RETVAL(IRQ_HANDLED);
}static int interrupt_irq_init(void)
{int ret = 0;// 获取中断号irq = gpio_to_irq(101);printk("irq is %d\\n", irq);// 申请中断ret = request_irq(irq, my_interrupt, IRQF_TRIGGER_RISING, "inttrupt_test", NULL);if(ret < 0){printk("request irq error.\\n");return 0;}// 创建并发工作队列my_workqueue = alloc_workqueue("my_workqueue", WQ_UNBOUND, 0);// 初始化工作队列INIT_WORK(&my_work, my_work_func);return 0;	
}static void interrupt_irq_exit(void)
{printk("interrupt irq exit.\\n");// 注销中断free_irq(irq, NULL);// 取消自定义任务队列调度cancel_work_sync(&my_work);// 刷新工作队列flush_workqueue(my_workqueue);// 删除工作队列destroy_workqueue(my_workqueue);
}module_init(interrupt_irq_init);
module_exit(interrupt_irq_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("xiaohui");

实验结果

并发管理工作队列测试效果和普通工作队列并没有什么区别(因为测试例程过于简单)

在这里插入图片描述