什么是异步通知?
异步通知类似于硬件上的“中断”,一旦设备就绪,由驱动程序主动通知应用程序,这样应用程序就不需要查询设备状态,而可以去干自己的事。通知采用信号的方式,所以可称为“信号驱动的异步I/O”。
为什么使用异步通知?
在一个进程中使用I/O多路复用可以同时监控多个I/O设备,但是这里有个问题,如果这些I/O设备都没有准备好的话,那进程就会阻塞休眠,这样就无法去干别的事了,所以就需要一种方法能主动告诉进程,I/O设备资源已经准备好,这种方法就是采用信号机制实现的异步通知(类型于硬件中断的软中断)。
信号
信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程在启动信号机制后,不需要通过任何操作来等待信号的到达,事实上,进程也不知道信号什么时候会到达。
异步通知使用
应用层
应用层启动异步通知机制五个步骤:
1.指定接收信号,设置信号处理函数
struct sigaction act,oldact;
sigemptyset( &act.sa_mask );//清除信号集
sigaddset( &act.sa_mask, SIGIO );//将SIGIO 加入信号捕捉集
act.sa_flags = SA_SIGINFO;//设置标志位后,使用 my_signal_func 函数处理信号
act.sa_sigaction = my_signal_func;//信号处理函数
sigaction( SIGIO, &act, &oldact ) ;//act为新的,oldact为老的
2.设置异步I/O所有权
fcntl( my_fd, F_SETOWN, getpid() ) ;//所有权为本进程,这样驱动才知道将信号发给谁
3.设置 SIGIO 信号
fcntl( my_fd, F_SETSIG, SIGIO );
4.获取文件 flags
flag == fcntl( my_fd, F_GETFL ));
5.设置文件 flags,设置 FASYNC,支持异步通知
fcntl( my_fd, F_SETFL, flag | FASYNC );//先获取原先标志位,然后或上fasync标志位,便设置好了异步通知
驱动层
在驱动中使用异步通知机制需要四个步骤:
1.定义一个fasync_struct结构体指针
static struct my_device {
char name[64]; /* 设备文件名 */
wait_queue_head_t read_queue; /* 读等待队列 */
wait_queue_head_t write_queue; /* 写等待队列 */
struct kfifo my_kfifo; /* 环形缓存区 */
struct fasync_struct *fasync; /* 异步通知结构体指针 */
};
2.实现xxx_fasync函数,使用fasync_helper函数构造struct fasync_struct类型的节点,并添加到系统的链表(异步通知链表,自定义名称)中。
static int my_kfifo_drv_fasync(int fd, struct file *file, int mode)
{
struct my_private_data *data = file->private_data;
struct my_device *device = data->device;
return fasync_helper( fd, file, mode, &device->fasync );
}
3.在设备资源准备就绪的地方添加kill_fasync函数发送信号SIGIO给内核
比如:
static ssize_t xxx_write(struct file *filp,const char __user *buf,size_t count,loff_t *ppos)
{
struct my_private_data *data = file->private_data;
struct my_device *device = data->device;
...
if( device->fasync )
kill_fasync(&device->fasync,GIGIO,POLL_IN);
...
}
static ssize_t xxx_read(struct file *filp,const char __user *buf,size_t count,loff_t *ppos)
{
struct my_private_data *data = file->private_data;
struct my_device *device = data->device;
...
if( device->fasync )
kill_fasync(&device->fasync,GIGIO,POLL_OUT);
...
}
4.最后要在文件关闭的时候,将文件从异步通知链表(自定义名称)中删除(前提是之前注册过,未注册也没关系,在移除函数内部会判断file是不是注册异步通知的file,只有是才会移除)
int xxx_release(struct inode *inode,struct file *filp)
{
xxx_fasync(-1,filp,0);
...
return 0;
}
缺陷
当进程打开设备文件1和设备文件2并且都设置了I/O异步通知,那么当异步通知信号到来时,进程怎么知道是哪个文件发来的?因此,通常在信号处理函数中使用poll机制协助完成I/O操作!