Linux驱动开发(十五):异步通知

简介

前面使用阻塞和非阻塞的方式来读取驱动中的按键值都是应用程序主动读取的,对于非阻塞的方式还需要应用程序通过poll函数不断的轮询
Linux内核提供了异步通知这个机制来实现驱动程序主动向应用程序发出通知,报告自己可以访问,然后应用程序再从驱动程序中读取或写入数据,软件中断的方式
”信号“类似于硬件上的中断,是软件层面上对中断的模拟,设备可以被读写时发出信号
阻塞、非阻塞、异步通知是针对不同的场合提出来的不同的解决方法,没有优劣之分,选择合适的
除了SIGKILL(9)和SIGSTOP(19)这两个信号不能被忽略外,其他的信号都可以被忽略
如果要在应用程序中使用信号,必须设置信号所使用的信号处理函数,使用如下的函数来注册信号处理函数

sighandler_t signal(int signum, sighandler_t handler)

signum信号标号,handler处理函数指针
处理函数的原型:typedef void (*sighandler_t)(int)

驱动中的信号处理

重点关注一个数据结构两个函数

fasync_struct结构体

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; 
};

fasync函数

如果要使用异步通知,需要在设备驱动中实现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_fasync函数

当设备可以访问的时候,驱动程序需要向应用程序发出信号,相当于产生”中断“。kill_fastync函数负责发送指定的信号,kill_fastync函数原型如下

void kill_fasync(struct fasync_struct **fp, int sig, int band)
  • fp:要操作的 fasync_struct
  • sig:要发送的信号
  • band:可读时设置为POLL_IN,可写时设置为POLL_OUT

应用程序的处理

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项工作

  • 通过F_SETOWN IO控制命令设置设备文件的拥有者为本进程,这样从设备驱动发出的信号才能被本进程接收到
  • 通过F_SETOWN IO控制命令设置设备文件以支持FASYNC,即异步通知模式
  • 通过signal()函数连接信号和信号处理函数

为了使设备支持异步通知机制,驱动程序中涉及3项工作

  • 支持F_SETOWN 命令,能在这个控制命令处理中设置filp->f_owner为对应进程ID。不过此项工作已由内核完成,设备驱动无需处理
  • 支持F_SETFL命令,没放FASYNC标志改变时,驱动程序中的fasync()函数将得以执行。因此,驱动中应该事先fasync()函数
  • 在设备资源可获得时,调用kill_fasync()函数激发相应的信号

整个机制的框图如下
在这里插入图片描述

发布了123 篇原创文章 · 获赞 598 · 访问量 34万+

猜你喜欢

转载自blog.csdn.net/a568713197/article/details/103145834
今日推荐