曾无好事来相访,赖尔高文一起予。这篇文章主要讲述Linux驱动开发-内核共享工作队列相关的知识,希望能为你提供帮助。
1. 内核工作队列工作队列常见的使用形式是配合中断使用,在中断的服务函数里无法调用会导致休眠的相关函数代码,有了工作队列机制以后,可以将需要执行的逻辑代码放在工作队列里执行,只需要在中断服务函数里触发即可,工作队列是允许被重新调度、睡眠。
在工作队列里,我们把推后执行的任务叫做工作(work),描述它的数据结构为work_struct,这些工作以队列结构组织成工作队列(workqueue),其数据结构为workqueue_struct,而工作线程就是负责执行工作队列中的工作。系统有默认的工作者线程,自己也可以创建自己的工作者线程。
2. 相关函数、结构介绍
2.1 工作结构
定义文件:
Workqueue.h (linux-3.5\\include\\Linux)原型:
struct work_struct
atomic_long_t data;
struct list_head entry;
work_func_t func;
/* 工作函数指针 */
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
;
在工作结构体里,只需要关心一个成员函数:work_func_t func;
这个成员函数是一个函数指针,指向工作函数的指针;内核使用这个结构来描述一个工作,一个工作简单理解就是对应于一个函数,可以通过内核调度函数来调用work_struct中func指针所指向的函数。
2.2 工作函数介绍
定义文件Workqueue.h (linux-3.5\\include\\linux)
函数原型typedef void (*work_func_t)(struct work_struct *work);
功能这是指向工作函数地址的函数指针,编写一个工作的函数。
参数struct work_struct *work,这个参数,指向struct work_struct结构变量本身。
示例:
struct work_struct work;
INIT_WORK(&
work, work_func);
初始化一个work结构,work_func工作函数的参数就是指向work结构。
2.3 初始化宏
1)初始化一个work结构:
INIT_WORK(_work, _func)
_work: struct work_struct work结构指针。
_func:用来填充work_struct work结构的fun成员,就是工作函数指针。2)共享工作队列调度宏:
schedule_work(_work)
它也是一个宏,作用是调度一个工作_work。
_work:要调度工作的结构指针;
示例:
schedule_work(&
work)
2.4 使用共享工作队列的步骤
1)定义一个工作结构变量
struct work_struct work;
2)初始化工作结构(重点func成员)。
先编写一个工作函数:
void work_func(struct work_struct * dat)printk(“%p:”,dat);
……初始化work:
INIT_WORK(&
work, work_func);
3)在适当的地方调度工作
如果工作用于中断底部代码,则在中断顶部调度。
schedule_work(&
work);
不是马上执行,而是等待CPU空闲才执行work_func。
3. 案例代码 3.1 共享工作队列-按键驱动
下面这份代码是在一个按键驱动代码,在按键中断服务函数里调度共享队列,最终在工作函数里完成按键值的检测打印。工作队列采用的是共享工作队列。
#include <
linux/kernel.h>
#include <
linux/module.h>
#include <
linux/interrupt.h>
#include <
linux/irq.h>
#include <
linux/gpio.h>
#include <
mach/gpio.h>
#include <
plat/gpio-cfg.h>
#include <
linux/delay.h>
#include <
linux/workqueue.h>
static struct work_struct work;
static struct m_key_info *key_info_p=NULL;
/*存放按键的信息*/
struct m_key_infoint gpio;
char name[50];
int val;
int irq;
;
struct m_key_info key_info[]=EXYNOS4_GPX3(2),"key_irq_1",0x01,
EXYNOS4_GPX3(3),"key_irq_2",0x02,
EXYNOS4_GPX3(4),"key_irq_3",0x03,
EXYNOS4_GPX3(5),"key_irq_4",0x04,
;
/*
工作函数
*/
static void key_work_func(struct work_struct *work)msleep(50);
//udelay(n);
//mdelay(n);
//msleep(unsigned int msecs);
if(gpio_get_value(key_info_p->
gpio)==0) //判断按键是否按下printk("按键值:%#x\\n",key_info_p->
val);
elseprintk("按键值:%#x\\n",key_info_p->
val|0x80);
/*
中断服务函数
*/
static irqreturn_t key_irq_handler(int irq, void *dev)key_info_p=(struct m_key_info*)dev;
/*调度工作----工作结构体添加到系统共享工作队列里*/
schedule_work(&
work);
return IRQ_HANDLED;
static int __init tiny4412_interrupt_drv_init(void)/*初始化工作*/
INIT_WORK(&
work,key_work_func);
int i;
for(i=0;
i<
sizeof(key_info)/sizeof(key_info[0]);
i++)/*1. 获取中断号*/
key_info[i].irq=gpio_to_irq(key_info[i].gpio);
/*2. 注册中断*/
if(request_irq(key_info[i].irq,key_irq_handler,IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,key_info[i].name,&
key_info[i]))printk("中断号%d注册失败:%s\\n",key_info[i].irq,key_info[i].name);
printk("按键中断 驱动注册-安装成功.\\n");
return 0;
static void __exit tiny4412_interrupt_drv_exit(void)/*注销中断*/
int i=0;
for(i=0;
i<
sizeof(key_info)/sizeof(key_info[0]);
i++)free_irq(key_info[i].irq,&
key_info[i]);
printk("按键中断 驱动注销成功.\\n");
/*驱动入口*/
module_init(tiny4412_interrupt_drv_init);
/*驱动出口*/
module_exit(tiny4412_interrupt_drv_exit);
/*许可证*/
MODULE_LICENSE("GPL");
3.2 自定义工作队列-按键驱动
【Linux驱动开发-内核共享工作队列】工作队列除了可以使用内核共享队列以外,也可以自己创建队列,下面这份代码就演示如何自己创建队列,并完成初始化、调用。代码原型还是一份按键驱动代码,与上面代码相比,加了字符设备节点注册,替换系统共享工作队列为自定义的工作队列。
#include <
linux/init.h>
#include <
linux/module.h>
#include <
linux/miscdevice.h>
/*杂项设备相关结构体*/
#include <
linux/fs.h>
/*文件操作集合头文件*/
#include <
linux/uaccess.h>
/*使用copy_to_user和copy_from_user*/
#include <
linux/io.h>
/*使用IO端口映射*/
#include <
linux/slab.h>
#include <
linux/device.h>
/*设备*/
#include <
linux/cdev.h>
/*标准字符设备--分配设备号*/
#include <
linux/ioctl.h>
/*ioctl操作*/
#include <
linux/interrupt.h>
/*注册中断相关*/
#include <
linux/irq.h>
/*中断边沿类型定义*/
#include <
linux/gpio.h>
/*中断IO口定义*/
#include <
linux/timer.h>
/*内核定时器相关*/
#include <
linux/wait.h>
/*等待队列相关*/
#include <
linux/sched.h>
/*等待队列相关*/
#include <
linux/poll.h>
/*POLL机制相关*/
#include <
linux/spinlock_types.h>
/*自旋锁相关*/
#include <
linux/spinlock.h>
/*自旋锁相关*/
#include <
linux/types.h>
/*原子操作相关*/
#include <
linux/atomic.h>
/*原子操作相关*/
#include <
linux/delay.h>
/*延时函数*/
#include <
linux/mutex.h>
#include <
linux/signal.h>
/*信号相关头文件*/
#include <
linux/workqueue.h>
/*工作队列相关*//*----------------------------------------------------
创建自己的工作队列creator_workqueue测试
-----------------------------------------------------*//*定义ioctl的命令*/
#define Cmd_LEDON_IO(L,1)//无方向--开灯
#define Cmd_LEDOFF_IO(L,0)//无方向---关灯/*定义设备号注册相关*/
static dev_t keydev;
//存放设备号
static struct cdev *keyCdev;
//定义cdev结构体指针
static struct class *cls;
//定义类结构体指针/*定义按键中断相关*/
static unsigned int irq_buff[4];
/*存放中断编号*/
static int key_value=https://www.songbingjia.com/android/0;
/*存放按键按下的键值*//*定时器相关*/
struct timer_list my_timer;
/*全局标志*/
static int poll_flag=0;
struct mutex ;
/*互斥锁*//*等待队列相关*/
static DECLARE_WAIT_QUEUE_HEAD(wait);
/*初始化等待队列头*/
static int condition=0;
/*唤醒队列的条件-为假休眠-为真唤醒*//*异步通知助手相关*/
static struct fasync_struct *myfasync;
/*信号量*/
static DEFINE_SEMAPHORE(name_sem);
/*内核工作队列相关结构体*/
static struct work_struct my_work;
/*延时工作队列相关结构体*/
static struct delayed_work my_delay_work;
/*创建自己的工作队列相关*/
struct workqueue_struct *my_work_queue;
struct Buttons_datachar key_name[10];
/*按键的名字*/
char key;
/*按键值*/
intGPIO;
/*GPIO口编号*/
;
/*工作队列的处理函数*/
static void my_work_func(struct work_struct *work)static int count=0;
printk("\\n\\n用户创建的系统共享工作队列调度成功%d 次\\n\\n",count++);
/*结构体整体赋值*/
static struct Buttons_data Key_interrupt[4]="key1",0x01,EXYNOS4_GPX3(2),
"key2",0x02,EXYNOS4_GPX3(3),
"key3",0x03,EXYNOS4_GPX3(4),
"key4",0x04,EXYNOS4_GPX3(5),
;
/*按键中断服务函数*/
static irqreturn_t irq_handler_function(int irq,void * dat)struct Buttons_data *p =(struct Buttons_data *)dat;
/*强制转换*/if(!gpio_get_value(p->
GPIO))key_value=https://www.songbingjia.com/android/p->
key;
/*获取按下按键值*/elsekey_value=p->
key|0x80;
/*获取松开按键值*/mod_timer(&
my_timer,jiffies+1);
/*修改超时时间*/
return IRQ_HANDLED;
/*定时器中断服务函数*/
static void timer_function(unsigned long data)printk("按键值读取成功!!0x%x--->
!\\n",key_value);
/*添加延时工作到系统工作队列中等待执行*/
// schedule_delayed_work(&
my_delay_work,HZ*5);
//queue_work(my_work_queue,&
my_work);
/*调度共享工作队列*/
queue_delayed_work_on(-1,my_work_queue,&
my_delay_work,HZ*5);
static int key_open(struct inode *my_inode, struct file *my_file)unsigned char i;
for(i=0;
i<
4;
i++)//获取中断编号
irq_buff[i]=gpio_to_irq(EXYNOS4_GPX3(2+i));
request_irq(irq_buff[i],irq_handler_function,IRQ_TYPE_EDGE_BOTH,Key_interrupt[i].key_name,&
Key_interrupt[i]);
/*定时器相关*/
my_timer.expires=0;
/*1秒钟*/
my_timer.function=timer_function;
/*定时器中断处理函数*/
my_timer.data=https://www.songbingjia.com/android/888;
/*传递给定时器中断服务函数的参数-用于共享定时器*/
init_timer(&
my_timer);
/*初始化定时器*/
add_timer(&
my_timer);
/*启动定时器*/printk("open ok !\\n");
return 0;
static ssize_t key_read(struct file *my_file, char __user *buf, size_t my_conut, loff_t * my_loff)int error=0;
error=copy_to_user(buf,&
key_value,my_conut);
/*向应用层拷贝按键值*/
key_value=https://www.songbingjia.com/android/0;
if(!error)return 0;
/*没有读取成功*/elsereturn my_conut;
/*返回成功读取的字节数*/staticssize_t key_write(struct file *my_file, const char __user *buf, size_t my_conut, loff_t *my_loff)int error;
printk("write ok !\\n");
return 1;
static long key_unlocked_ioctl(struct file *my_file, unsigned int cmd, unsigned long argv)int dat;
/*只有传递地址的时候才需要转换-----*/
void __user *argv1=(void __user*)argv;
//强制转换地址
printk("argv1=%ld\\n",*(unsigned long*)argv1);
//取出数据argv=(unsigned long*)argv;
/*转为指针形式*/switch(cmd)case Cmd_LEDON:
dat=100;
copy_to_user(argv,&
dat,4);
printk("LEDON_----->
OK\\n");
break;
case Cmd_LEDOFF:
dat=200;
copy_to_user(argv,&
dat,4);
printk("LEDOFF_----->
OK\\n");
break;
return 0;
/*poll--*/
unsigned int my_poll(struct file *my_file, struct poll_table_struct * p)
/*唤醒休眠的进程*/
poll_wait(my_file,&
wait,p);
/*添加等待队列--不是立即休眠*/
printk("<
1>
""8888\\n");
if(condition==1)printk("drive----poll ----ok!\\n");
condition=0;
/*清除标志*/
return POLLIN;
/*返回事件*/return 0;
/*返回事件*//*异步通知助手*/
int key_fasync(int fd, struct file *my_file,int on)//异步通知int error;
printk("驱动层收到的文件描述符:%d\\n",fd);
error=fasync_helper(fd,my_file,on,&
myfasync);
printk("驱动层异步通知结构体文件描述符:%d\\n",myfasync->
fa_fd);
return error;
static intkey_release(struct inode *my_inode, struct file *my_file)int i;
//释放中断
for(i=0;
i<
4;
i++)free_irq(irq_buff[i],&
Key_interrupt[i]);
return 0;
/*定义一个文件操作集合结构体*/
static struct file_operations ops_key=
.owner = THIS_MODULE,
.read=key_read,/*读函数-被应用层read函数调用*/
.write=key_write,/*写函数-被应用层write函数调用*/
.open=key_open,/*打开函数-被应用层open函数调用*/
.release=key_release, /*释放函数*/
.unlocked_ioctl=key_unlocked_ioctl,/*ioctl操作*/
.poll=my_poll,/*poll机制*/
.fasync=key_fasync,/*异步通知助手*/
;
static int __init key_init1(void)/*动态分配一个设备号*/
alloc_chrdev_region(&
keydev,0,1,"mykey");
//我们可以读取/proc/devices文件以获得Linux内核分配给设备的主设备号和设备名字
/*动态分配cdev结构体,返个cdev结构;
如果执行失败,将返回NULL。*/
keyCdev = cdev_alloc();
/*初始化Cdev结构体*/
cdev_init(keyCdev,&
ops_key);
/*注册Cdev结构体*/
cdev_add(keyCdev,keydev,1);
/*创建类*/
cls=class_create(THIS_MODULE,"my_key");
/*在类下面创建设备*/
device_create(cls,NULL,keydev,NULL,"my_delaywork");
///dev//*创建自己的工作队列*/
my_work_queue =create_workqueue("my_workqueue");
/*初始化延时工作队列*/
INIT_DELAYED_WORK(&
my_delay_work,my_work_func);
/*初始化无延时的工作队列*/
// INIT_WORK(&
my_work,my_work_func);
printk("<
1>
""key drive init OK!!-->
__FILE__=%s__LINE__=%d\\n",__FILE__,__LINE__);
return 0;
//KERN_EMERG
static void __exit key_exit(void)device_destroy(cls,keydev);
//注销设备节点
class_destroy(cls);
//注销分配的类
cdev_del(keyCdev);
//注销CDEV结构体
unregister_chrdev_region(keydev,1);
//注销设备
kfree(keyCdev);
//释放结构体
printk("<
1>
""key drive exit OK!! -->
__FILE__=%s__LINE__=%d\\n",__FILE__,__LINE__);
//EXPORT_SYMBOL(key_init);
module_init(key_init1);
/*驱动入口*/
module_exit(key_exit);
/*驱动出口*/
MODULE_LICENSE("GPL");
推荐阅读
- SMTP和HTTP协议一样都属于请求应答式协议
- Flutter 专题87 初识状态管理 Bloc#yyds干货盘点#
- APP metrics,loT传感器数据和实时分析数据
- 路由基础之基本的路由策略配置
- 在查询层面统一时序数据和关系数据可消除数据孤岛
- 实战篇(MySQL优化系列--SQL优化实战)
- VUE深度解析
- 前端VueRouter解析
- Zabbix 5.4 Server安装