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

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

工作队列简介

工作队列是实现中断下半部分的机制之一,是一种将工作推后执行的形式,工作队列和同为中断下半部分的 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_UNBOUNDungound 队列不绑定指定 CPU,不会参与并发管理(不会出现并发冲突问题)
WQ_FREEZABLE在该队列上工作的工作不会被执行
WQ_MEM_RECLAIM所有有可能运行在内存回收流程中的工作队列都需要设置该标记
WQ_HIGHPRIhighpri 队列上的工作会被指定的 CPU 上的线程池来处理
WQ_CPU_INTENSIVECPU 密集型工作队列

本笔记只用到了 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");

实验结果

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

在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小辉_Super

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值