前面的队列以及锁都是基于阻塞是的操作。要实现同步,还可以通过信号也就是异步的方式来进行。例如在往文件的写入字符后,发送一个信号。捕捉到信号后执行动作。这样就不会造成阻塞,之前的阻塞性IO和POLL,是调用函数进去检查,条件不满足是造成阻塞。
应用层启动异步通知机制就三个步骤:
1 调用signal函数,让指定的信号SIGIO与处理函数sig_handle对应
2 指定一个进程作为文件的”属主(filp-owner)”, 这样内核才知道信号要发给哪个进程
3 在设备文件中添加FASYNC标志,驱动中就会调用xxx_fasync函数。
流程图如下:
在这里,命名为globalfifo_fasync
static const struct file_operations globalmem_fops={
.owner=THIS_MODULE,
.llseek=globalmem_llseek,
.read=globalmem_read_queue,
.write=globalmem_write_queue,
.unlocked_ioctl=globalmem_ioctl,
.open=globalmem_open,
.release=globalmem_release,
.fasync=globalfifo_fasync,
};
globalfifo_fasync函数实现如下,当文件的模式被设置成FASYNC的时候,将会调用这个函数
static int globalfifo_fasync(int fd,struct file *filp,int mode)
{
struct globalmem_dev *dev=filp->private_data;
return fasync_helper(fd,filp,mode,&dev->async_queue);
}
在 globalfifo_fasync中实际调用的是fasync_add_entry。
(1) 申请一个fasync_struct结构体。可以看到这个结构体手链表形式。fa_fd关联了设备文件fd参数
struct fasync_struct {
spinlock_t fa_lock;
int magic;
int fa_fd;
struct fasync_struct *fa_next; /* singly linked list */
struct file *fa_file;
struct rcu_head fa_rcu;
};
(2) 调用fasync_insert_entry将新分配的fasync_struct插入到fasync列表中去。代码如下,如果能在fasync列表中找到对应的filp文件。则直接返回。如果没有找到,则将new插入到fapp的最前面。插入方法new->fa_fd = fd; new->fa_next = *fapp; 和前面介绍的列表插入方法是一样的。
4 等操作完成后,比如写完成,此时可以读取数据了,则通过kill_fasync释放一个信号。代码如下
实际起作用的是kill_fasync_rcu函数,在这个里面,遍历列表中所有的数据,找到owner以及fd。然后发送相应的信号
5 等操作完后fasync_remove_entry将异步队列清空