在上一节里,我们在中断的基础上添加poll机制来实现有数据的时候就去读,没数据的时候,自己规定一个时间,如果还没有数据,就表示超时时间。在此以前,我们都是让应用程序主动去读,那有没有一种情况,当驱动程序有数据时,主动去告诉应用程序,告诉它,有数据了,你赶紧来读吧。答案当然是有的,这种情况在Linux里的专业术语就叫异步通知。
上一节文章链接:http://blog.csdn.net/u013491946/article/details/72886234
在这一节里,我们将在上一节的基础上修改驱动,将其修改为有异步通知功能的按键驱动,目标:按下按键时,驱动主动去通知应用程序。
问:如何实现异步通知,有哪些要素?
答:有四个要素:
一、应用程序要实现有:注册信号处理函数,使用signal函数
二、谁来发?驱动来发
三、发给谁?发给应用程序,但应用程序必须告诉驱动PID
四、怎么发?驱动程序使用kill_fasync函数
问:应该在驱动的哪里调用kill_fasync函数?
答:kill_fasync函数的作用是,当有数据时去通知应用程序,理所当然的应该在用户终端处理函数里调用。
问:file_operations需要添加什么函数指针成员吗?
答:要的,需要添加fasync函数指针,要实现这个函数指针,幸运的是,这个函数仅仅调用了fasync_helper函数,而且这个函数是内核帮我们实现好了,驱动工程师不用修改,fasync_helper函数的作用是初始化/释放fasync_struct
详细请参考驱动源码:
- #include <linux/kernel.h>
- #include <linux/fs.h>
- #include <linux/init.h>
- #include <linux/delay.h>
- #include <linux/irq.h>
- #include <asm/uaccess.h>
- #include <asm/irq.h>
- #include <asm/io.h>
- #include <linux/module.h>
- #include <linux/device.h> //class_create
- #include <mach/regs-gpio.h> //S3C2410_GPF1
-
- #include <mach/hardware.h>
-
- #include <linux/interrupt.h> //wait_event_interruptible
- #include <linux/poll.h> //poll
- #include <linux/fcntl.h>
-
-
-
- static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
-
-
- static struct class *fifthdrv_class;
- static struct device *fifthdrv_device;
-
- static struct pin_desc{
- unsigned int pin;
- unsigned int key_val;
- };
-
- static struct pin_desc pins_desc[4] = {
- {S3C2410_GPF1,0x01},
- {S3C2410_GPF4,0x02},
- {S3C2410_GPF2,0x03},
- {S3C2410_GPF0,0x04},
- };
-
- static int ev_press = 0;
-
-
-
- static unsigned char key_val;
- int major;
-
- static struct fasync_struct *button_fasync;
-
-
- static irqreturn_t buttons_irq(int irq, void *dev_id)
- {
- struct pin_desc *pindesc = (struct pin_desc *)dev_id;
- unsigned int pinval;
- pinval = s3c2410_gpio_getpin(pindesc->pin);
-
- if(pinval)
- {
-
- key_val = 0x80 | (pindesc->key_val);
- }
- else
- {
-
- key_val = pindesc->key_val;
- }
-
- ev_press = 1;
- wake_up_interruptible(&button_waitq);
-
-
-
-
-
-
- kill_fasync(&button_fasync, SIGIO, POLL_IN);
- return IRQ_HANDLED;
- }
- static int fifth_drv_open(struct inode * inode, struct file * filp)
- {
-
-
-
-
- request_irq(IRQ_EINT1, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K1",&pins_desc[0]);
- request_irq(IRQ_EINT4, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K2",&pins_desc[1]);
- request_irq(IRQ_EINT2, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K3",&pins_desc[2]);
- request_irq(IRQ_EINT0, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K4",&pins_desc[3]);
- return 0;
- }
-
- static ssize_t fifth_drv_read(struct file *file, char __user *user, size_t size,loff_t *ppos)
- {
- if (size != 1)
- return -EINVAL;
-
-
-
-
-
-
-
- wait_event_interruptible(button_waitq, ev_press);
- copy_to_user(user, &key_val, 1);
-
-
- ev_press = 0;
- return 1;
- }
-
- static int fifth_drv_close(struct inode *inode, struct file *file)
- {
- free_irq(IRQ_EINT1,&pins_desc[0]);
- free_irq(IRQ_EINT4,&pins_desc[1]);
- free_irq(IRQ_EINT2,&pins_desc[2]);
- free_irq(IRQ_EINT0,&pins_desc[3]);
- return 0;
- }
-
- static unsigned int fifth_drv_poll(struct file *file, poll_table *wait)
- {
- unsigned int mask = 0;
-
-
- poll_wait(file, &button_waitq, wait);
-
-
-
-
- if(ev_press)
- {
- mask |= POLLIN | POLLRDNORM;
- }
-
-
- return mask;
- }
-
-
-
-
-
-
- static int fifth_drv_fasync(int fd, struct file *filp, int on)
- {
- return fasync_helper(fd, filp, on, &button_fasync);
- }
-
-
- static const struct file_operations fifth_drv_fops = {
- .owner = THIS_MODULE,
- .open = fifth_drv_open,
- .read = fifth_drv_read,
- .release = fifth_drv_close,
- .poll = fifth_drv_poll,
- .fasync = fifth_drv_fasync,
- };
-
-
-
- static int fifth_drv_init(void)
- {
-
- major = register_chrdev(0, "fifth_drv", &fifth_drv_fops);
-
-
- fifthdrv_class = class_create(THIS_MODULE, "fifthdrv");
-
-
- fifthdrv_device = device_create(fifthdrv_class, NULL, MKDEV(major, 0), NULL, "buttons");
-
- return 0;
- }
-
-
- static void fifth_drv_exit(void)
- {
- unregister_chrdev(major, "fifth_drv");
- device_unregister(fifthdrv_device);
- class_destroy(fifthdrv_class);
- }
-
- module_init(fifth_drv_init);
- module_exit(fifth_drv_exit);
-
- MODULE_AUTHOR("LWJ");
- MODULE_DESCRIPTION("Just for Demon");
- MODULE_LICENSE("GPL");
应用测试程序源码:
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <unistd.h> //sleep
- #include <poll.h>
- #include <signal.h>
- #include <fcntl.h>
-
- int fd;
-
- void mysignal_fun(int signum)
- {
- unsigned char key_val;
- read(fd,&key_val,1);
- printf("key_val = 0x%x\n",key_val);
- }
-
-
-
-
- int main(int argc ,char *argv[])
- {
- int flag;
- signal(SIGIO,mysignal_fun);
-
- fd = open("/dev/buttons",O_RDWR);
- if (fd < 0)
- {
- printf("open error\n");
- }
-
-
-
-
- fcntl(fd, F_SETOWN, getpid());
-
-
-
-
- flag = fcntl(fd,F_GETFL);
-
-
-
-
-
- fcntl(fd,F_SETFL,flag | FASYNC);
-
- while(1)
- {
-
- sleep(1000);
- }
- return 0;
- }
测试步骤:
- [WJ2440]# ls
- Qt fifth_drv.ko lib sddisk udisk
- TQLedtest fifth_test linuxrc second_drv.ko usr
- app_test first_drv.ko mnt second_test var
- bin first_test opt sys web
- dev fourth_drv.ko proc third_drv.ko
- driver_test fourth_test root third_test
- etc home sbin tmp
- [WJ2440]# insmod fifth_drv.ko
- [WJ2440]# lsmod
- fifth_drv 3360 0 - Live 0xbf006000
- [WJ2440]# ls /dev/buttons -l
- crw-rw---- 1 root root 252, 0 Jan 2 04:27 /dev/buttons
- [WJ2440]# ./fifth_test
- key_val = 0x1
- key_val = 0x81
- key_val = 0x4
- key_val = 0x84
- key_val = 0x2
- key_val = 0x82
- key_val = 0x3
- key_val = 0x83
- key_val = 0x4
- key_val = 0x84
- key_val = 0x84
由测试可知,当无按键按下时,应用测试程序一直在sleep,当有按键按下时,signal会被调用,最终会调用mysignal_fun,在此函数里read(fd,&key_val,1);会去读出按键值,这样一来,应用程序就相当于不用主动去读数据了,每当驱动里有数据时,就会告诉应用程序有数据了,你该去读数据了,此时read函数才会被调用。
这里最后总结一下韦老师的笔记:
为了使设备支持异步通知机制,驱动程序中涉及以下3项工作:
1. 支持F_SETOWN命令,能在这个控制命令处理中设置filp->f_owner为对应进程ID。
不过此项工作已由内核完成,设备驱动无须处理。
2. 支持F_SETFL命令的处理,每当FASYNC标志改变时,驱动程序中的fasync()函数将得以执行。
驱动中应该实现fasync()函数。
3. 在设备资源可获得时,调用kill_fasync()函数激发相应的信号
应用程序:
fcntl(fd, F_SETOWN, getpid()); // 告诉内核,发给谁
Oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, Oflags | FASYNC); // 改变fasync标记,最终会调用到驱动的faync > fasync_helper:初始化/释放fasync_struct