Linux的fasync驱动异步通知详解【转】

本文转载自:http://blog.csdn.net/coding__madman/article/details/51851338

版权声明:本文为博主原创文章,未经博主允许不得转载。

工作项目用有个需求是监测某个GPIO输入方波的频率!通俗的讲就是一个最最简单的测方波频率的示波器!不过只是测方波的频率!频率范围是0~200HZ,而且频率方波不是一直都是200HZ,大多数的时候可能一直是0或者一个更低频率的方波!同时要考虑到方波有可能一直维持在200HZ ,同时保持效率和性能的情况下,fasync驱动异步通知是个不错的选择,当初写demo的时候实测1K的方波完全没有问题!应用到项目中也是完全能满足需求!驱动很简单!业余时间把自己之前学到的知识总结一下!对自己也是个提高!

根据需求,驱动中实现比较简单!自己只实现open、close、fasync和read函数 ,这里只需要读取方波的频率即可!

驱动大概实现原理:方波每产生一个下降沿,产生一个中断,然后根据中断在通过异步通知应用程序,以此来测定输入方波的频率!

fansync机制的优势是能使驱动的读写和应用程序的读写分开,使得应用程序可以在驱动读写的时候去做别的事情!

下面是驱动的源码:

[cpp] view plain copy

  1. **-------File Info---------------------------------------------------------------------------------------
  2. ** File Name:               gpioInt.c
  3. ** Latest modified Data:    2015_11_16
  4. ** Latest Version:          v1.0
  5. ** Description:             NOME
  6. **
  7. **--------------------------------------------------------------------------------------------------------
  8. ** Create By:               K
  9. ** Create date:             20015-11-16
  10. ** Version:                 v1.0
  11. ** Descriptions:            混杂设备驱动程序 GPIO中断驱动 下降沿触发GPIO 内核会向用户空间发送一个键值
  12. **                          用户态的应用程序通过读取键值来判断GPIO中断状况
  13. **
  14. **--------------------------------------------------------------------------------------------------------
  15. *********************************************************************************************************/
  16. #include<linux/init.h>
  17. #include<linux/module.h>
  18. #include<mach/gpio.h>
  19. #include<asm/io.h>
  20. #include"mach/../../mx28_pins.h"
  21. #include <mach/pinctrl.h>
  22. #include "mach/mx28.h"
  23. #include<linux/fs.h>
  24. #include <linux/io.h>
  25. #include<asm/uaccess.h>
  26. #include<linux/miscdevice.h>
  27. #include<linux/irq.h>
  28. #include<linux/sched.h>
  29. #include<linux/interrupt.h>
  30. #include<linux/timer.h>
  31. #include <linux/kernel.h>
  32. #include <linux/delay.h>
  33. #include <asm/uaccess.h>
  34. #include <asm/io.h>
  35. /*
  36. *中断事件标志,中断服务程序将它置1,在gpio_drv_read将它清0
  37. */
  38. static volatile int ev_press = 0;
  39. /*
  40. *异步结构体指针 用于读
  41. */
  42. static struct fasync_struct *b_async;
  43. /*
  44. *中断引脚描述结构体
  45. */
  46. struct pin_desc_s{
  47. unsigned int pin;
  48. unsigned int key_val;
  49. unsigned int irq;
  50. };
  51. static unsigned char key_val;
  52. struct pin_desc_s pin_desc[5] = {
  53. {MXS_PIN_TO_GPIO(PINID_LCD_ENABLE),0x03,},    /* IO1 rain GPIO1_31     */
  54. {MXS_PIN_TO_GPIO(PINID_LCD_HSYNC),0x05,},     /* IO2 windspeed GPIO1_29*/
  55. {MXS_PIN_TO_GPIO(PINID_LCD_DOTCK),0x0A,},     /* 机箱门             */
  56. {MXS_PIN_TO_GPIO(PINID_AUART3_RX),0x07,},     /* key1 GPIO3_12         */
  57. {MXS_PIN_TO_GPIO(PINID_AUART3_TX),0x09,},     /* key2 GPIO3_13         */
  58. };
  59. static DECLARE_MUTEX(b_lock);
  60. static DECLARE_WAIT_QUEUE_HEAD(b_waitq);
  61. static irqreturn_t b_irq(int irq, void *dev_id)
  62. {
  63. struct pin_desc_s * pindesc = (struct pin_desc_s *)dev_id;
  64. unsigned int pinval;
  65. pinval = gpio_get_value(pindesc->pin);
  66. if (pinval)
  67. {
  68. key_val = 1;
  69. }
  70. else
  71. {
  72. key_val = pindesc->key_val;
  73. }
  74. ev_press = 1;
  75. wake_up_interruptible(&b_waitq);        //唤醒等待队列里面的进程
  76. kill_fasync(&b_async, SIGIO, POLL_IN);  //异步通知
  77. //printk("interrupt occur..........\n");
  78. return IRQ_RETVAL(IRQ_HANDLED);
  79. }
  80. static int gpio_drv_open(struct inode *inode, struct file *file)
  81. {
  82. int iRet[5]={0};
  83. int i = 0;
  84. if (file->f_flags & O_NONBLOCK)
  85. {
  86. if (down_trylock(&b_lock))
  87. return -EBUSY;
  88. }
  89. else
  90. {
  91. down(&b_lock);
  92. }
  93. for(i = 0; i < 5; i++)
  94. {
  95. gpio_direction_input((pin_desc[i]).pin);
  96. (pin_desc[i]).irq = gpio_to_irq((pin_desc[i]).pin);
  97. if ((pin_desc[i]).irq)
  98. disable_irq((pin_desc[i]).irq);
  99. set_irq_type((pin_desc[i]).irq, IRQF_TRIGGER_FALLING);  //下降沿中断
  100. iRet[i] = request_irq((pin_desc[i]).irq, buttons_irq, IRQF_SHARED, "gpio_int", &pin_desc[i]);
  101. if (iRet[i] != 0){
  102. printk("request irq failed!! ret: %d  irq:%d \n", iRet[i],(pin_desc[i]).irq);
  103. return -EBUSY;}
  104. }
  105. return 0;
  106. }
  107. ssize_t gpio_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
  108. {
  109. if (size != 1)
  110. return -EINVAL;
  111. if (file->f_flags & O_NONBLOCK)
  112. {
  113. if (!ev_press)
  114. return -EAGAIN;
  115. }
  116. else
  117. {
  118. wait_event_interruptible(b_waitq, ev_press);
  119. }
  120. copy_to_user(buf, &key_val, 1);
  121. ev_press = 0;
  122. return 1;
  123. }
  124. int gpio_drv_close(struct inode *inode, struct file *file)
  125. {
  126. int i = 0;
  127. for( i = 0; i < 5; i++)
  128. {
  129. free_irq((pin_desc[i]).irq, &pin_desc[i]);
  130. }
  131. up(&b_lock);
  132. return 0;
  133. }
  134. static int gpio_drv_fasync (int fd, struct file *filp, int on)
  135. {
  136. printk("driver: gpio_drv_successful\n");
  137. return fasync_helper (fd, filp, on, &b_async);
  138. }
  139. static struct file_operations gpio_drv_fops = {
  140. .owner      = THIS_MODULE,
  141. .open       = gpio_drv_open,
  142. .read       = gpio_drv_read,
  143. .release    = gpio_drv_close,
  144. .fasync     = gpio_drv_fasync,
  145. };
  146. static struct miscdevice b_miscdev =
  147. {
  148. .minor          = MISC_DYNAMIC_MINOR,
  149. .name           = "magic-gpio",
  150. .fops           = &gpio_drv_fops,
  151. };
  152. static int __init gpio_drv_init(void)
  153. {
  154. int iRet=0;
  155. printk("gpio_miscdev module init!\n");
  156. iRet = misc_register(&b_miscdev);
  157. if (iRet) {
  158. printk("register failed!\n");
  159. }
  160. return 0;
  161. }
  162. static void __exit gpio_drv_exit(void)
  163. {
  164. printk("gpio_miscdev module exit!\n");
  165. misc_deregister(&b_miscdev);
  166. }
  167. module_init(gpio_drv_init);
  168. module_exit(gpio_drv_exit);
  169. MODULE_AUTHOR("HEHAI & RK");
  170. MODULE_LICENSE("Dual BSD/GPL");
  171. MODULE_DESCRIPTION("gpio interrupt module");

首先还是先从init函数来总结:该驱动是一混杂设备驱动模型来写的,这个主要是借鉴网上的好多资料都是一这种模式来写的,Linux里面misc混杂设备驱动的主设备号是为10的驱动设备,init模块首先是用 misc_register()函数注册一个一个混杂设备驱动,参数一个混杂设备驱动里面非常重要的一个数据结构 struct miscdevice,下面把原型贴出来:

[cpp] view plain copy

  1. struct miscdevice  {
  2. int minor;
  3. const char *name;
  4. const struct file_operations *fops;
  5. struct list_head list;
  6. struct device *parent;
  7. struct device *this_device;
  8. const char *nodename;
  9. mode_t mode;
  10. };

当然我上面的驱动代码只初始化了前面的关键三项:

[cpp] view plain copy

  1. static struct miscdevice b_miscdev =
  2. {
  3. .minor          = MISC_DYNAMIC_MINOR,
  4. .name           = "magic-gpio",
  5. .fops           = &gpio_drv_fops,
  6. };

这里先说说 .minor这个成员:定义次设备号的,这里使用了一个MISC_DYNAMIC_MINOR宏! 这个宏的意思就是动态分配次设备号!而且这个次设备号不会超过64!实现的方法比较巧妙!这里贴出一篇相关的文章:

http://blog.csdn.NET/yongan1006/article/details/6778285 这个可以研究一下,还比较有意思!

剩下的两个name 和 fops成员对驱动开发来说就最熟悉不过了!驱动的名字和驱动的接口函数这里就不说了!

注册混杂设备驱动后就是接口函数的表演了!

这里和内核硬件相关的就是struct pin_desc_s 结构了,硬件的初始化工作比较简单,放在open函数里面了!

[cpp] view plain copy

  1. struct pin_desc_s pin_desc[5] = {
  2. {MXS_PIN_TO_GPIO(PINID_LCD_ENABLE),0x03,},    /* IO1 rain GPIO1_31     */
  3. {MXS_PIN_TO_GPIO(PINID_LCD_HSYNC),0x05,},     /* IO2 windspeed GPIO1_29*/
  4. {MXS_PIN_TO_GPIO(PINID_LCD_DOTCK),0x0A,},     /* 机箱门             */
  5. {MXS_PIN_TO_GPIO(PINID_AUART3_RX),0x07,},     /* key1 GPIO3_12         */
  6. {MXS_PIN_TO_GPIO(PINID_AUART3_TX),0x09,},     /* key2 GPIO3_13         */
  7. };

这里把好几个gpio接口都放到这一个里面了!都是后边加进去的!上面的是直接根据文档在内核头文件中找到GPIO引脚对应的宏定义的!后边是给GPIO设置的键值!就是当应用程序收到一个signal后,根据读取到的键值来区分是哪一个GPIO发生了中断或是有信号传过来!看看open函数:

[cpp] view plain copy

  1. static int gpio_drv_open(struct inode *inode, struct file *file)
  2. {
  3. int iRet[5]={0};
  4. int i = 0;
  5. if (file->f_flags & O_NONBLOCK)//非阻塞
  6. {
  7. if (down_trylock(&b_lock))
  8. return -EBUSY;
  9. }
  10. else
  11. {
  12. down(&b_lock);
  13. }
  14. for(i = 0; i < 5; i++)
  15. {
  16. gpio_direction_input((pin_desc[i]).pin);//设置对应的GPIO输入
  17. (pin_desc[i]).irq = gpio_to_irq((pin_desc[i]).pin);//把GPIO对应的pin值转换为相应的IRQ值并返回
  18. if ((pin_desc[i]).irq)
  19. disable_irq((pin_desc[i]).irq);//先关闭中断并等待中断处理完
  20. set_irq_type((pin_desc[i]).irq, IRQF_TRIGGER_FALLING);  //设置下降沿中断
  21. iRet[i] = request_irq((pin_desc[i]).irq, b_irq, IRQF_SHARED, "gpio_int", &pin_desc[i]);
  22. if (iRet[i] != 0){
  23. printk("request irq failed!! ret: %d  irq:%d \n", iRet[i],(pin_desc[i]).irq);
  24. return -EBUSY;}
  25. }
  26. return 0;
  27. }

这里可以详细了解一下关于GPIO的一些API函数:http://blog.sina.com.cn/s/blog_a6559d9201015vx9.html

request_irq函数:http://blog.csdn.net/wealoong/article/details/7566546
说说上面的request_irq函数了:

int request_irq(unsigned int irq, irq_handler_t handler,
                         unsigned long irqflags, const char *devname, void *dev_id)
irq是要申请的硬件中断号。
handler是向系统注册的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,dev_id参数将被传递给它。
irqflags是中断处理的属性,SA_SHARED表示多个设备共享中断,
devname设置中断名称,通常是设备驱动程序的名称  在cat /proc/interrupts中可以看到此名称。
dev_id在中断共享时会用到,一般设置为这个设备的设备结构体或者NULL。
request_irq()返回0表示成功,返回-INVAL表示中断号无效或处理函数指针为NULL,返回-EBUSY表示中断已经被占用且不能共享。

这里用到回调函数b_irq函数就是根据响应的GPIO中断返回设置好的相应的值,这样应用程序在得到这个值的时候就可以知道是哪个GPIO发送的中断!

b_irq函数:

[cpp] view plain copy

  1. static irqreturn_t b_irq(int irq, void *dev_id)
  2. {
  3. struct pin_desc_s * pindesc = (struct pin_desc_s *)dev_id;
  4. unsigned int pinval;
  5. pinval = gpio_get_value(pindesc->pin);
  6. if (pinval)
  7. {
  8. key_val = 1;
  9. }
  10. else
  11. {
  12. key_val = pindesc->key_val;
  13. }
  14. ev_press = 1;
  15. wake_up_interruptible(&b_waitq);        //唤醒等待队列里面的进程
  16. kill_fasync(&b_async, SIGIO, POLL_IN);  //异步通知
  17. //printk("interrupt occur..........\n");
  18. return IRQ_RETVAL(IRQ_HANDLED);
  19. }

其中上面的b_waitq是这样定义的:

[cpp] view plain copy

  1. static DECLARE_WAIT_QUEUE_HEAD(b_waitq);//生成一个等待队列的头 名字为b_waitq

关于等待队列可以看下这篇文章:http://www.cnblogs.com/xmphoenix/archive/2011/11/20/2256419.html

其实这里有一个很关键的地方就是kill_fasync异步通知应用程序。这里有很关键的一步,可以说是整个驱动程序的核心:kill_fasync 及 fasync_helper用于异步通知中,其中 kill_fasync(&b_async,SIGIO,POLL_IN)函数的功能是向应用程序发送可读信号,还有那个进程调用fasync_helper函数就向谁发!这个可以结合应用程序是如何拿到信号的对比着看,关于应用程序这里就不说了!网上的资料也比较多讲解的也很详细!例程代码还有理论分析都有!

fansync_helpr函数内部实现:

[cpp] view plain copy

  1. int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
  2. {
  3. struct fasync_struct *fa, **fp;
  4. struct fasync_struct *new = NULL;
  5. int result = 0;
  6. if (on) {
  7. new = kmem_cache_alloc(fasync_cache, GFP_KERNEL);
  8. if (!new)
  9. return -ENOMEM;
  10. }
  11. write_lock_irq(&fasync_lock);
  12. for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) {
  13. if (fa->fa_file == filp) {
  14. if(on) {
  15. fa->fa_fd = fd;   //区分向谁发
  16. kmem_cache_free(fasync_cache, new);
  17. } else {
  18. *fp = fa->fa_next;
  19. kmem_cache_free(fasync_cache, fa);
  20. result = 1;
  21. }
  22. goto out;
  23. }
  24. }
  25. if (on) {
  26. new->magic = FASYNC_MAGIC;
  27. new->fa_file = filp;
  28. new->fa_fd = fd;
  29. new->fa_next = *fapp;
  30. *fapp = new;
  31. result = 1;
  32. }
  33. out:
  34. write_unlock_irq(&fasync_lock);
  35. return result;
  36. }

kill_fasync函数里面的b_async参数:struct fasync_struct类型定义:

[cpp] view plain copy

  1. struct   fasync_struct   {
  2. int magic;
  3. int fa_fd;
  4. struct fasync_struct *fa_next;
  5. struct file   *fa_file;
  6. };

这个参数在下面中也被调用:实现的fasync成员函数

[cpp] view plain copy

  1. static int gpio_drv_fasync (int fd, struct file *filp, int on)
  2. {
  3. printk("driver: gpio_drv_successful\n");
  4. return fasync_helper (fd, filp, on, &b_async);
  5. }

这也是应用程序和内核之间传参的一个关键:

要实现传参,我们需要把一个结构体struct fasync_struct添加到内核的异步队列中,这个结构体用来存放对应设备文件的信息(如fd, filp)并交给内核来管理。一但收到信号,内核就会在这个所谓的异步队列头找到相应的文件(fd),并在filp->owner中找到对应的进程PID,并且调用对应的sig_handler了。

关于剩下的程序中用到的down() 、up() 还有 DECILARE_MUTEX(b_lock)这里简单的用到了信号量的两个简单的操作,主要是用于保护临界资源,保证中断不被丢失!

剩下的read和close都比较简单,驱动里面的函数基本都是对应的,close里面一把是释放所有申请的资源!这也是模块化驱动的一个好处!虽然这个驱动很简单!但是要仔细深究起来,里面所涉及的知识量也不小!上面也只是简单的分析总结一下!做个笔记算是对自己的一个提高,也别人在参考的时候能有一点点的帮助!

最近住的地方没网!感觉好长时间没写博客了!现在业余时间看Linux驱动设备详解,哈哈,比一年多前看的效果好多了,至少书上的好多知识多多少少都接触过!而且看起来还比较有收获,就是看了就忘!看来总结还是相当重要的!好记性不如烂笔头!

时间: 2024-09-30 00:46:55

Linux的fasync驱动异步通知详解【转】的相关文章

Linux的fasync驱动异步通知详解

工作项目用有个需求是监测某个GPIO输入方波的频率!通俗的讲就是一个最最简单的测方波频率的示波器!不过只是测方波的频率!频率范围是0~200HZ,而且频率方波不是一直都是200HZ,大多数的时候可能一直是0或者一个更低频率的方波!同时要考虑到方波有可能一直维持在200HZ ,同时保持效率和性能的情况下,fasync驱动异步通知是个不错的选择,当初写demo的时候实测1K的方波完全没有问题!应用到项目中也是完全能满足需求!驱动很简单!业余时间把自己之前学到的知识总结一下!对自己也是个提高! 根据需

linux视频处理驱动通道的详解

视频信息是由前端数据采集-->ISP图像处理-->scaler到目标图像大小-->encode-->送给用户(例如电视可显示) 视频数据量大,处理图像环节多.如果每一个环节都把数据存到内存,然后在做下一步的动作.大家应该知道些内存是一个很慢的动作,这样系统效率就非常的低.所以内核驱动程序的好坏直接决定了这个芯片的可用性. 现在市面上主流的芯片厂商都是通过通道技术来实现从图像输入到视频流输出. 通道技术具体实现方式如下,各位同行可以参考. A -- 采集数据 B--图像处理 C--s

迅为4412开发板Linux驱动教程——总线_设备_驱动注册流程详解

视频下载地址: 驱动注册:http://pan.baidu.com/s/1i34HcDB 设备注册:http://pan.baidu.com/s/1kTlGkcR 总线_设备_驱动注册流程详解 ? 注册流程图 ? 设备一般都需要先注册,才能注册驱动 – 现在越来越多的热拔插设备,反过来了.先注册驱动,设备来了再注册 设备 ? 本节使用的命令 – 查看总线的命令#ls /sys/bus/ – 查看设备号的命令#cat /proc/devices ? 设备都有主设备号和次设备号,否则255个设备号不

Linux进程间通信与线程间同步详解(全面详细)

引用:http://community.csdn.net/Expert/TopicView3.asp?id=4374496linux下进程间通信的几种主要手段简介: 1. 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信:   2. 信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身

[C] linux静态链接库与动态链接库详解

http://blog.chinaunix.net/u2/76292/showart.php?id=1274181 一顺便说说了哦  通常情况下,对函数库的链接是放在编译时期(compile time)完成的.所有相关的对象文件(object file)与牵涉到的函数库(library)被链接合成一个可执行文件(executable file).程序在运行时,与函数库再无瓜葛,因为所有需要的函数已拷贝到自己门下.所以这些函数库被成为静态库(static libaray),通常文件名为"libxx

linux中文乱码问题及locale详解

--set github proxy git config --global http.proxy http://user_name:[email protected]_ip:port git config --system http.sslcainfo /bin/curl-ca-bundle.crt --cancel proxy git config --global (or --system or --local) --unset http.proxy linux中文乱码问题及locale详

linux添加开机自启动脚本示例详解

来源: linux添加开机自启动脚本示例详解 linux下(以RedHat为范本)添加开机自启动脚本有两种方法,先来简单的; 一.在/etc/rc.local中添加如果不想将脚本粘来粘去,或创建链接什么的,则:step1. 先修改好脚本,使其所有模块都能在任意目录启动时正常执行;step2. 再在/etc/rc.local的末尾添加一行以绝对路径启动脚本的行;如:$ vim /etc/rc.local#!/bin/sh## This script will be executed *after*

Linux的Ext2文件系统(Inode&Block)详解

前述:Linux系统管理员很重要的任务之一就是管理好自己的磁盘文件系统,每个分区不可太大也不可以太小,太大会导致磁盘容量的浪费,太小会导致产生的文件无法存储的问题.在Linux里面文件是由两部分数据组成,一部分是metadata,另一部分是data.那么这些数据都存放在文件系统的什么地方呢?这就让我们必须得了解文件系统的Inode与Block的基本原理了,而Linux最传统的磁盘文件系统使用的是Ext2,所以我们了解下它的内部原理. 第一部分:磁盘的组成和分区(基础) 磁盘的机械部分: 1.圆形

Linux 查看系统硬件信息(实例详解)

cpu lscpu命令,查看的是cpu的统计信息. [email protected]:~$ lscpu Architecture: i686 #cpu架构 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian #小尾序 CPU(s): 4 #总共有4核 On-line CPU(s) list: 0-3 Thread(s) per core: 1 #每个cpu核,只能支持一个线程,即不支持超线程 Core(s) per socket: