异步通知

异步通知:

使用异步通知机制可以提高查询设备的效率。通过使用异步通知,应用程序可以在数据可用时收到一个信号,而无需不停地轮询。

设置异步通知的步骤(针对应用层来说的):

1.首先制定一个进程作为文件的属主。通过使用fcntl系统调用执行F_SETOWN命令时,属主进程的ID号就会保存在filp->f_owner中,目的是为了让内核知道应该通知哪个进程。

2.在设备中设置FASYNC标志。通过fcntl调用的F_SETFL来完成。

设置晚以上两步后,输入文件就可以在新数据到达时请求发送一个SIGIO信号,该信号被发送到存放在filp->f_owner中的进程。

实例:启用stdin输入文件到当前进程的异步通知机制

 signal(SIGIO,&input_handler);

 fcntl(STDIN_FILENO,F_SETOWN,getpid());//设置STDIN_FILENO的属主为当前进程

 oflags=fcntl(STDIN_FILENO,F_GETFL);//获得STDIN_FILENO的描述符

fcntl(STDIN_FILENO,F_SETFL,oflags|FASYNC);//从新设置描述符

注意的问题:

1.应用程序通常只假设套接字和终端具备异步通知功能。

2.当进程受到SIGIO信号时,它并不知道哪个输入文件有了新的输入。如果有多于一个文件可以异步通知输入的进程,则应用程序必须借助于poll或select来确定输入的来源。

 

驱动程序的实现:

1、F_SETOWN被调用时对filp->f_owner赋值。

2.在执行F_SETFL启用FASYNC时,调用驱动程序的fasync方法。只要filp->f_owner中的FASYNC标志发生了变化,就会调用该方法,以便把这个变化通知驱动程序,使其正确响应。文件打开时,默认fasync标志是被清除的。

3.当数据到达时,所有注册为异步通知的进程都会收到一个SIGIO信号。

 

linux 这种调用方法基于一个数据结构和两个函数,对于驱动开发而言主要关注两个函数,内核会自己维护该数据结构,为驱动程序服务(书上对该数据结构也并未给出多少解释)。包含在头文件<linux/fs.h>中。

 

struct fasync_struct {
    int    magic;
    int    fa_fd;
    struct    fasync_struct    *fa_next; /* singly linked list */
    struct    file         *fa_file;
};

两个函数如下:

int fasnyc_helper(int fd,struct file *filp,int mode,struct fasync_struct **fa);

void kill_fasync(struct fasync_struct **fa,int sig,int band);

当一个打开的文件的FASYNC标志被修改时,调用fasync_helper以便从相关的进程列表中增加或删除文件。当数据到达时,可使用kill_fasync通知所有的相关进程。它的参数是要发送的信号(sig)和带宽(band)。

注意的地方:1.对于struct fasync_struct这个数据结构在编写驱动时并不需要特别关注,它会由内核来维护,驱动程序中调用它即可。如同poll的底层实现上poll_table这个内存页链表也是由内核来维护,驱动程序使用它即可。

2.对于用来通知可读的异步通知:band几乎总是为poll_in

对于用来通知可写的异步通知:band几乎总是为poll_out

3.wait_enevt_interruptible(dev->inq,(dev->rp != dev->wp));

wake_up_interruptible(&dev->inq);

在使进程休眠的时候使用的是值传递,但在唤醒进程的时候使用的指针传递。

实现:

static int scull_p_fasync(int fd,struct file *filp,int mode)

{

   struct scull_pipe *dev = filp->private;

   fasync_helper(fd,filp,mod,&dev->async_queue);

}

 

 

接着当数据到达时,必须执行下面的语句来通知异步读取进程。由于提供scullpipe的读取进程的新数据是在某个进程调用wirte产生的,所以这条语句在scullpipe的write方法中实现:

if(dev->async_queue)

      kill_fasync(&dev->async_queue,SIGIO,POLL_IN);

 

最后注意在文件关闭之前,必须调用fasync方法,以便从活动的异步读取进程列表中删除文件。

scull_p_fasync(-1,filp,0);

下面是自己敲了一下scull_pipe的程序,顺便对前边学的复习一下:

#include <linux/module.h>

#include <linux/moduleparam.h>

#include <linux/init.h>

 

#include <linux/kernel.h>

#include <linux/slab.h>

#include <linux/fs.h>

#include <linux/proc.h>

#include <linux/errno.h>

#include <linux/types.h>

#include <linux/fcntl.h>

#include <linux/poll.h>

#include <linux/cdev.h>

#include <linux/uaccess.h>

 

#include <linux/scull.h>  //包含一些局部变量

 

int scull_major = SCULL_MAJOR;

int scull_minor = 0;

int scull_p_nr_devs = SCULL_P_NR_DEVS;

int scull_p_buffer = SCULL_P_BUFFER;

 

module_param(scull_major,int,S_IRUGO);

module_param(scull_minor,int,S_IRUGO);

module_param(scull_p_nr_devs,int,0);

module_param(scull_p_buffer,int,0);

 

struct scull_pipe {

     wait_queue_head_t  inq,outq;  //定义读写等待队列

     char *buffer,*end;  //缓冲区开始和结束指针

     int buffersize;

     char *rp,*wp;  //当前的读写位置

     int nreaders,nwriters; //读写者计数

     sttuct fasync_struct *asynv_queue;

     struct semphore sem;

     struct cdev cdev;

};

 

dev_t scull_devno;

static struct scull_pipe *scull_p_devices;

 

static int scull_p_fasync(int fd,struct file *filp,int mode);

static int spacefree(struct scull_pipe *dev);

 

static int scull_p_open(struct inode *inode,struct file *filp)

{

   struct scull_pipe *dev;

   dev = container_of(inode->i_cdev,struct scull_pipe,cdev);

   filp->private_data = dev;

 

   if(down_interuptible(&dev->sem))

        return -ERESTARTSYS;

   if(!dev->buffer){

        dev->buffer = kmalloc(scull_p_buffer,GFP_KERNEL);

        if(!dev->buffer){

              up(&dev->sem);

              return -ENOMEM;

         }

   }

   dev->buffersize = scull_p_buffersize;

   dev->end = scull_p_buffer + scull_p_buffersize;

   dev->rp = dev->wp = dev->buffer;

 

   if(filp->f_mode & FMODE_READ)

         dev->nreaders++;

   if(filp->f_mode & FMODE_WRITE)

         dev->nwriters++;

 

   up(&dev->sem);

   return nonseekable_open(inode,filp);

}

 

static int scull_p_release(struct inode *inode,struct file *filp)

{

   struct scull_pipe *dev = filp->private_data;

   scull_p_fasync(-1,filp,0);

   down_interruptible(&dev->sem);

   if(filp->f_mode & FMODE_READ)

         dev->nreaders--;

   if(filp->f_mode & FMODE_WRITE)

         dev->nwriters--;

   if(dev->nreaders + dev->nwriters == 0){

          kfree(dev->buffer);

          dev->buffer =NULL;

   }

    up(&dev->sem);

    return 0;

}

 

static scull_p_read(struct file *filp,char __user *buf,size_t count,loof_t *f_pos)

{

   struct scull_pipe *dev = filp->private_data;

 

   if(down_interruptible(&dev->sem))

        renturn -ERESTARTSYS;

 

    while(dev->rp == dev->wp){

        up(&dev->sem);

        if(filp->f_flags & O_NONBLOCK)

               return -EAGAIN;

        PDEBUG(....);

        if(wait_event_intertuptible(dev->inq,(dev->rp != dev->wp)))

               return -ERESTARTSYS;

        if(down_interruptible(&dev->sem))

               return -ERESTARTSYS;

     }

 

     ....

   if(copy_to_user(buf,dev->rp,count){

            up(&dev->sem);

            return -EFAULT;

   }

   dev->rp += count;

   if(dev->rp == dev->end)

             dev->rp = dev->end;

 

   up(&dev->sem);

 

   wake_up_interrputible(&dev->outq); //在读走数据以后,唤醒写等待队列

   return count;

}

 

static int scull_p_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)

{

     int err = 0;

 

     if(_IOC_TYPE(cmd) != SCULL_IOC_MAGIC)  return -ENOTTY;

     if(_IOC_NR(cmd)     > SCULL_IOC_MAXNR)  return -ENOTTY;

 

     if(_IOC_DIR(cmd) & _IOC_READ)

            err = !access_ok(VERIFY_WRITE,(void __user *)arg,_IOC_SIZE(cmd));

     else if(_IOC_DIR(cmd) & _IOC_WRITE)

             err = !access_ok(VERIFY_READ,(void __user *)arg,_IOC_SIZE(cmd)):

     if(err)  return -EFAULT;

 这里的access_ok函数,成功返回1,失败返回零。上述代码完成了对cmd的检查确保其唯一性,access_ok是面向内核的 ,所以传输方向与其刚好相反。

      switch(cmd){

           case SCULL_P_IOCTSIZE:               //分别是在头文件中定义的宏命令

                     scull_p_buffer = arg;

                     break;

 

           case SCULL_P_IOCQSIZE:

                     return scull_p_buffer;

 

          default:  /* redundant, as cmd was checked against MAXNR */       

                     return -ENOTTY;

      }

      return 0;

}

 

 

static int scull_getwritespace(struct scull_pipe *dev, struct file *filp)

       while (spacefree(dev) == 0) { /* full */

                DEFINE_WAIT(wait);   设置等待队列入口

                up(&dev->sem);

                if (filp->f_flags & O_NONBLOCK)

                       return -EAGAIN;

                PDEBUG("/"%s/" writing: going to sleep/n",current->comm);

                prepare_to_wait(&dev->outq, &wait, TASK_INTERRUPTIBLE); //将dev->outq加入到等待队列,并设置其为可中断状态。

                if (spacefree(dev) == 0)  //这个检查狠重要,确保仍需要休眠

                      schedule();

                finish_wait(&dev->outq, &wait);//schedule()晚以后,马上调用finish_wait

                if (signal_pending(current))

                       return -ERESTARTSYS; /* signal: tell the fs layer to handle it */

                if (down_interruptible(&dev->sem))

                      return -ERESTARTSYS;

       }

       return 0;

}

这里结合scull代码分析下异步通知实现的过程:

异步机制在应用层设计FASYNC标志的置位,驱动层涉及到一个数据结构和两个函数的调用,首先来看驱动层,在scull的驱动代码中用scull_p_fasync函数实现了对fasync_helper的调用,如下:

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

}

在scull_p_write函数中实现了对kill_fasync的调用,如下:

static ssize_t scull_p_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)

{

     struct scull_pipe *dev = filp->private_data;

     int result;

 

     if (down_interruptible(&dev->sem))

           return -ERESTARTSYS;

 

    /* Make sure there's space to write */

    result = scull_getwritespace(dev, filp);

    if (result)

          return result; /* scull_getwritespace called up(&dev->sem) */

 

    /* ok, space is there, accept something */

    count = min(count, (size_t)spacefree(dev));

    if (dev->wp >= dev->rp)

         count = min(count, (size_t)(dev->end - dev->wp)); /* to end-of-buf */

    else                       /* the write pointer has wrapped, fill up to rp-1 */

         count = min(count, (size_t)(dev->rp - dev->wp - 1));

    PDEBUG("Going to accept %li bytes to %p from %p/n", (long)count, dev->wp, buf);

    if (copy_from_user(dev->wp, buf, count)) {

         up (&dev->sem);

         return -EFAULT;

    }

    dev->wp += count;

    if (dev->wp == dev->end)

           dev->wp = dev->buffer;       /* wrapped */

    up(&dev->sem);

 

     /* finally, awake any reader */

     wake_up_interruptible(&dev->inq);  /* blocked in read() and select() */

 

     /* and signal asynchronous readers, explained late in chapter 5 */

     if (dev->async_queue)

             kill_fasync(&dev->async_queue, SIGIO, POLL_IN);

     PDEBUG("/"%s/" did write %li bytes/n",current->comm, (long)count);

     return count;

}

 

在程序的最后调用了kill_fasync,在调用kill_fasync后会向文件的属主进程发送SIGIO信号,从而在驱动层完成了异步机制的设置。下面来看应用程序:

#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)

{

    if (signo==SIGIO)

        gotdata++;

    return;

}

 

char buffer[21];

 

int main(int argc, char **argv)

{

int pipetest0;

    int count;

    struct sigaction action;

 

if ((pipetest0 = open("/dev/scullpipe0",O_RDONLY)) < 0) {

printf("open scullpipe0 error! /n"); 

exit(1);

}

 

    memset(&action, 0, sizeof(action));

    action.sa_handler = sighandler;

    action.sa_flags = 0;

 

    sigaction(SIGIO, &action, NULL);

 

    fcntl(pipetest0, F_SETOWN, getpid());

    fcntl(pipetest0, F_SETFL, fcntl(pipetest0, F_GETFL) | FASYNC);

 

    while(1) {

        /* this only returns if a signal arrives */

        sleep(86400); /* one day */

        if (!gotdata)

            continue;

        count=read(pipetest0, buffer, 21);

        /* buggy: if avail data is more than 4kbytes... */

        write(1,buffer,count);

        gotdata=0;

break;

    }

 

close(pipetest0 );

printf("close pipetest0  ! /n"); 

 

printf("exit !/n"); 

  exit(0);

}

这个程序运行在后台,首先在主程序中通过

    fcntl(pipetest0, F_SETOWN, getpid());

    fcntl(pipetest0, F_SETFL, fcntl(pipetest0, F_GETFL) | FASYNC);

完成了应用层异步通知的设置,然后只有在驱动层调用到了kill_fasync从而发出SIGIO信号后,激活sigaction的处理函数,设置了gotdata的值,并得到了POLL_IN状态表明可读了,这是调用read函数读出数据,并通过write将读出的数据发往到终端。以上就是异步交换的整个过程。

你可能感兴趣的:(数据结构,struct,File,IOC,buffer,Signal)