linux设备驱动开发学习之旅--异步通知

来源:互联网 发布:求婚大作战日剧知乎 编辑:程序博客网 时间:2024/05/20 17:40
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Author:hasen 
  3.  * 参考 :《linux设备驱动开发详解》 
  4.  * 简介:android小菜鸟的linux 
  5.  *           设备驱动开发学习之旅 
  6.  * 主题:异步通知 
  7.  * Date:2014-11-05 
  8.  */  
一、异步通知的概念和作用
阻塞和非阻塞访问、poll()函数提供了较好地解决设备访问的机制,但是如果有了异步通知整套机制就更
加完整了。
异步通知的意思是:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这
一点非常类似于硬件上“中断”的概念,比较准确的称谓是“信号驱动的异步I/O”。信号时在软件层次上对中断机制
的一种模拟,进程收到信号和处理器收到中断可以说是一样的。信号是异步的,进程不知道信号什么时候到达。
阻塞I/O意味着一直等待设备可访问后再访问,非阻塞I/O使用poll()意味着查询设备是否可访问,而异步
通知则意味着设备通知自身可访问,实现了异步I/O。由此可见,这几种方式I/O可以互为补充。

二、Linux异步通知编程
1、Linux信号
            使用信号进行进程间通信(IPC)是UNIX中一种传统机制,LInux也支持这种机制。在Linux中,异步通知
使用信号来实现,Linux中可用的信号及其定义如下表:

Linux信号信号值含义SIGHUP1挂起SIGINT2终端中断SIGQUIT3终端退出SIGILL4无效命令SIGTRAP5跟踪陷阱SIGIOT6IOT陷阱SIGBUS7BUS错误SIGFPE8浮点异常SIGKILL9强行终止(不能被捕捉或忽略)SIGSR110用户定义的信号1SIGSEGV11无效的内存段处理SIGUSR212用户定义的信号2SIGPIPE13半关闭管道的写操作已经发生SIGALRM14计时器到期SIGTERM15终止SIGSTKFLT16堆栈错误SIGCHLD17子进程已经停止或退出SIGCONT18如果停止了,继续执行SIGSTOP19停止执行(不能被捕获或忽略)SIGTSTP20终端停止信号SIGTTIN21后台进程需要从终端读取输入SIGTTOU22后台进程需要向从终端写出SIGURG23紧急的套接字事件SIGXCPU24超额使用CPU分配的时间SIGXFSZ25文件尺寸超额SIGVTALRM26虚拟时钟信号SIGPROF27时钟信号描述SIGWINCH28窗口尺寸变化SIGIO29I/OSIGPWR30断电重启

2、信号的接收
           在用户程序中,为了捕获信号,可以使用signal()函数来设置对应信号的处理函数:
           void (*signal (int signum,void(*handler))(int))(int) ;
           该函数原型较难理解,它可以分解为:
           typedef void (*sighandler_t) (int) ;
           sighandler_t signal(int signum,sighandler_t sighandler) ;
第一个参数指定信号的值,第二个参数指定针对前面信号值的处理函数,若为SIG_IGN,表示忽略该信号;
若为SIG_DFL,表示采用系统默认方式处理信号;若为用户自定义函数,信号捕捉到后,将会执行该函数。
           如果signal()调用成功,它返回最后一为信号signum绑定的处理函数handler的值,失败返回SIG_ERR。
           在进程执行时,按下“Ctrl+c”将向其发出SIGINT信号,kill正在运行的进程将向其发送SIGTERM信号。
示例:进程捕捉SIGINT和SIGTERM信号并输出信号值

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void sigterm_handler(int signo)  
  2. {  
  3.     printf("Have caught sig NO.%d\n",signo) ;  
  4.     exit(0) ;  
  5. }  
  6. int main(void)  
  7. {  
  8.     signal(SIGINT,sigterm_handler) ;  
  9.     signal(SIGTERM,sigterm_handler) ;  
  10.     while(1) ;  
  11.       
  12.     return 0 ;  
  13. }  
除了signal()函数之外,sigaction()函数也可用于改变进程接收到特点信号的行为,它的原型是:
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int sigaction(int signum,const struct sigaction *act,struct sigaction oldact) ;  
           该函数的第一个参数为信号的值,可以为除SIGKILL及SIGSTOP之外的任何一个特定有效的信号。第二个
参数是指向结构体sigaction的一个实例指针,在结构体sigaction的实例中,指定了对特定信号的处理函数,若为
空,则进程会以缺省方式对信号进行处理。第三个参数指向的对象用来保存原来对相应信号的处理函数,可指定
oldact为NULL,当后面两个参数都为NULL时,那么该函数可用以检查信号的有效性。
           下面是使用信号实现异步通知的实例,它通过signal(SIGIO,input_handler)对标准输入文件描述符
STDIN_FILENO启动信号机制。用户输入后,应用程序将接收到SIGNO信号,处理函数input_handler()将被调用。
示例:使用信号实现异步通知的应用程序
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include <sys/types.h>  
  2. #include <sys/stat.h>  
  3. #include <stdio.h>  
  4. #include <fcntl.h>  
  5. #include <signal.h>  
  6. #include <unistd.h>  
  7. #define MAX_LEN 100 ;  
  8.   
  9. void input_handler(int num)  
  10. {  
  11.     char data[MAX_LEN] ;  
  12.     int len ;  
  13.     /*读取并输出STDIN_FILENO上的输入*/  
  14.     len = read(STDIN_FILENO,&data,MAX_LEN) ;  
  15.     data[len] = 0 ;  
  16.     printf("input available:%s\n,data") ;  
  17. }  
  18. main()  
  19. {  
  20.     int oflags ;  
  21.       
  22.     /*启动信号驱动机制*/  
  23.     signal(SIGIO,input_handler) ;//input_handler为信号处理函数  
  24.     fcntl(STDIN_FILENO,F_SETOWN,getpid()) ;//设备本进程为文件拥有者  
  25.     oflags = fcntl(STDIN_FILENO,F_GETFL) ;//得到文件标志  
  26.     fcntl(STDIN_FILENO,FSETFL,oflags|FASYNC) ;//为文件添加FASYNC标志(异步通知机制)  
  27.     /*最后进入一个死循环,仅为保持进程不终止,如果程序中没有这个死循环会立即执行完毕*/  
  28.     while(1) ;  
  29. }  
           为了在用户空间中能处理一个设备释放的信号,需要完成下面三个步骤:
           (1)通过F_SETOWN IO控制命令设置文件的拥有者为本进程,这样从设备驱动发出的信号才能被本进
程接收到。
           (2)通过F_SETFL IO控制命令设置设备文件支持FASYNC,即异步通知模式。
           (3)通过signal()函数连接信号和信号处理函数。
3、信号的释放
           在设别驱动和应用程序的异步通知交互中,仅仅在应用程序端捕获信号是不够的,因为信号的源头
在设备驱动端,因此,在设备驱动中添加信号释放的代码。
           为了使设别支持异步通知机制,驱动程序设计3项工作:
           (1)支持F_SETOWN命令,能在这个控制命令处理中设置filp->f_owner为对应进程ID,不过,内核已
经处理了此项工作,设备驱动无需处理。
           (2)支持F_SETFL命令的处理,每当FASYNC标志改变时,驱动程序中的fasync()函数将得以执行。因
此,驱动中应该实现fasync()函数。
           (3)在设备资源可获得时,调用kill_fasync()函数激发相应的信号。


           驱动中的工作与用户空间中的是一一对应的,下图是异步通信过程中用户空间和设备驱动的交互。

           设备驱动中的异步通知编程,主要用到两个函数和一个结构体
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. 结构体:fasync_struct   
  2. 方法:  
  3.     (1)处理FASYNC标志变更的  
  4.         int fasync_helper(int fd,struct file *flip,int mode,struct fasync_struct **fa);  
  5.     (2)释放信号用的函数  
  6.         void kill_fasync(struct fasync_struct **fa ,int sig,int band);  
  7.            和其他的设备驱动一样,将fasync_struct结构体指针放在设备结构体中最合适。  
示例:支持异步通知的设备结构体模板
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. struct xxx_dev{  
  2.     struct cdev cdev ;/*cdev结构体*/  
  3.     ...  
  4.     struct fasync_struct *fasync_queue ;/*异步结构体指针*/  
  5. }  
           在设备驱动的fasync()函数中,只要简单地将该函数的3个参数以及fasync_struct结构体指针的指针
作为第4个参数传入fasync_helper()函数即可。
示例:支持异步通知的设备驱动fasync()函数模板
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. static int xxx_fasync()  
  2. {  
  3.     struct xxx_dev *dev = filp->private_data;  
  4.     return fasync_helper(fd,filp,mode,&dev->async_queue) ;  
  5. }  
           在设备资源可以获得时,应该调用kill_fasync()释放SIGIO信号,可读时第三个参数设置为POLL_IN,
可写时第三个参数设置为POLL_OUT 。 
示例:支持异步通知的设备驱动信号释放。
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. static ssize_t xxx_write(struct file *filp,const char __user buf,  
  2.         size_t count,loff_t *f_pos)  
  3. {  
  4.     struct xxx_dev *dev = filp->private_data ;  
  5.     ...  
  6.     /*产生异步读信号*/  
  7.     if(dev->async_queue)  
  8.         kill_fasync(&dev->async_queue,SIGIO,POLL_IN) ;  
  9.     ...  
  10. }  
           最后在文件关闭时,即在设备的release()函数中,应调用设备驱动的fasync()函数将文件从异步通
知的列表中删除。
示例:支持异步通知的设备驱动release()函数模板    
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. static ssize_t xxx_release(struct inode *inode,struct file filp)  
  2. {  
  3.    struct xxx_dev *dev = filp->private_data ;  
  4.    ...  
  5.    /*产生异步读信号*/  
  6.    if(dev->async_queue)  
  7.     kill_fasync(&dev->async_queue,SIGIO,POLL_IN) ;  
  8.    ...  
  9. }  
           下面是增加异步通知机制的设备驱动和验证代码

在globalfifo驱动中增加异步通知

[cpp] view plaincopy
  1. int GLOBALFIFO_SIZE = 100 ;  
  2.   
  3. /*增加异步通知后的globalfifo设备结构体*/  
  4. struct globalfifo_dev {  
  5.     struct cdev cdev ;/*cdev结构体*/  
  6.     unsigned int current_len ;/*fifo有效数据长度*/  
  7.     unsigned char mem[GLOBALFIFO_SIZE];/*全局内存*/  
  8.     struct semaphore sem ;/*并发控制用的信号量*/  
  9.     wait_queue_head_t r_wait ;/*阻塞读用的等待队列头*/  
  10.     wait_queue_head_t w_wait ;/*阻塞写用的等待队列头*/  
  11.     struct fasync_struct *async_queue ;/*异步结构体指针*/    
  12. } ;  
  13.   
  14. /*支持异步通知的globalfifo设备驱动的fasync()函数*/  
  15. static int globalfifo_fasync(int fd,struct file *flip,int mode)  
  16. {  
  17.     struct globalfifo_dev *dev = flip->private_data ;  
  18.     return fasync_helper(fd,flip,mode,&dev->async_queue) ;  
  19. }  
  20.   
  21. /*支持异步通知的globalfifo设备驱动写函数*/  
  22. static ssize_t globalfifo_write(struct file *filp,const char __user *buf,  
  23.     size_t count,loff_t *ppos)  
  24. {  
  25.     struct globalfifo_dev *dev = filp->private_data ;/*获得设备结构体指针*/  
  26.     int ret ;  
  27.     DECLARE_WAITQUEUE(wait,current) ;/*定义等待队列*/  
  28.     down(&dev->sem) ;/*获取信号量*/  
  29.     add_wait_queue(&dev->w_wait,&wait) ;/*进入写等待队列头*/  
  30.     /*等待fifo非满*/  
  31.     if(dev->current_len == GLOBALFIFO_SIZE) {  
  32.         if(filp->f_flag & O_NONBLOCK ){/*如果是非阻塞访问*/  
  33.             ret = -EAGAIN ;  
  34.             goto out ;  
  35.         }  
  36.         __set_current_state(TASK_INTERRPTIBLE) ;/*改变进程状态为睡眠*/  
  37.         up(&dev->sem) ;  
  38.           
  39.         schedule() ;/*调度其他进程执行*/  
  40.         if(signal_pending(current)){ /*如果是因为信号唤醒*/  
  41.             ret = -ERESTARTSYS ;  
  42.             goto out2 ;  
  43.         }  
  44.         down(&dev->sem) ;/*获取信号量*/  
  45.     }  
  46.       
  47.     /*从用户空间拷贝到内核空间*/  
  48.     if(count >GLOBALFIFO_SIZE - dev->current_len)  
  49.         count = GLOBALFIFO_SIZE - dev->current_len ;  
  50.     if(copy_from_user(dev->mem + dev->current_len , buf ,count)){  
  51.         ret = -EFAULT ;  
  52.         goto out ;  
  53.     }else{  
  54.         dev->current_len += count ;  
  55.         printk(KERN_INFO "writeen %d bytes(s),current_len:%d\n",count,dev->current_len) ;  
  56.         wake_up_interruptible(&dev->r_wait) ;/*唤醒读等待队列*/  
  57.         /*产生异步读信号*/  
  58.         if(dev->async_queue)  
  59.             /*释放信号,可写时为POLL_OUT,可读时为POLL_IN*/  
  60.             kill_fasync(&dev->async_queue,SIGIO,POLL_IN) ;  
  61.         ret = count ;  
  62.     }  
  63.     out: up(&dev->sem) ;/*释放信号量*/  
  64.     out2 :remove_wait_queue(&dev->w_wait,&wait) ;  
  65.     set_current_state(TASK_RUNNING) ;  
  66.     return ret ;  
  67. }  
  68.   
  69. /*增加异步通知的globalfifo设备驱动release()函数*/  
  70. int globalfifo_release(struct inode *inode ,struct file *filp )   
  71. {  
  72.     /*将文件从异步通知列表中删除*/  
  73.     globalfifo_fasync(-1,filp,0) ;  
  74.     return 0 ;  
  75. }  

在用户空间验证globalfifo的异步通知
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include <xxx.h>  
  2.   
  3. void input_handler(int signum)  
  4. {  
  5.     printf("receive a signal from globalfifo,signum:%d\n",signum) ;  
  6. }  
  7.   
  8. void main()  
  9. {  
  10.     int fd  , oflags ;  
  11.     fd = open("/dev/globalfifo",O_RDWR ,S_IRUSR|S_IWUSR) ;  
  12.     if(fd != -1){  
  13.         /*启动信号驱动机制*/  
  14.         signal(SIGIO,input_handler);/*让input_handler处理SIGIO信号*/  
  15.         fcntl(fd,F_SETOWN,getpid()) ;/*设置当前进程为文件所有者*/  
  16.         oflags = fcntl(fd,F_GETFL) ; /*得到文件的所有标志*/  
  17.         fcntl(fd.F_SETFL,oflags | FASYNC) ;/*给文件加上FASYNC标志,使支持异步通知模式*/  
  18.         while(1){  
  19.             sleep(100) ;  
  20.         }  
  21.     }else{  
  22.         printf("open driver failure\n") ;  
  23.     }  
  24. }  
0 0