《【北京迅为】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");
实验结果
并发管理工作队列测试效果和普通工作队列并没有什么区别(因为测试例程过于简单)