Hasen的linux设备驱动开发学习之旅--异步通知
/** * Author:hasen * 参考 :《linux设备驱动开发详解》 * 简介:android小菜鸟的linux * 设备驱动开发学习之旅 * 主题:异步通知 * Date:2014-11-05 */一、异步通知的概念和作用
阻塞和非阻塞访问、poll()函数提供了较好地解决设备访问的机制,但是如果有了异步通知整套机制就更加完整了。
异步通知的意思是:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上“中断”的概念,比较准确的称谓是“信号驱动的异步I/O”。信号时在软件层次上对中断机制
的一种模拟,进程收到信号和处理器收到中断可以说是一样的。信号是异步的,进程不知道信号什么时候到达。
阻塞I/O意味着一直等待设备可访问后再访问,非阻塞I/O使用poll()意味着查询设备是否可访问,而异步通知则意味着设备通知自身可访问,实现了异步I/O。由此可见,这几种方式I/O可以互为补充。
二、Linux异步通知编程
1、Linux信号
使用信号进行进程间通信(IPC)是UNIX中一种传统机制,LInux也支持这种机制。在Linux中,异步通知
使用信号来实现,Linux中可用的信号及其定义如下表:
Linux信号 | ||
信号 | 值 | 含义 |
SIGHUP | 1 | 挂起 |
SIGINT | 2 | 终端中断 |
SIGQUIT | 3 | 终端退出 |
SIGILL | 4 | 无效命令 |
SIGTRAP | 5 | 跟踪陷阱 |
SIGIOT | 6 | IOT陷阱 |
SIGBUS | 7 | BUS错误 |
SIGFPE | 8 | 浮点异常 |
SIGKILL | 9 | 强行终止(不能被捕捉或忽略) |
SIGSR1 | 10 | 用户定义的信号1 |
SIGSEGV | 11 | 无效的内存段处理 |
SIGUSR2 | 12 | 用户定义的信号2 |
SIGPIPE | 13 | 半关闭管道的写操作已经发生 |
SIGALRM | 14 | 计时器到期 |
SIGTERM | 15 | 终止 |
SIGSTKFLT | 16 | 堆栈错误 |
SIGCHLD | 17 | 子进程已经停止或退出 |
SIGCONT | 18 | 如果停止了,继续执行 |
SIGSTOP | 19 | 停止执行(不能被捕获或忽略) |
SIGTSTP | 20 | 终端停止信号 |
SIGTTIN | 21 | 后台进程需要从终端读取输入 |
SIGTTOU | 22 | 后台进程需要向从终端写出 |
SIGURG | 23 | 紧急的套接字事件 |
SIGXCPU | 24 | 超额使用CPU分配的时间 |
SIGXFSZ | 25 | 文件尺寸超额 |
SIGVTALRM | 26 | 虚拟时钟信号 |
SIGPROF | 27 | 时钟信号描述 |
SIGWINCH | 28 | 窗口尺寸变化 |
SIGIO | 29 | I/O |
SIGPWR | 30 | 断电重启 |
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信号并输出信号值
void sigterm_handler(int signo) { printf("Have caught sig NO.%d\n",signo) ; exit(0) ; } int main(void) { signal(SIGINT,sigterm_handler) ; signal(SIGTERM,sigterm_handler) ; while(1) ; return 0 ; }除了signal()函数之外,sigaction()函数也可用于改变进程接收到特点信号的行为,它的原型是:
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()将被调用。
示例:使用信号实现异步通知的应用程序
#include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <fcntl.h> #include <signal.h> #include <unistd.h> #define MAX_LEN 100 ; void input_handler(int num) { char data[MAX_LEN] ; int len ; /*读取并输出STDIN_FILENO上的输入*/ len = read(STDIN_FILENO,&data,MAX_LEN) ; data[len] = 0 ; printf("input available:%s\n,data") ; } main() { int oflags ; /*启动信号驱动机制*/ signal(SIGIO,input_handler) ;//input_handler为信号处理函数 fcntl(STDIN_FILENO,F_SETOWN,getpid()) ;//设备本进程为文件拥有者 oflags = fcntl(STDIN_FILENO,F_GETFL) ;//得到文件标志 fcntl(STDIN_FILENO,FSETFL,oflags|FASYNC) ;//为文件添加FASYNC标志(异步通知机制) /*最后进入一个死循环,仅为保持进程不终止,如果程序中没有这个死循环会立即执行完毕*/ while(1) ; }为了在用户空间中能处理一个设备释放的信号,需要完成下面三个步骤:
(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()函数激发相应的信号。
驱动中的工作与用户空间中的是一一对应的,下图是异步通信过程中用户空间和设备驱动的交互。
设备驱动中的异步通知编程,主要用到两个函数和一个结构体
结构体:fasync_struct 方法: (1)处理FASYNC标志变更的 int fasync_helper(int fd,struct file *flip,int mode,struct fasync_struct **fa); (2)释放信号用的函数 void kill_fasync(struct fasync_struct **fa ,int sig,int band); 和其他的设备驱动一样,将fasync_struct结构体指针放在设备结构体中最合适。示例:支持异步通知的设备结构体模板
struct xxx_dev{ struct cdev cdev ;/*cdev结构体*/ ... struct fasync_struct *fasync_queue ;/*异步结构体指针*/ }在设备驱动的fasync()函数中,只要简单地将该函数的3个参数以及fasync_struct结构体指针的指针
作为第4个参数传入fasync_helper()函数即可。
示例:支持异步通知的设备驱动fasync()函数模板
static int xxx_fasync() { struct xxx_dev *dev = filp->private_data; return fasync_helper(fd,filp,mode,&dev->async_queue) ; }在设备资源可以获得时,应该调用kill_fasync()释放SIGIO信号,可读时第三个参数设置为POLL_IN,
可写时第三个参数设置为POLL_OUT 。
示例:支持异步通知的设备驱动信号释放。
static ssize_t xxx_write(struct file *filp,const char __user buf, size_t count,loff_t *f_pos) { struct xxx_dev *dev = filp->private_data ; ... /*产生异步读信号*/ if(dev->async_queue) kill_fasync(&dev->async_queue,SIGIO,POLL_IN) ; ... }最后在文件关闭时,即在设备的release()函数中,应调用设备驱动的fasync()函数将文件从异步通
知的列表中删除。
示例:支持异步通知的设备驱动release()函数模板
static ssize_t xxx_release(struct inode *inode,struct file filp) { struct xxx_dev *dev = filp->private_data ; ... /*产生异步读信号*/ if(dev->async_queue) kill_fasync(&dev->async_queue,SIGIO,POLL_IN) ; ... }下面是增加异步通知机制的设备驱动和验证代码
在globalfifo驱动中增加异步通知
int GLOBALFIFO_SIZE = 100 ; /*增加异步通知后的globalfifo设备结构体*/ struct globalfifo_dev { struct cdev cdev ;/*cdev结构体*/ unsigned int current_len ;/*fifo有效数据长度*/ unsigned char mem[GLOBALFIFO_SIZE];/*全局内存*/ struct semaphore sem ;/*并发控制用的信号量*/ wait_queue_head_t r_wait ;/*阻塞读用的等待队列头*/ wait_queue_head_t w_wait ;/*阻塞写用的等待队列头*/ struct fasync_struct *async_queue ;/*异步结构体指针*/ } ; /*支持异步通知的globalfifo设备驱动的fasync()函数*/ static int globalfifo_fasync(int fd,struct file *flip,int mode) { struct globalfifo_dev *dev = flip->private_data ; return fasync_helper(fd,flip,mode,&dev->async_queue) ; } /*支持异步通知的globalfifo设备驱动写函数*/ static ssize_t globalfifo_write(struct file *filp,const char __user *buf, size_t count,loff_t *ppos) { struct globalfifo_dev *dev = filp->private_data ;/*获得设备结构体指针*/ int ret ; DECLARE_WAITQUEUE(wait,current) ;/*定义等待队列*/ down(&dev->sem) ;/*获取信号量*/ add_wait_queue(&dev->w_wait,&wait) ;/*进入写等待队列头*/ /*等待fifo非满*/ if(dev->current_len == GLOBALFIFO_SIZE) { if(filp->f_flag & O_NONBLOCK ){/*如果是非阻塞访问*/ ret = -EAGAIN ; goto out ; } __set_current_state(TASK_INTERRPTIBLE) ;/*改变进程状态为睡眠*/ up(&dev->sem) ; schedule() ;/*调度其他进程执行*/ if(signal_pending(current)){ /*如果是因为信号唤醒*/ ret = -ERESTARTSYS ; goto out2 ; } down(&dev->sem) ;/*获取信号量*/ } /*从用户空间拷贝到内核空间*/ if(count >GLOBALFIFO_SIZE - dev->current_len) count = GLOBALFIFO_SIZE - dev->current_len ; if(copy_from_user(dev->mem + dev->current_len , buf ,count)){ ret = -EFAULT ; goto out ; }else{ dev->current_len += count ; printk(KERN_INFO "writeen %d bytes(s),current_len:%d\n",count,dev->current_len) ; wake_up_interruptible(&dev->r_wait) ;/*唤醒读等待队列*/ /*产生异步读信号*/ if(dev->async_queue) /*释放信号,可写时为POLL_OUT,可读时为POLL_IN*/ kill_fasync(&dev->async_queue,SIGIO,POLL_IN) ; ret = count ; } out: up(&dev->sem) ;/*释放信号量*/ out2 :remove_wait_queue(&dev->w_wait,&wait) ; set_current_state(TASK_RUNNING) ; return ret ; } /*增加异步通知的globalfifo设备驱动release()函数*/ int globalfifo_release(struct inode *inode ,struct file *filp ) { /*将文件从异步通知列表中删除*/ globalfifo_fasync(-1,filp,0) ; return 0 ; }在用户空间验证globalfifo的异步通知
#include <xxx.h> void input_handler(int signum) { printf("receive a signal from globalfifo,signum:%d\n",signum) ; } void main() { int fd , oflags ; fd = open("/dev/globalfifo",O_RDWR ,S_IRUSR|S_IWUSR) ; if(fd != -1){ /*启动信号驱动机制*/ signal(SIGIO,input_handler);/*让input_handler处理SIGIO信号*/ fcntl(fd,F_SETOWN,getpid()) ;/*设置当前进程为文件所有者*/ oflags = fcntl(fd,F_GETFL) ; /*得到文件的所有标志*/ fcntl(fd.F_SETFL,oflags | FASYNC) ;/*给文件加上FASYNC标志,使支持异步通知模式*/ while(1){ sleep(100) ; } }else{ printf("open driver failure\n") ; } }
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。