设备驱动中的异步通知与异步I/O

在设备驱动中使用异步通知可以使得再进行对设备的访问时,由驱动主动通知用户程序进行访问。这样,使用非阻塞I/O的应用程序无需轮询机制查询设备是否可访问,而阻塞访问也可以被类似“中断”的异步通知所取代。除了异步通知以为,应用还可以在发起I/O请求后,立即返回。之后,在查询I/O完成情况,或者I/O完成后被返回。这个过程为异步I/O。

阻塞与非阻塞访问、poll函数提供了较好的解决设备访问机制,但是如果有了异步通知,整套机制则更加完整了。异步通知的意思是:一旦设备就绪,则主动通知用户程序,这样用户程序就不需要查询设备状态,这一点非常类似于硬件上的“中断”的概念,比较准确的称呼是“信号驱动的异步I/O”。信号是在软件层次上对终端机制的一种模拟,在原理上,一个进程手收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不需要任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。

阻塞I/O意味着一直等待设备可访问后访问,非阻塞I/O中使用了poll函数意味着查询设备是或否可访问,而异步通知则意味着设备通知用户程序自身可访问,之后用户在进行I/O处理。由此可见,这几种I/O方式可以相互补充。阻塞、非阻塞、异步通知本身没有优劣,应根据不同的应运场景合理选择。

异步通知使用信号来实现,信号是进程间通信(IPC)的一种机制,linux可用的信号有30多种,可以百度查询。除了SIGSYOP与SIGKILL两个信号外,进程能够忽略或获取其他全部信号。一个信号被捕获的意识是当一个信号到达是有相应的代码处理它。如果一个信号没有被这个进程捕获,内核将采取默认行为处理。

信号的接收函数

void (*singal(int signum,void(* handler))(int))(int);
//分解为
typedef void (* sighsndler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);
//第一个参数是指定信号的值,第二个参数时指定针对前面信号值的处理函数,若为SIG_IGN,表示忽略给信号,若为SIG_DFL,表示使用
系统默认的方式处理信号,若为用户自定义的函数,则信号被捕获后,该函数将被执行。若signal()函数调用成功,他返回最后一次为
信号signum绑定的处理函数的handler值,失败返回SIG_ERR。

获取“Ctrl+C”并打印相应信号值

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
void sigtem_handler(int signo)
{
    printf("Have change sig N.O %d\n",signo);
    exit(0);
}

int main()
{
    signal(SIGINT,sigtem_handler);
 //   signal(SIGTERM,sigtem_handler);
    while(1);
    return 0;
}

设备驱动中的异步通知与异步I/O

除了signal函数外,sigaction()函数可以用于改变进程接收到信号后的行为,它的原型为:

int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact);
//第一个参数为信号的值,可以是除却SIGKILL及SIGSTOP外的任何一个特定有效的信号。第二个参数是指向结构体sigaction的一个实例的指针,
在结构体sigaction的实例中,指定了对特定信号的处理函数,若为空,则进程会以缺省方式对信号处理;第三个参数oldcat 指向的对象用来
保存原来对相应信号的处理函数,可指定oldact为NULL。如果把第二、第三个参数都设置为NULL,那么刚函数可以用来检测信号的有效性。

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

  1. 支持F_SETOWN命令,能在这个控制命令处理中设置filp->f_owner为对应进程ID。不过西乡工程已有内核完成,设备驱动无需处理。、
  2. 支持F_SETFL命令的处理,每当FASYNC标志改变时,驱动程序中的fasync()函数将得以执行。因此,驱动中应该实现fasync()函数。
  3. 在设备资源可获得时,调用kill_fasync()函数激发相应的信号。

设备驱动中异步通知编程比较简单,主要用到一项数据结构和两个函数,数据结构是fasync_struct结构体,两个函数分别是:

//处理FASYNC标志变更的函数
int fasync_helper(int fd,struct file *filp,int mode,struct fasync_struct **fa);
//释放信号用的函数
void kill_fasync(struct fasync_struct **fa,int sig,int band);

 异步通知函数模板

1.在设备结构体中添加异步结构体
struct xxx_dev{
    struct cdev cdve;
    ...
    struct fasync_struct *async_queue; //异步通知结构体
};
2.file_operations结构体中添加fasync
static struct file_operations xxx_fops{
    ...
    .fasync = xxx_fasync,
};
3.设备驱动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);
} //即只需要把 异步结构体 作为fasync_helper的第四个参数返回即可
4.在write函数中添加 异步信号的读取
static ssize_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);//调用kill_fasync 释放信号
}
5.在文件关闭,即release函数中应用函数fasync将文件从异步通信列表中删除
static int xxx_release(struct inode *inode,struct file *filp)
{
    xxx_fasync(-1,filp,0);
    ...
    return 0;
}

 修改阻塞I/O博文中的驱动代码,编译加载,测试代码

#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>

static void signalio_handler(int signum)
{
    printf("receive a signal from mymodules,signalnum:%d\n",signum);
}

int main()
{
    int fd,oflags;
    fd = open("/dev/mymodules",O_RDWR,S_IRUSR | S_IWUSR);
    if(fd != -1){
        signal(SIGIO,signalio_handler);
        fcntl(fd,F_SETOWN,getpid());
        oflags = fcntl(fd,F_GETFL);
        fcntl(fd,F_SETFL,oflags | FASYNC);
        while(1){
            sleep(100);
        }
    }else
        printf("open device error\n");
    return 0;
}

编译后测试

设备驱动中的异步通知与异步I/O

上一篇:异步通知fasync机制


下一篇:2018 Oracle OpenWorld的正确打开方式