Linux设备驱动程序第三版学习(8)- 高级字符驱动程序操作(续3)- 异步通知

第六章:高级字符驱动程序操作(续3)
以下为第四部分:异步通知

使用poll轮询方式的时候,相当于应用程序在需要的时候询问设备“准备好了吗?”,如果有这样一种情况,一个进程在低优先级正在执行长的循环计算,但又需要“尽可能快”的处理输入数据,如果采用poll的方式,那么需要这个应用程序周期性的调用poll来检测数据,也就是周期性的询问设备“准备好了吗?” 显然这种情况下poll并不是最佳的方法。更好的方法应该是一旦设备准备好了就发出一个“我准备好了”的信号给应用程序,然后应用程序再去处理。这样显然更高效。这种方法就是:异步通知。
通过上面的描述,显然如果想要异步通知,首先设备就要有发信号的功能,这就要启动文件的异步通知机制。为了启动这个机制,用户程序需要进行两个步骤:
1.指定一个进程作为文件的owner(“所有者”或者“属主”),用来接收“我准备好了”这个信号。该进程的ID好被保存到filp->f_owner中。通过调用fcntl执行F_SETOWN来完成这一步。
2.在设备中设置FASYNC标志来真正启动异步通知机制。通过调用fcntl执行F_SETFL命令完成这一步。
执行完这两个步骤以后,当有新数据到达时设备文件会发送一个SIGIO信号(“我准备好了”),该信号被发送到存放在file->f_owner中的进程。
注:显然进程如果只接受到“我准备好了”这个信号,它就会问“那个‘我’是谁阿?”(突然想起《鬼子来了》,淡定...),所以如果有多于一个文件可以异步通知输入的进程,那么当应用程序接收到信号时还是要借助poll轮询一次,以便真正确定输入的来源。

下面分别从驱动程序角度和应用程序角度分别总结一下我们应该进行哪些工作。
一、 驱动方面:
1. 在设备抽象的数据结构中增加一个struct fasync_struct的指针
2. 实现设备操作中的fasync函数,其主体就是调用内核的fasync_helper函数。
3. 在需要向用户空间通知的地方(例如scullpipe的write中)调用内核的kill_fasync函数。
4. 在驱动的release方法中调用前面定义的fasync函数, 例如scull_p_fasync(-1, filp, 0);
看一下驱动方面的源码:
    //对应上边的第1步
    struct scull_pipe { ...... struct fasync_struct *async_queue; ...... };  
    //对应上面的第2步
    static int scull_p_fasync(int fd, struct file *filp, int mode) { struct scull_pipe *dev = filp->private_data; return fasync_helper(fd, filp, mode, &dev->async_queue); } 
    
    //对应上面的第3步,由于新数据是由于进程调用了write而产生的,所以在这里发送SIGIO信号
static ssize_t scull_p_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { ...... ...... if (dev->async_queue) kill_fasync(&dev->async_queue, SIGIO, POLL_IN); ...... ...... } 
    
    //对应上面第4步
    static int scull_p_release(struct inode *inode, struct file *filp) { ...... scull_p_fasync(-1, filp, 0); ...... }

二、 应用层方面
1. 利用signal或者sigaction设置SIGIO信号的处理函数,关于signal的使用参照本博客转载的一篇“signal”
2. fcntl的F_SETOWN指令设置当前进程为设备文件owner
3. fcntl的F_SETFL指令设置FASYNC标志
看一下应用层方面的源代码,来自asynctest.c文件
    #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <signal.h> #include <fcntl.h> int gotdata=0; void sighandler(int signo) //这个就是收到SIGIO信号对应的处理函数 { if (signo==SIGIO) gotdata++; return; } char buffer[4096]; int main(int argc, char **argv) { int count; struct sigaction action; //sigaction结构变量action memset(&action, 0, sizeof(action)); //清空该变量 action.sa_handler = sighandler; action.sa_flags = 0; //对应上面的第1步,设置SIGIO的处理函数为sighandle sigaction(SIGIO, &action, NULL); r //对应上面的第2步,设置当前进程为设备文件owner。使用getpid获得当前进程的ID fcntl(STDIN_FILENO, F_SETOWN, getpid()); //对应上面的第3步,设置FASYNC标志,使用的命令是F_SETFL //一旦文件描述符被设置成具有FASYNC属性的状态, //也就是将设备文件切换到异步操作模式。 //这时系统就会自动调用驱动程序的fasync方法 fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | FASYNC); //到此异步通知机制就已经设置好了 while(1) { /* this only returns if a signal arrives */ sleep(86400); //此处线程开始休眠,该程序一直处于休眠状态,当有标准输入时候,linux操作系统发送SIGIO消息给进程,并将其唤醒 if (!gotdata) //唤醒之后还要再检查以下gotdata的状态,如果不是SIGIO唤醒的,则表明没有标准输入,则继续进行while循环 continue; count=read(0, buffer, 4096); /* buggy: if avail data is more than 4kbytes... */ write(1,buffer,count); gotdata=0; } }

你可能感兴趣的:(Linux设备驱动程序第三版学习(8)- 高级字符驱动程序操作(续3)- 异步通知)