一起分析Linux系统设计思想——05字符设备驱动之按键驱动

Posted 穿越临界点

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一起分析Linux系统设计思想——05字符设备驱动之按键驱动相关的知识,希望对你有一定的参考价值。

在学习资料满天飞的大环境下,知识变得非常零散,体系化的知识并不多,这就导致很多人每天都努力学习到感动自己,最终却收效甚微,甚至放弃学习。我的使命就是过滤掉大量的无效信息,将知识体系化,以短平快的方式直达问题本质,把大家从大海捞针的痛苦中解脱出来。


接上一篇,继续介绍应用层使用同步机制实现按键驱动的方法,以及内核为此提供的中庸之法——select/poll/epoll接口。

2.2 使用非阻塞(轮循与退出)方式实现

应用层使用非阻塞——轮循与退出方式实现起来比较简单。主要就是应用层在read不对该线程进行阻塞,而是立即返回。返回后可以做一些其他的事情(当然也可以选择睡一会儿,此时的睡眠不要和按键的轮循混淆,因为此时的睡眠是可选的,可睡可不睡,和内核态按键值传递到用户态没有什么关系)。

2.2.1 内核驱动程序

内核驱动程序在上一篇的基础上只做了2处小的改动。一处是在read接口实现时不再进入阻塞,而是直接将按键值返回给用户态;另一处是在中断上下文中不再唤醒阻塞队列。

Tips:为了保证代码完整性和可执行性,有一些未改动的代码也复制过来了,大家可以只关注改动点。

  1 #include <linux/module.h>
  2 #include <linux/kernel.h>
  3 #include <linux/fs.h>
  4 #include <linux/init.h>
  5 #include <asm/io.h>         /* ioremap()  */
  6 #include <linux/uaccess.h>
  7 #include <linux/device.h>
  8 #include <linux/interrupt.h>
  9 #include <linux/irqreturn.h>
 10 
 11 #define GPFCON_ADD_BASE 0x56000050
 12 
 13 volatile unsigned int *pgpfcon = NULL;
 14 volatile unsigned int *pgpfdata = NULL;
 15 DECLARE_WAIT_QUEUE_HEAD(key_wait_q);
 16 volatile int event_press = 0;
 17 
 18 irqreturn_t keys_handler(int irq, void *dev_id)
 19 {
 20     printk("irq = %d is triggered. \\n", irq);
 21     /* Wake up the blocked thread */
 22     event_press = 1;
 23     //wake_up_interruptible(&key_wait_q); /*改动点*/
 24 
 25     return IRQ_HANDLED;
 26 }
 27 int cdriver_open(struct inode *inode, struct file *file)
 28 {
 29     int minor = MINOR(inode->i_rdev);
 30 
 31     printk("cdriver open success!\\n");
 32 
 33     request_irq(IRQ_EINT0, keys_handler, SA_TRIGGER_RISING, "key0", 1);
 34 
 35     return 0;
 36 }
 37
 38 int cdriver_release(struct inode *inode, struct file *file)
 39 {
 40     printk("cdriver released. \\n");
 41 
 42     free_irq(IRQ_EINT0, 1);
 43 
 44     return 0;
 45 }
 46 ssize_t key_read(struct file *file, char __user *user_buff, size_t len, loff_t *offset)
 47 {
 48     /* Don't Block current thread. */
 49     if (copy_to_user(user_buff, &event_press, sizeof(event_press)) < 0) /*改动点*/
 50         return -EFAULT;
 51     event_press = 0;
 52     //wait_event_interruptible(key_wait_q, event_press); /*改动点*/
 53 
 54     return 0;
 55 }
 56 
 57 struct file_operations cdriver_fops = {
 58     .owner = THIS_MODULE,
 59     .open = cdriver_open,
 60     .read = key_read,
 61     .release = cdriver_release,
 62 };
 63 
 64 int major = 0;
 65 struct class *led_class = NULL;
 66 struct class_device *led_class_dev[3] = {NULL};
 67 
 68 int __init cdriver_init(void)
 69 {
 70     int minor = 0;
 71 
 72     major = register_chrdev(0, "key_driver", &cdriver_fops);
 73 
 74     led_class = class_create(THIS_MODULE, "keys");
 75     if (NULL == led_class)
 76         return -EINVAL;
 77     for (minor = 0; minor < 3; minor++) {
 78         led_class_dev[minor] = class_device_create(led_class, NULL, MKDEV(major, minor), NULL, "key%d", minor);
 79         if (NULL == led_class_dev[minor])
 80             return -EINVAL;
 81     }
 82     return 0;
 83 }
 84 
 85 void __exit cdriver_exit(void)
 86 {
 87     int minor = 0;
 88 
 89 
 90     unregister_chrdev(major, "key_driver");
 91     for (minor = 0; minor < 3; minor++)
 92         class_device_unregister(led_class_dev[minor]);
 93     class_destroy(led_class);
 94 
 95 }
 96 
 97 module_init(cdriver_init);
 98 module_exit(cdriver_exit);
 99 MODULE_LICENSE("GPL");                         

2.2.2 应用程序实现

应用程序的改动更小。由于不再阻塞,所以read返回后需要根据读取的val值来判断按键是否按下过。

返回后可以加一些业务代码,也可以睡一会儿。

Tips:使用非阻塞方式——轮循就一定比阻塞方式实时性差么?答案是不一定。如果去掉sleep代码,让CPU跑满,甚至是将该线程绑定到指定核并进行核隔离,如果CPU主频比较高,实时性甚至是比阻塞方式要高很多的——参照DPDK实现思路就知道了。

所以,凡事不要背结论,也不要想当然,要根据实际情况从本质从微观角度去分析,去推理才能得到正确结论。

  1 #include <stdio.h>
  2 #include <fcntl.h>
  3 
  4 int main(void)
  5 {  
  6     int fd = 0;
  7     int val = 0xff; 
  8 
 10     fd = open("/dev/key0", O_RDWR);
 11     if (fd < 0) {
 12         printf("open /dev/key_dev failed!\\n");
 13         return -1;
 14     }
 15 
 16     for(;;) {    

 18             if (read(fd, &val, sizeof(val)) < 0) {
 19                 printf("read failed. \\n");
 20                 perror("read");
 21             } else {
 22                 if (val)
 23                     printf("key pressed, val = %d  \\n", val); 
 25             }
     		   /*do something else*/ /*此处可以添加一些业务代码*/
                usleep(20 * 1000); /*改行是可选的,不添加CPU占用率会比较高*/
 26     }
 27 
 28     (void)close(fd);
 29     
 30     return 0;
 31 }

2.3 中庸之道——select/poll/epoll

2.3.1 poll机制引入

有时候用户的需求是复杂的,并不是单纯的使用阻塞或者非阻塞可以解决。

阻塞前是不是可以先检查一下事件是否已经产生?阻塞时间过程是不是可以超时退出?当超时时间趋于0时就是非阻塞;超时时间趋于无穷大时就是阻塞。

为了兼顾上述需求,祭出大招——poll机制。select核epoll原理和poll大同小异,这里仅以poll为代表进行介绍。

2.3.2 内核驱动程序

poll函数的原型使用man指令可以查看,这里仅仅说明poll是如何从我们熟悉的代码演变成一个接口或者机制的。

其实,将2.1和2.2的代码组合起来就是poll机制。我们看下述的源码,来说明poll机制其实就是轮循与阻塞的合体。

使用poll机制,应用层在调用read之前就需要调用poll函数,最终会调用到内核驱动中的poll接口。我们看源码中的key_poll函数实现,其实和2.1中的key_read函数几乎是一样的,主要的差异有两点,一点是使用mask替代了event_press,这一点其实是换汤不换药。另外一点差异是主要差异,就是wait_event_interruptible函数将本线程挂入休眠队列后立马发起调度 令其休眠;而poll_wait函数只是将本线程挂入休眠队列,并不立马发生调度。不立马发起调度在等什么呢?等的就是和轮循合体。主要做了两件事,一件是判断此时的按键事件是否已经发生,发生索性就不休眠了,直接返回;另外一件大事就是在进入休眠之前启动一个超时定时器,然后调度该线程进入休眠,定时时间到之后,即使按键事件不发生也会唤醒该线程,唤醒该线程之后还会再去查询一下mask是否已经置位(再给最后一次机会嘛~),当然即使不置位也会直接返回用户态了,因为已经超时了。

Tips:如果不太理解上述这个流程或者想自己摸索一下,可以从内核中的sys_poll函数看起。

我们再来关注中断上下文keys_handler的处理其实和2.1的代码是完全相同的。

所以,poll机制并没有多么复杂,也不是什么新鲜发明,只是我们基本功能的组合(一组合就变得强大了,和动画片里的思路差不多哈~),我们自己也可以实现。


 19 irqreturn_t keys_handler(int irq, void *dev_id)
 20 {
 21     printk("[kernel]irq = %d is triggered. \\n", irq);
 22     /* Wake up the blocked thread */
 23     event_press = 1;
 24     wake_up_interruptible(&key_wait_q);
 25 
 26     return IRQ_HANDLED;
 27 }

 53 ssize_t key_read(struct file *file, char __user *user_buff, size_t len, loff_t *offset)
 54 {
 55     /* Block current thread. */
 56     if (copy_to_user(user_buff, &event_press, sizeof(event_press)) < 0)
 57         return -EFAULT;
 58     event_press = 0;
 59     //wait_event_interruptible(key_wait_q, event_press); /*1)挂入休眠队列;2)发起调度*/
 60 
 61     return 0;
 62 }  
 63    
 64 unsigned int key_poll(struct file *file, struct poll_table_struct *wait)
 65 {  
 66     unsigned int mask = 0;
 67    
 68     poll_wait(file, &key_wait_q, wait); /*只是将本线程挂入休眠队列,并不立马发生调度*/
 69    
 70     if (event_press)
 71         mask = POLLIN | POLLRDNORM;
 72 
 73     return mask;
 74 } 

101 struct file_operations cdriver_fops = {
102     .owner = THIS_MODULE,
103     .open = cdriver_open,
104 //    .write = led_write,
105     .read = key_read,
106     .poll = key_poll,
107     .release = cdriver_release,
108 };

2.3.3 应用程序实现

应用程序实现起来相对比较简单和容易理解。只需要将poll理解为从2.1小节的read中分离出了阻塞的功能自立门户就可以了。

  1 #include <stdio.h>
  2 #include <fcntl.h>
  3 #include <poll.h>
  4 
  5 struct pollfd fds = {0};
  6 
  7 int main(void)
  8 {
  9     int fd = 0;
 10     int val = 0xff;
 11 
 12 
 13     fd = open("/dev/key0", O_RDWR);
 14     if (fd < 0) {
 15         printf("open /dev/key_dev failed!\\n");
 16         return -1;
 17     }
 18 
 19     fds.fd = fd;
 20     fds.events = POLLIN;
 21 
 22     for(;;) {    
 23             if (poll(&fds, 1, -1) < 0) {
 24                 printf("poll faild. \\n");
 25                 perror("poll");
 26             }
 27     
 28             if (read(fd, &val, sizeof(val)) < 0) {
 29                 printf("read failed. \\n");
 30                 perror("read");
 31             } else {
 32                 if (val)
 33                     printf("key pressed, val = %d  \\n", val);
 34                 
 35             }
 36     }
 37 
 38     (void)close(fd);
 39 
 40     return 0;
 41 }

应用层使用同步机制实现按键驱动就介绍到此了,由于细节还很多,限于时间和篇幅不做过多介绍了,留给大家自己探索,遇到任何问题可以留言讨论。

下一篇我们一起探索如何使用异步机制来实现将按键事件从内核态传递给用户态。


恭喜你又坚持看完了一篇博客,又进步了一点点!如果感觉还不错就点个赞再走吧,你的点赞和关注将是我持续输出的哒哒哒动力~~

以上是关于一起分析Linux系统设计思想——05字符设备驱动之按键驱动的主要内容,如果未能解决你的问题,请参考以下文章

一起分析Linux系统设计思想——05字符设备驱动之按键驱动

一起分析Linux系统设计思想——05字符设备驱动之按键驱动

一起分析Linux系统设计思想——05中断框架剖析

一起分析Linux系统设计思想——05中断框架剖析

一起分析Linux系统设计思想——05中断框架剖析

一起分析Linux系统设计思想——05中断框架剖析