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机制的优势是能使驱动的读写和应用程序的读写分开,使得应用程序可以在驱动读写的时候去做别的事情!
下面是驱动的源码:
- **-------File Info---------------------------------------------------------------------------------------
- ** File Name: gpioInt.c
- ** Latest modified Data: 2015_11_16
- ** Latest Version: v1.0
- ** Description: NOME
- **
- **--------------------------------------------------------------------------------------------------------
- ** Create By: K
- ** Create date: 20015-11-16
- ** Version: v1.0
- ** Descriptions: 混杂设备驱动程序 GPIO中断驱动 下降沿触发GPIO 内核会向用户空间发送一个键值
- ** 用户态的应用程序通过读取键值来判断GPIO中断状况
- **
- **--------------------------------------------------------------------------------------------------------
- *********************************************************************************************************/
- #include<linux/init.h>
- #include<linux/module.h>
- #include<mach/gpio.h>
- #include<asm/io.h>
- #include"mach/../../mx28_pins.h"
- #include <mach/pinctrl.h>
- #include "mach/mx28.h"
- #include<linux/fs.h>
- #include <linux/io.h>
- #include<asm/uaccess.h>
- #include<linux/miscdevice.h>
- #include<linux/irq.h>
- #include<linux/sched.h>
- #include<linux/interrupt.h>
- #include<linux/timer.h>
- #include <linux/kernel.h>
- #include <linux/delay.h>
- #include <asm/uaccess.h>
- #include <asm/io.h>
- /*
- *中断事件标志,中断服务程序将它置1,在gpio_drv_read将它清0
- */
- static volatile int ev_press = 0;
- /*
- *异步结构体指针 用于读
- */
- static struct fasync_struct *b_async;
- /*
- *中断引脚描述结构体
- */
- struct pin_desc_s{
- unsigned int pin;
- unsigned int key_val;
- unsigned int irq;
- };
- static unsigned char key_val;
- struct pin_desc_s pin_desc[5] = {
- {MXS_PIN_TO_GPIO(PINID_LCD_ENABLE),0x03,}, /* IO1 rain GPIO1_31 */
- {MXS_PIN_TO_GPIO(PINID_LCD_HSYNC),0x05,}, /* IO2 windspeed GPIO1_29*/
- {MXS_PIN_TO_GPIO(PINID_LCD_DOTCK),0x0A,}, /* 机箱门 */
- {MXS_PIN_TO_GPIO(PINID_AUART3_RX),0x07,}, /* key1 GPIO3_12 */
- {MXS_PIN_TO_GPIO(PINID_AUART3_TX),0x09,}, /* key2 GPIO3_13 */
- };
- static DECLARE_MUTEX(b_lock);
- static DECLARE_WAIT_QUEUE_HEAD(b_waitq);
- static irqreturn_t b_irq(int irq, void *dev_id)
- {
- struct pin_desc_s * pindesc = (struct pin_desc_s *)dev_id;
- unsigned int pinval;
- pinval = gpio_get_value(pindesc->pin);
- if (pinval)
- {
- key_val = 1;
- }
- else
- {
- key_val = pindesc->key_val;
- }
- ev_press = 1;
- wake_up_interruptible(&b_waitq); //唤醒等待队列里面的进程
- kill_fasync(&b_async, SIGIO, POLL_IN); //异步通知
- //printk("interrupt occur..........\n");
- return IRQ_RETVAL(IRQ_HANDLED);
- }
- static int gpio_drv_open(struct inode *inode, struct file *file)
- {
- int iRet[5]={0};
- int i = 0;
- if (file->f_flags & O_NONBLOCK)
- {
- if (down_trylock(&b_lock))
- return -EBUSY;
- }
- else
- {
- down(&b_lock);
- }
- for(i = 0; i < 5; i++)
- {
- gpio_direction_input((pin_desc[i]).pin);
- (pin_desc[i]).irq = gpio_to_irq((pin_desc[i]).pin);
- if ((pin_desc[i]).irq)
- disable_irq((pin_desc[i]).irq);
- set_irq_type((pin_desc[i]).irq, IRQF_TRIGGER_FALLING); //下降沿中断
- iRet[i] = request_irq((pin_desc[i]).irq, buttons_irq, IRQF_SHARED, "gpio_int", &pin_desc[i]);
- if (iRet[i] != 0){
- printk("request irq failed!! ret: %d irq:%d \n", iRet[i],(pin_desc[i]).irq);
- return -EBUSY;}
- }
- return 0;
- }
- ssize_t gpio_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
- {
- if (size != 1)
- return -EINVAL;
- if (file->f_flags & O_NONBLOCK)
- {
- if (!ev_press)
- return -EAGAIN;
- }
- else
- {
- wait_event_interruptible(b_waitq, ev_press);
- }
- copy_to_user(buf, &key_val, 1);
- ev_press = 0;
- return 1;
- }
- int gpio_drv_close(struct inode *inode, struct file *file)
- {
- int i = 0;
- for( i = 0; i < 5; i++)
- {
- free_irq((pin_desc[i]).irq, &pin_desc[i]);
- }
- up(&b_lock);
- return 0;
- }
- static int gpio_drv_fasync (int fd, struct file *filp, int on)
- {
- printk("driver: gpio_drv_successful\n");
- return fasync_helper (fd, filp, on, &b_async);
- }
- static struct file_operations gpio_drv_fops = {
- .owner = THIS_MODULE,
- .open = gpio_drv_open,
- .read = gpio_drv_read,
- .release = gpio_drv_close,
- .fasync = gpio_drv_fasync,
- };
- static struct miscdevice b_miscdev =
- {
- .minor = MISC_DYNAMIC_MINOR,
- .name = "magic-gpio",
- .fops = &gpio_drv_fops,
- };
- static int __init gpio_drv_init(void)
- {
- int iRet=0;
- printk("gpio_miscdev module init!\n");
- iRet = misc_register(&b_miscdev);
- if (iRet) {
- printk("register failed!\n");
- }
- return 0;
- }
- static void __exit gpio_drv_exit(void)
- {
- printk("gpio_miscdev module exit!\n");
- misc_deregister(&b_miscdev);
- }
- module_init(gpio_drv_init);
- module_exit(gpio_drv_exit);
- MODULE_AUTHOR("HEHAI & RK");
- MODULE_LICENSE("Dual BSD/GPL");
- MODULE_DESCRIPTION("gpio interrupt module");
首先还是先从init函数来总结:该驱动是一混杂设备驱动模型来写的,这个主要是借鉴网上的好多资料都是一这种模式来写的,Linux里面misc混杂设备驱动的主设备号是为10的驱动设备,init模块首先是用 misc_register()函数注册一个一个混杂设备驱动,参数一个混杂设备驱动里面非常重要的一个数据结构 struct miscdevice,下面把原型贴出来:
- struct miscdevice {
- int minor;
- const char *name;
- const struct file_operations *fops;
- struct list_head list;
- struct device *parent;
- struct device *this_device;
- const char *nodename;
- mode_t mode;
- };
当然我上面的驱动代码只初始化了前面的关键三项:
- static struct miscdevice b_miscdev =
- {
- .minor = MISC_DYNAMIC_MINOR,
- .name = "magic-gpio",
- .fops = &gpio_drv_fops,
- };
这里先说说 .minor这个成员:定义次设备号的,这里使用了一个MISC_DYNAMIC_MINOR宏! 这个宏的意思就是动态分配次设备号!而且这个次设备号不会超过64!实现的方法比较巧妙!这里贴出一篇相关的文章:
http://blog.csdn.NET/yongan1006/article/details/6778285 这个可以研究一下,还比较有意思!
剩下的两个name 和 fops成员对驱动开发来说就最熟悉不过了!驱动的名字和驱动的接口函数这里就不说了!
注册混杂设备驱动后就是接口函数的表演了!
这里和内核硬件相关的就是struct pin_desc_s 结构了,硬件的初始化工作比较简单,放在open函数里面了!
- struct pin_desc_s pin_desc[5] = {
- {MXS_PIN_TO_GPIO(PINID_LCD_ENABLE),0x03,}, /* IO1 rain GPIO1_31 */
- {MXS_PIN_TO_GPIO(PINID_LCD_HSYNC),0x05,}, /* IO2 windspeed GPIO1_29*/
- {MXS_PIN_TO_GPIO(PINID_LCD_DOTCK),0x0A,}, /* 机箱门 */
- {MXS_PIN_TO_GPIO(PINID_AUART3_RX),0x07,}, /* key1 GPIO3_12 */
- {MXS_PIN_TO_GPIO(PINID_AUART3_TX),0x09,}, /* key2 GPIO3_13 */
- };
这里把好几个gpio接口都放到这一个里面了!都是后边加进去的!上面的是直接根据文档在内核头文件中找到GPIO引脚对应的宏定义的!后边是给GPIO设置的键值!就是当应用程序收到一个signal后,根据读取到的键值来区分是哪一个GPIO发生了中断或是有信号传过来!看看open函数:
- static int gpio_drv_open(struct inode *inode, struct file *file)
- {
- int iRet[5]={0};
- int i = 0;
- if (file->f_flags & O_NONBLOCK)//非阻塞
- {
- if (down_trylock(&b_lock))
- return -EBUSY;
- }
- else
- {
- down(&b_lock);
- }
- for(i = 0; i < 5; i++)
- {
- gpio_direction_input((pin_desc[i]).pin);//设置对应的GPIO输入
- (pin_desc[i]).irq = gpio_to_irq((pin_desc[i]).pin);//把GPIO对应的pin值转换为相应的IRQ值并返回
- if ((pin_desc[i]).irq)
- disable_irq((pin_desc[i]).irq);//先关闭中断并等待中断处理完
- set_irq_type((pin_desc[i]).irq, IRQF_TRIGGER_FALLING); //设置下降沿中断
- iRet[i] = request_irq((pin_desc[i]).irq, b_irq, IRQF_SHARED, "gpio_int", &pin_desc[i]);
- if (iRet[i] != 0){
- printk("request irq failed!! ret: %d irq:%d \n", iRet[i],(pin_desc[i]).irq);
- return -EBUSY;}
- }
- return 0;
- }
这里可以详细了解一下关于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函数:
- static irqreturn_t b_irq(int irq, void *dev_id)
- {
- struct pin_desc_s * pindesc = (struct pin_desc_s *)dev_id;
- unsigned int pinval;
- pinval = gpio_get_value(pindesc->pin);
- if (pinval)
- {
- key_val = 1;
- }
- else
- {
- key_val = pindesc->key_val;
- }
- ev_press = 1;
- wake_up_interruptible(&b_waitq); //唤醒等待队列里面的进程
- kill_fasync(&b_async, SIGIO, POLL_IN); //异步通知
- //printk("interrupt occur..........\n");
- return IRQ_RETVAL(IRQ_HANDLED);
- }
其中上面的b_waitq是这样定义的:
- 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函数内部实现:
- int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
- {
- struct fasync_struct *fa, **fp;
- struct fasync_struct *new = NULL;
- int result = 0;
- if (on) {
- new = kmem_cache_alloc(fasync_cache, GFP_KERNEL);
- if (!new)
- return -ENOMEM;
- }
- write_lock_irq(&fasync_lock);
- for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) {
- if (fa->fa_file == filp) {
- if(on) {
- fa->fa_fd = fd; //区分向谁发
- kmem_cache_free(fasync_cache, new);
- } else {
- *fp = fa->fa_next;
- kmem_cache_free(fasync_cache, fa);
- result = 1;
- }
- goto out;
- }
- }
- if (on) {
- new->magic = FASYNC_MAGIC;
- new->fa_file = filp;
- new->fa_fd = fd;
- new->fa_next = *fapp;
- *fapp = new;
- result = 1;
- }
- out:
- write_unlock_irq(&fasync_lock);
- return result;
- }
kill_fasync函数里面的b_async参数:struct fasync_struct类型定义:
- struct fasync_struct {
- int magic;
- int fa_fd;
- struct fasync_struct *fa_next;
- struct file *fa_file;
- };
这个参数在下面中也被调用:实现的fasync成员函数
- static int gpio_drv_fasync (int fd, struct file *filp, int on)
- {
- printk("driver: gpio_drv_successful\n");
- return fasync_helper (fd, filp, on, &b_async);
- }
这也是应用程序和内核之间传参的一个关键:
要实现传参,我们需要把一个结构体struct fasync_struct添加到内核的异步队列中,这个结构体用来存放对应设备文件的信息(如fd, filp)并交给内核来管理。一但收到信号,内核就会在这个所谓的异步队列头找到相应的文件(fd),并在filp->owner中找到对应的进程PID,并且调用对应的sig_handler了。
关于剩下的程序中用到的down() 、up() 还有 DECILARE_MUTEX(b_lock)这里简单的用到了信号量的两个简单的操作,主要是用于保护临界资源,保证中断不被丢失!
剩下的read和close都比较简单,驱动里面的函数基本都是对应的,close里面一把是释放所有申请的资源!这也是模块化驱动的一个好处!虽然这个驱动很简单!但是要仔细深究起来,里面所涉及的知识量也不小!上面也只是简单的分析总结一下!做个笔记算是对自己的一个提高,也别人在参考的时候能有一点点的帮助!
最近住的地方没网!感觉好长时间没写博客了!现在业余时间看Linux驱动设备详解,哈哈,比一年多前看的效果好多了,至少书上的好多知识多多少少都接触过!而且看起来还比较有收获,就是看了就忘!看来总结还是相当重要的!好记性不如烂笔头!
Linux的fasync驱动异步通知详解【转】的更多相关文章
- 迅为4412开发板Linux驱动教程——总线_设备_驱动注册流程详解
本文转自:http://www.topeetboard.com 视频下载地址: 驱动注册:http://pan.baidu.com/s/1i34HcDB 设备注册:http://pan.baidu.c ...
- Linux 进程间通信 --- 信号通信 --- signal --- signal(SIGINT, my_func); --- 按键驱动异步通知(转)
信号 ( signal ) 机制是 UNIX 系统中最为古老的进程间通信机制,很多条件可以产生一个信号. 信号的产生: 1,当用户按下某些按键时,产生信号. 2,硬件异常产生信号:除数为 0 ,无效 ...
- Linux内核开发之异步通知与异步I/O(一)
“小王,听说过锦上添花吧..”我拍拍下王的头说. “还锦上添花你,为你上次提的几个东东,我是头上长包..”小王气愤地瞪着我. “啊,为啥这样呢,本来还特意拒绝了MM的月份,抽出时间打算给你说点高级的东 ...
- linux mount命令参数及用法详解
linux mount命令参数及用法详解 非原创,主要来自 http://www.360doc.com/content/13/0608/14/12600778_291501907.shtml. htt ...
- Linux中/proc目录下文件详解
转载于:http://blog.chinaunix.net/uid-10449864-id-2956854.html Linux中/proc目录下文件详解(一)/proc文件系统下的多种文件提供的系统 ...
- linux dmesg命令参数及用法详解(linux显示开机信息命令)
linux dmesg命令参数及用法详解(linux显示开机信息命令) http://blog.csdn.net/zhongyhc/article/details/8909905 功能说明:显示开机信 ...
- 莱特币ltc在linux下的多种挖矿方案详解
莱特币ltc在linux下的多种挖矿方案详解 4.0.1 Nvidia显卡Linux驱动Nvidia全部驱动:http://www.nvidia.cn/Download/index.aspx?lang ...
- Linux C 语言之 Hello World 详解
目录 Linux C 语言之 Hello World 详解 第一个 C 语言程序 程序运行原理 编译,链接 运行时 链接库 编译器优化 Hello World 打印原理 stdout, stdin 和 ...
- Linux中/proc目录下文件详解(转贴)
转载:http://www.sudu.cn/info/index.php?op=article&id=302529 Linux中/proc目录下文件详解(一) 声明:可以自由转载本文, ...
随机推荐
- 手把手教你编写Logstash插件
使用过Logstash的朋友都知道,它强大的插件生态几乎覆盖了所有的开源框架.从基本的http.tcp.udp.file,到强大的kafa.redis.ganglia,还有丰富的解析工具,比如date ...
- rabbitmq使用心得
因为公司项目需要使用消息中间件,实现相关业务的异步处理,所有选用了rabbitmq.通过看文档,爬过一个一个坑,终于还是实现了相关功能. 直接上配置文件: <?xml version=" ...
- 3D滚动下拉菜单-简直不要太任性
预览 先看看最终效果 简介 由来 最初看到这个是在14年5月,猛戳这里:妙味官网,觉得非常炫.想要做出来,所以就开始学习web. 那时候是做c/s的,也因为这个走上了b/s之路,(゚Д゚≡゚Д゚) 现 ...
- Xamarin.Android 应用程序配置
* 在 Xamarin 中 Android 清单文件的内容一般不通过手动编辑,而是由编译器根据 项目属性设置 和 一系列 特性类 自动生成 1. 应用程序在android启动器中显示的名称设置: 主活 ...
- 浅谈iOS开发中方法延迟执行的几种方式
Method1. performSelector方法 Method2. NSTimer定时器 Method3. NSThread线程的sleep Method4. GCD 公用延迟执行方法 - (vo ...
- Xcode清除缓存等
Xcode出现一些错误的时候,有时候不是代码的问题,需要清理一下Xcode的缓存或者项目的Product等: 1. Product清理 1.1 Product-Clean 1.2 Product- ...
- selenium RC+JAVA 运行所遇到的问题
1.报错一 Failed to start new browser session: java.lang.RuntimeException: Firefox 3 could not be found ...
- Linq to Sql : 并发冲突及处理策略
原文:Linq to Sql : 并发冲突及处理策略 1. 通过覆盖数据库值解决并发冲突 try { db.SubmitChanges(ConflictMode.ContinueOnConflict) ...
- Javascript模板引擎:Hogan
hogan.js是一个开源前端模板引擎,无逻辑的设计,简单好用,性能也不错. 使用 引入hogan.js,下载链接:http://www.bootcdn.cn/hogan.js/,然后通过hogan. ...
- 用Netty开发中间件:高并发性能优化
用Netty开发中间件:高并发性能优化 最近在写一个后台中间件的原型,主要是做消息的分发和透传.因为要用Java实现,所以网络通信框架的第一选择当然就是Netty了,使用的是Netty 4版本.Net ...