Linux驱动开发-内核共享工作队列

曾无好事来相访,赖尔高文一起予。这篇文章主要讲述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");


    推荐阅读