LINUX裝置驅動學習(十)–非同步通知

來源:互聯網
上載者:User

非同步通知:

使用非同步通知機制可以提高查詢裝置的效率。通過使用非同步通知,應用程式可以在資料可用時收到一個訊號,而無需不停地輪詢。

設定非同步通知的步驟(針對應用程式層來說的):

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將讀出的資料發往到終端。以上就是非同步交換的整個過程。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.