前面使用阻塞和非阻塞的方式来读取驱动中的按键值都是应用程序主动读取的,对于非阻塞的方式还需要应用程序通过poll函数不断的轮询
Linux内核提供了异步通知这个机制来实现驱动程序主动向应用程序发出通知,报告自己可以访问,然后应用程序再从驱动程序中读取或写入数据,软件中断的方式
”信号“类似于硬件上的中断,是软件层面上对中断的模拟,设备可以被读写时发出信号
阻塞、非阻塞、异步通知是针对不同的场合提出来的不同的解决方法,没有优劣之分,选择合适的
除了SIGKILL(9)和SIGSTOP(19)这两个信号不能被忽略外,其他的信号都可以被忽略
如果要在应用程序中使用信号,必须设置信号所使用的信号处理函数,使用如下的函数来注册信号处理函数
sighandler_t signal(int signum, sighandler_t handler)
signum信号标号,handler处理函数指针
处理函数的原型:typedef void (*sighandler_t)(int)
重点关注一个数据结构两个函数
struct fasync_struct {
spinlock_t fa_lock;
int magic;
int fa_fd;
struct fasync_struct *fa_next;
struct file *fa_file;
struct rcu_head fa_rcu;
};
如果要使用异步通知,需要在设备驱动中实现fops中的fasync函数
int (*fasync) (int fd, struct file *filp, int on)
fasync函数中一般通过fasync_helper函数来初始化fasync_struct结构体
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
前三个参数就是fasync的三个参数
第四个参数就是要初始化fasync_struct结构体指针变量
当应用程序通过fcntl(fd,F_SETEL,flags | FASYNC)改变fasync标记时,驱动程序fops操作集中的fasync函数就会执行
关闭驱动文化的时候需要在file_operations操作集中的release函数中释放fasync_struct,释放时同样调用fasync_helper
当设备可以访问的时候,驱动程序需要向应用程序发出信号,相当于产生”中断“。kill_fastync函数负责发送指定的信号,kill_fastync函数原型如下
void kill_fasync(struct fasync_struct **fp, int sig, int band)
1、注册信号处理函数
2、将本应用程序的进程号告诉内核
3、开启异步通知
flags = fcntl(fd,F_GETEL);//获取当前的进程状态
fcntl(fd,F_SETEL,flags | FASYNC);//开启当前进程异步通知功能
重点就是通过fcntl函数设置进程状态为FASYNC,经过这一步,驱动程序中的fasync函数就会执行
struct irqkey_dev
{
dev_t devid;
...
struct fasync_struct *async_queue;
};
void timer_function(unsigned long arg)
{
unsigned char value;
...
//一次完成的按键过程
if(atomic_read(&dev->releasekey))
{
if(dev->async_queue)
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
}
}
static int key_fasync(int fd, struct file *filp, int on)
{
printk(KERN_EMERG "key_fasync enter!\n");
struct irqkey_dev *dev = (struct irqkey_dev *)filp->private_data;
return fasync_helper(fd, filp, on, &dev->async_queue);
}
static int key_release(struct inode *inode,struct file *filp)
{
printk(KERN_EMERG "key_release enter!\n");
return key_fasync(-1, filp, 0);
}
static void sigio_signal_func(int signum)
{
int err = 0;
unsigned int keyvalue = 0;
err = read(fd, &keyvalue, sizeof(keyvalue));
if(err < 0)
{
}
else
{
printf("SIGIO signal! key value = %d\r\n",keyvalue);
}
}
int main(int argc, char *argv[])
{
...
/*open device*/
fd = open(filename, O_RDWR);
if(fd < 0)
{
printf("Can't open file %s\r\n", filename);
return -1;
}
printf("Open file %s OK!\r\n", filename);
signal(SIGIO, sigio_signal_func);
fcntl(fd, F_SETOWN, getpid());
flags = fcntl(fd, F_GETFD);
fcntl(fd, F_SETFL, flags | FASYNC);
while(1)
{
sleep(2);
}
}
在timer_function中,当经过按键防抖确实按键被按下后使用kill_fasync()释放SIGIO信号,第三个参数为POLL_IN表示资源可读
在驱动的fasync函数中使用fasync_helper函数初始化fasync,包括分配内存和设置属性
在驱动的release函数中调用设备驱动的fasync函数(key_fasync())将文件从异步通知的列表中删除
使用如下的语句
return key_fasync(-1, filp, 0);
在应用程序中使用signal来为SIGIO安装sigio_signal_func()作为信号处理函数
signal(SIGIO, sigio_signal_func);
使用fcntl来设置本进程为文件的拥有者,没有这一步内核不会知道应该将信号发给哪个进程
fcntl(fd, F_SETOWN, getpid());
因为启用了异步通知机制还需对设备设置FASYNC机制
flags = fcntl(fd, F_GETFD);
fcntl(fd, F_SETFL, flags | FASYNC);
为了能在用户空间中处理一个设备释放的信号,它必须完成3项工作
为了使设备支持异步通知机制,驱动程序中涉及3项工作