当我们驱动程序当中有数据可读的时候,可以通过驱动程序中的异步通知机制来通知应用程序数据是否可读,从而提高读取数据的效率。
主要实现思路是:设备I/O驱动如果准备好数据(用户可读或者可写),向用户进程发送信号,用户进程收到信号后调用相关的信号处理函数对设备的数据进行访问。
实现上述的思路需要两方面的实现:用户程序的实现、设备驱动程序的实现。
用户程序的实现需要:
(1) 设置执行用户程序的PID作为设备文件的所有者。可通过下面语句实现:
fcntl(STDIN_FILENO, F_SETOWN, getpid());
STDIN_FILENO这里是标准输入的文件描述符,我们可以重定向为设备的文件描述符。
F_SETOWN命令表示设置接收SIGIO或SIGURG信号的进程ID或者进程组ID。
(2) 用户程序必须对访问的设备文件设置FASYNC标志。可通过下面语句实现:
fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | FASYNC);
F_SETFL命令表示设置文件状态标志位(取第3个参数的值)
测试程序一:
/* * asynctest.c: use async notification to read stdin * * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet * Copyright (C) 2001 O'Reilly & Associates * * The source code in this file can be freely used, adapted, * and redistributed in source or binary form, so long as an * acknowledgment appears in derived source files. The citation * should list that the code comes from the book "Linux Device * Drivers" by Alessandro Rubini and Jonathan Corbet, published * by O'Reilly & Associates. No warranty is attached; * we cannot take responsibility for errors or fitness for use. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <signal.h> #include <fcntl.h> int gotdata=0; //全局flag,判断signal处理函数有没有执行; void sighandler(int signo) //signal处理函数 { if (signo==SIGIO) gotdata++; return; } char buffer[4096]; //全局buffer int main(int argc, char **argv) { int count; struct sigaction action; //初始化 action memset(&action, 0, sizeof(action)); action.sa_handler = sighandler; action.sa_flags = 0; sigaction(SIGIO, &action, NULL);//绑定action到SIGIO signal fcntl(STDIN_FILENO, F_SETOWN, getpid());//设置STDIN_FILENOD的owner为当前进程 fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | FASYNC);//设置STDIN_FILENO的文件状态标志(增加了FASYNC标志)。 while(1) { /* this only returns if a signal arrives */ sleep(86400); /* one day */ //当SIGIO signal来了,会将当前进程从sleep唤醒 if (!gotdata) continue; count=read(0, buffer, 4096);//从标准输入读数据 /* buggy: if avail data is more than 4kbytes... */ write(1,buffer,count);//向标准输出写数据 gotdata=0; } }
测试程序二:
#include <stdio.h> #include <sys/types.h> #include <fcntl.h> #include <string.h> #include <signal.h> #include <unistd.h> #define MAX_LEN 100 int fd; //异步通知处理函数,有点类似于软中断 void input_handler(int num) { char data[MAX_LEN]; int len; int temp; len = read(fd,&temp,4); data[len]=0; printf("------------->input available: %d\n",temp); memset(data,0,MAX_LEN); } int main() { int oflags; int buf,ret; int i =3; fd = open("/dev/test", O_RDWR); if( fd < 0){ printf("open error ret = %d, buf = %d\n",ret,buf); return -1; } //启动信号驱动机制 /* 将SIGIO信号同input_handler函数关联起来, 一旦产生SIGIO信号,就会执行input_handler, */ signal(SIGIO,input_handler); /* F_SETOWN用来决定操作是干什么的, getpid()是个系统调用,功能是返回当前进程的进程号 整个函数的功能是STDIN_FILENO设置这个设备文件的拥有者为当前进程。 属主进程的进程ID号被保存到filp->f_owner中 */ fcntl(fd,F_SETOWN,getpid()); /*得到打开文件描述符的状态*/ oflags = fcntl(fd, F_GETFL); /* 设置文件描述符的状态为oflags | FASYNC属性, 一旦文件描述符被设置成具有FASYNC属性的状态, 也就是将设备文件切换到异步操作模式。 这时系统就会自动调用驱动程序的fasync方法。 */ fcntl(fd, F_SETFL, oflags | FASYNC); for(i=0;i<10;i++) { sleep(1); printf("wait for interrupt...\n"); } return 0; }
驱动程序的当中:
/*首先是定义一个结构体,其实这个结构体存放的是一个列表,这个列表保存的是 一系列设备文件,SIGIO信号就发送到这些设备上*/ static struct fasync_struct fasync_queue;
定义异步的方法:
static int my_fasync(int fd, struct file * filp, int on) /*fasync方法的实现*/ { int retval; retval=fasync_helper(fd,filp,on,&fasync_queue); /*将该设备登记到fasync_queue队列中去*/ if(retval<0) return retval; return 0; }
在中断处理函数中定义:
//这样后我们在需要的地方(比如中断)调用下面的代码,就会向fasync_queue队列里的设备发送SIGIO信号 //,应用程序收到信号,执行处理程序,当是设备可写入的时候采用异步通知应用程序最后的标记应该使用POLL_OUT if (&fasync_queue) kill_fasync(&fasync_queue, SIGIO, POLL_IN);
实验结果:
bash-4.0# ./test open major=252, minor=0 wait for interrupt... wait for interrupt... wait for interrupt... EINT1 EINT1 EINT1 ------------->input available: 1 wait for interrupt... wait for interrupt... wait for interrupt...