/** * 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之外的任何一个特定有效的信号。第二个
#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) ; }为了在用户空间中能处理一个设备释放的信号,需要完成下面三个步骤:
结构体: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结构体指针的指针
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,
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()函数将文件从异步通
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") ; } }
原文地址:http://blog.csdn.net/android_hasen/article/details/40977779