手把手教你写Linux设备驱动---中断(三)--workqueue实现(基于友善之臂4412开发板)

简介: 上节,我们讲到如何来实现tasklet小任务机制http://blog.csdn.net/morixinguan/article/details/69666935这节,我们来实现一下中断下半部的工作队列:在写这个demo之前,我们要了解一下工作队列的相关数据结构还有API。

上节,我们讲到如何来实现tasklet小任务机制

http://blog.csdn.net/morixinguan/article/details/69666935

这节,我们来实现一下中断下半部的工作队列:

在写这个demo之前,我们要了解一下工作队列的相关数据结构还有API。

需要包含的头文件:

#include <linux/workqueue.h>

基本的数据结构:

//工作队列结构
struct work_struct {
	atomic_long_t data;
	//链表处理
	struct list_head entry;
	//工作处理函数
	work_func_t func;
#ifdef CONFIG_LOCKDEP
	struct lockdep_map lockdep_map;
#endif
};
当然,如果需要等待一定时间后再执行工作队列,可以用下面这个结构体申请一个内核定时器:

//指定时间让工作队列执行
struct delayed_work {
	//初始化
	struct work_struct work;
	//内核定时器
	struct timer_list timer;
};
一般,不要轻易的去使用工作队列,因为每当创建一条工作队列,内核就会为这条工作队列创建一条内核线程。

工作队列位于进程上下文,与软中断,tasklet有所区别,工作队列里允许延时,睡眠操作,而软中断,tasklet位于中断上下文,不允许睡眠,延时操作。

参考我转发的这位博主写的工作队列和tasklet的区别:

http://blog.csdn.net/morixinguan/article/details/69666642

工作队列(work queue)是另外一种将工作推后执行的形式,它和前面讨论的tasklet有所不同。工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势。最重要的就是工作队列允许被重新调度甚至是睡眠
      那么,什么情况下使用工作队列,什么情况下使用tasklet。如果推后执行的任务需要睡眠,那么就选择工作队列;如果推后执行的任务不需要睡眠,那么就选择tasklet。另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。如果不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet。

接下来我们看看需要使用到哪些API:

创建一个队列就会有一个内核线程,一般不要轻易创建队列
位于进程上下文--->可以睡眠
定义:
	struct work_struct work;

初始化:
	INIT_WORK(struct work_struct *work, void (*func)(struct work_struct *work));

定义并初始化:
	DECLARE_WORK(name, void (*func)(struct work_struct *work));

===========================================================

调度:
	int schedule_work(struct work_struct *work);
	返回1成功, 0已经添加在队列上

延迟调度:
	int schedule_delayed_work(struct work_struct *work, unsigned long delay);

===========================================================

创建新队列和新工作者线程:
	struct workqueue_struct *create_workqueue(const char *name);

调度指定队列:
	int queue_work(struct workqueue_struct *wq, struct work_struct *work);

延迟调度指定队列:
	int queue_delayed_work(struct workqueue_struct *wq, 
			struct work_struct *work, unsigned long delay);
销毁队列:
	void destroy_workqueue(struct workqueue_struct *wq);
接下来,我们来实现这个demo:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/err.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/timer.h>  /*timer*/
#include <asm/uaccess.h>  /*jiffies*/
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
struct tasklet_struct task_t ; 
struct workqueue_struct *mywork ;
//定义一个工作队列结构体
struct work_struct work;
static void task_fuc(unsigned long data)
{
	if(in_interrupt()){
             printk("%s in interrupt handle!\n",__FUNCTION__);
        }
}
//工作队列处理函数
static void mywork_fuc(struct work_struct *work)
{
	if(in_interrupt()){
             printk("%s in interrupt handle!\n",__FUNCTION__);
        }
	msleep(2);
	printk("%s in process handle!\n",__FUNCTION__);
}

static irqreturn_t irq_fuction(int irq, void *dev_id)
{	
	tasklet_schedule(&task_t);
	//调度工作
	schedule_work(&work);
	if(in_interrupt()){
	     printk("%s in interrupt handle!\n",__FUNCTION__);
	}
	printk("key_irq:%d\n",irq);
	return IRQ_HANDLED ;
}
	
static int __init tiny4412_Key_irq_test_init(void) 
{
	int err = 0 ;
	int irq_num1 ;
	int data_t = 100 ;
	//创建新队列和新工作者线程
	mywork = create_workqueue("my work");
	//初始化
	INIT_WORK(&work,mywork_fuc);
	//调度指定队列
	queue_work(mywork,&work);
	tasklet_init(&task_t,task_fuc,data_t);
	printk("irq_key init\n");
	irq_num1 = gpio_to_irq(EXYNOS4_GPX3(2));
	err = request_irq(irq_num1,irq_fuction,IRQF_TRIGGER_FALLING,"tiny4412_key1",(void *)"key1");
	if(err != 0){
		free_irq(irq_num1,(void *)"key1");
		return -1 ;
	}
	return 0 ;
}

static void __exit tiny4412_Key_irq_test_exit(void) 
{
	int irq_num1 ;
	printk("irq_key exit\n");
	irq_num1 = gpio_to_irq(EXYNOS4_GPX3(2));
	//销毁一条工作队列
	destroy_workqueue(mywork);
	free_irq(irq_num1,(void *)"key1");
}

module_init(tiny4412_Key_irq_test_init);
module_exit(tiny4412_Key_irq_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("YYX");
MODULE_DESCRIPTION("Exynos4 KEY Driver");
将程序编译完,将zImage下到板子上:


我们可以看到,当我们按下按键的时候,进入外部中断服务函数,此时task_fuc先被调用,然后调用到mywork_fuc,并打印了mywork_fuc里面的信息,从这里我们用程序验证了,工作队列是位于进程上下文,而不是中断上下文,和tasklet是有所区别的。





目录
相关文章
|
1月前
|
Shell Linux C语言
【Shell 命令集合 设备管理 】Linux 创建设备文件 MAKEDEV命令 使用指南
【Shell 命令集合 设备管理 】Linux 创建设备文件 MAKEDEV命令 使用指南
33 0
|
1月前
|
安全 Shell Linux
【Shell 命令集合 网络通讯 】Linux 打开终端设备 mingetty命令 使用指南
【Shell 命令集合 网络通讯 】Linux 打开终端设备 mingetty命令 使用指南
39 0
|
8天前
|
Linux Go
Linux命令Top 100驱动人生! 面试必备
探索Linux命令不再迷茫!本文分10部分详解20个基础命令,带你由浅入深掌握文件、目录管理和文本处理。 [1]: <https://cloud.tencent.com/developer/article/2396114> [2]: <https://pan.quark.cn/s/865a0bbd5720> [3]: <https://yv4kfv1n3j.feishu.cn/docx/MRyxdaqz8ow5RjxyL1ucrvOYnnH>
63 0
|
15天前
|
网络协议 Linux SDN
虚拟网络设备与Linux网络协议栈
在现代计算环境中,虚拟网络设备在实现灵活的网络配置和隔离方面发挥了至关重要的作用🔧,特别是在容器化和虚拟化技术广泛应用的今天🌐。而Linux网络协议栈则是操作系统处理网络通信的核心💻,它支持广泛的协议和网络服务🌍,确保数据正确地在网络中传输。本文将深入分析虚拟网络设备与Linux网络协议栈的关联,揭示它们如何共同工作以支持复杂的网络需求。
|
16天前
|
存储 缓存 固态存储
Linux设备全览:从字符到块,揭秘每种设备的秘密
在Linux的世界里,设备是构成系统的基础,它们使得计算机能够与外界互动。Linux设备可以大致分为几种类型,每种类型都有其独特的特性和用途。🌌让我们一起探索这些设备类型及其特性。
|
21天前
|
Linux
Linux驱动运行灯 Heartbeat
Linux驱动运行灯 Heartbeat
10 0
|
1月前
|
Shell Linux C语言
【Shell 命令集合 系统设置 】Linux 配置鼠标设备的相关设置 mouseconfig命令 使用指南
【Shell 命令集合 系统设置 】Linux 配置鼠标设备的相关设置 mouseconfig命令 使用指南
36 0
|
1月前
|
存储 编解码 Shell
【Shell 命令集合 系统设置 】⭐Linux 设置和调整帧缓冲设备 fbset命令 使用指南
【Shell 命令集合 系统设置 】⭐Linux 设置和调整帧缓冲设备 fbset命令 使用指南
30 0
|
1月前
|
存储 安全 Linux
【Shell 命令集合 设备管理 】Linux 更改根文件系统的设备号 rdev命令 使用指南
【Shell 命令集合 设备管理 】Linux 更改根文件系统的设备号 rdev命令 使用指南
31 0
|
11月前
|
存储 负载均衡 算法
Linux内核17-硬件如何处理中断和异常
Linux内核17-硬件如何处理中断和异常