我们前面介绍了阻塞与非阻塞的相关操作,其中poll()函数提供了较好的解决设备访问的基址
,但是如果有了异步通知的整套基址就更加完整了。
这次我就和大家一起来探讨一下Linux驱动开发中的异步操作
异步操作的概念与作用:
概念:一旦设备准备就绪,则主动通知应用程序,这样应用程序就不需要查询设备状态了
,这一点与硬件上的"中断"概念非常相似
在原理上一个进程收到一个信号与处理器收到一个中断请求时一样的。而所谓
异步通知就是指:一个进程不必通过任何操作来等待信号的到达,进程也不知道信号shi什么时候到达
这与阻塞及非阻塞I/O区别很大,阻塞I/O意味着一直等待到设备可以访问,而非阻塞I/O中使用poll()意味着需要查询设备
是否可以访问,而异步通知则意味着设备通知自身可
Linux异步通知编程:
Linux信号:
Linux中异步通知通过信号来实现
Linux信号表:见下图
除了SIGSTOP和SIGKILL两个信号外(这两个信号一定会被捕获),进程能够忽略或捕获其他的全部
信号,一个信号被捕获的意思是当一个信号到达时有相应的代码处理它,如果一个信号没有被这个进程锁捕获,
内核将采用默认处理
信号的接收:
在用户空间,为了捕获信号,可以使用signal()函数来设置对应信号的处理函数:
void (*signal (int signum,void (*handler))(int))(int);
通常把该函数原型分解为:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler));
第一个参数指定信号的值,
第二个参数指定对前面信号值的处理函数,若为SIG_IGN,表示忽略该信号。
若为SIG_DFL表示采用系统默认方式处理信号,若为用户自定义函数,则信号被捕捉到
后此函数将被执行
函数返回值:如果signal()调用成功,它返回最后一次为信号signum绑定的处理函数handler值,失败
则返回SIG_ERR
signal()捕获信号范例:
void sigterm_handler(int signo)
{
printf("Have caught sig N.O. %d\n",signo);
exit(0);
}
int main(void)
{
/*捕捉SIGINT信号(Ctrl+c)*/
signal(SIGINT,sigterm_handler);
/*捕捉SIGTERM信号,kill正在运行的程序将发出SIGTERM信号*/
signal(SIGTERM,sigterm_handler);
while(1);
return 0;
}
sigaction()函数与signal()函数功能类似
int sigaction(int signum,const struct sigaction t*act,struct sigaction *oldact)
参数介绍:
signum:为信号值
act:为指向结构体sigaction的一个指针,在结构体signaction的实例中,指定对特殊信号的处理函数
,若为空则进程会以缺省的方式对信号进行处理
oldact:用来保护原来的信号处理函数
此函数使用的一个小技巧:把第二三个参数设为NULL,那么该函数
可以用于检查信号的有效性
在用户空间对设备释放信号的处理:
(1).通过F_SETOWN IO控制命令设置文件的拥有者为本进程,这样从驱动发出的信号才能被本进程
接收
fcntl(STDIN_FILENO,F_SETOWN,getpid());
(2).通过F_SETFLIO控制命令设置设备文件支持FASYNC,即异步通知模式
oflags = fcntl(STDIN_FILENO,F_GETFL);//获取原有模式
fcntl(STDIN_FILENO,F_SETEL,oflags | FASYNC);//设置异步通知模式
(3).通过signal()函数连接信号和信号处理函数
以上介绍的两个函数都是在用户空间对信号进行处理的函数,下面介绍一下信号处理的
驱动程序设计
首先介绍一下一个函数和一个结构体:
int fasync_helper(int fd,struct file*filp,int mode,struct fasync_struct **fa)
//此函数与用来处理FASYNC标识的变更,简单的说可以用来设置设备文件支持FASYNC模式,执行此函数后,通过
fa就可以得知当前设备资源是否可以获得
结构体:fasync_struct,对于此结构体的对象你不需要深入了解,这里你只需要知道,再执行上述函数
后,一旦资源可获得,此结构体不为空,从下面的程序中你也可以看出
信号的释放:
在设备驱动和应用程序的异步通知交互中,仅仅在应用程序端捕获信号是不够的,因为信号的源头在
设备的驱动端,应该在合适的时候让设备驱动释放信号,在设备驱动程序中增加释放信号的相关代码
支持设备异步通知机制的驱动程序编写:
(1) 支持F_SETOWN命令,能在这个控制命令处理中设置filp->f_owner为对应进程ID(此工作已由内核实现)
(2) 支持F_SETEL命令的处理,每当FASYNC标识改变时,驱动程序的fasync()函数将得以执行。此函数需要在驱动程序中实现
static int xxx_fasync(int fd,struct file *filp,int mode)
{
struct xxx_dev *dev = filp->private_data;
return fasync_helper(fd,filp,mode,&dev->async_queue);
}
(3)在设备资源可获得时,调用kill_fasync()函数激发响应信号
例如:
static ss_size_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);
...
}
//驱动程序中的这三步操作正好对应于用户空间的那三步操作
具体如图所示: