異步IO


結合阻塞與非阻塞訪問、poll 函數可以較好地解決設備的讀寫,但是如果有了異步通知就更方便了。異步通知的意思是:一旦設備就緒,則主動通知應用程序,這樣應用程序根本就不需要查詢設備狀態,這一點非常類似於硬件上“中斷”地概念,比較准確的稱謂是:信號驅動(SIGIO)的異步 I/O。可以使用signal()函數來設置對應的信號的處理函數。函數原型是:

void (*signal(int signo,void (*func)(int))) (int)

 

我們先來看一個使用信號驅動的例子,通過signal(SIGIO,input_handler) 對打開的文件fd 啟動信號機制,輸入可獲得時inputhandler被調用,代碼如下:

/*async_io_app.c*/

#include <sys/types.h>

#include <sys/stat.h>

#include <stdio.h>

#include <fcntl.h>

#include <signal.h>

#include <unistd.h>

#include <stdlib.h>

#define MAX_LEN 100

int fd;

void input_handler(int num)
{

    char data[MAX_LEN];

    int len;

 

    //讀取並輸出 STDIN_FILENO 上的輸入

    len = read(fd, &data, MAX_LEN);

    data[len] = 0;

    printf("input available:%s\n", data);

}

int main()
{

    int oflags;

    //啟動信號驅動機制

    fd = open("/dev/CDEV_ZHU", O_RDWR, S_IRUSR | S_IWUSR);

    if(fd == -1)
{ printf(
"Device Open Failure !\n"); exit(0); } signal(SIGIO, input_handler); fcntl(fd, F_SETOWN, getpid()); oflags = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, oflags | FASYNC); //最后進入一個死循環,程序什么都不干了,只有信號能激發 input_handler 的運行 //如果程序中沒有這個死循環,會立即執行完畢 while (1); return 0; }

 

下面來解釋一下上面的代碼。為了一個用戶在用戶空間中能處理一個設備釋放的信號,它必須完成一下3份工作:

 1)通過F_SETOWN控制指令設置設備文件的擁有者為本進程,這樣從設備驅動中發出的信號才能被本進程收到。

 2)通過F_SETFL 控制命令設置設備文件支持FASYNC,即異步通知模式。

 3)通過signal()鏈接信號和信號處理函數。

有了信號的發送,那么就一定得有信號的釋放了:

  在設備驅動和應用程序的異步通知交互中,僅僅在應用程序端捕獲信號是不夠的,因為信號沒有的源頭是在驅動端,因此要在適當的時機讓設備驅動釋放信號。

 為了使設備支持異步通知機制,驅動程序中涉及三個操作:

 1)支持F_SETOWN命令,能在這個控制命令處理中設置filp->f_owner為對應的進程ID。不過此項工作已由內核完成,設備驅動無須處理。

 2)支持F_SETFL命令的處理,每當FASYNC標志改變時,驅動程序中fasync()函數將得以進行。因此,驅動程序必須實現fasync()函數。

 3)在設備資源可獲得時,調用kill_fasync()函數激發相應的信號。

 驅動程序中上面的三步是和應用程序是一一對應的。如下圖:

 

 

設備驅動中異步通知編程還是比較簡單的,主要就是一些數據結構,和兩個函數:

數據結構:fasync_struct結構體

函數:1)處理FASYNC標志變更的函數int fasync_helper(int fd, struct file *filp, int mode ,struct fasync_struct **fa);

2) 釋放信號用的函數void kill_fasync(struct fasync_struct **fa, int sig, int band);

和其他設備驅動一樣,一般將fasync_struct放到設備結構體中。

 

下面給出驅動程序部分實現支持異步IO的代碼:

/* async_io_driver.c */

#include <linux/module.h>

#include <linux/init.h>

#include <linux/fs.h>

#include <asm/uaccess.h>

#include <linux/wait.h>

#include <linux/semaphore.h>

#include <linux/device.h>

#include <linux/cdev.h>

#include <linux/sched.h>

#include <linux/fcntl.h>

#include <linux/poll.h>

 

MODULE_LICENSE("GPL");

 

#define LEN  30

#define init_MUTEX(LOCKNAME) sema_init(LOCKNAME,1)

#define DEVICE_NAME  "CDEV_ZHU"

static struct class *cdev_class;

 

struct asycIO                                    

{                                                        

  struct cdev dev_c; /*cdev結構體*/   

  dev_t  dev; 

  char  mem[LEN];

  int   flag ;

  struct semaphore sem; /*並發控制用的信號量*/          

  wait_queue_head_t r_wait; /*阻塞讀用的等待隊列頭*/    

  struct fasync_struct *async_queue; /* 異步結構體指針,用於讀 */

};

 

struct asycIO  asyc_device;

 

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

{

         return fasync_helper(fd, filp, mode, &asyc_device.async_queue);

}

 

/*文件釋放函數*/

int asyc_io_release(struct inode *inode, struct file *filp)

{

         /* 將文件從異步通知列表中刪除 */

  asyc_io_fasync( - 1, filp, 0);

  return 0;

}

 

/*寫操作*/

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

{

   int ret = count;

  printk("In asyc_write! \n");

  down(&asyc_device.sem);  //獲取信號量

  memset(asyc_device.mem,0,LEN);

  if (copy_from_user(asyc_device.mem, buf, count))

  {

        up(&asyc_device.sem);

        return    - EFAULT;

  }

  printk("kernel recieve: %s  and the length is %d \n",asyc_device.mem,count);

  up(&asyc_device.sem);

  asyc_device.flag = 1;

 

  if (asyc_device.async_queue)

       kill_fasync(&asyc_device.async_queue, SIGIO, POLL_IN);

 

  wake_up_interruptible(&asyc_device.r_wait);

 

  return ret;

}

 

static ssize_t asyc_read(struct file *filp, char *buf, size_t len, loff_t *off)

{

         int ret = len;

         printk("In asyc_read \n");

    if (wait_event_interruptible(asyc_device.r_wait, asyc_device.flag != 0))

    {

        return    - ERESTARTSYS;

    }

 

    if (down_interruptible(&asyc_device.sem))

    {

        return    - ERESTARTSYS;

    }

 

    asyc_device.flag = 0;

        

    if (copy_to_user(buf, asyc_device.mem, len))

    {

        up(&asyc_device.sem);

        return    - EFAULT;

    }

    up(&asyc_device.sem);

    return ret;

}

 

struct file_operations asyc_fops =

{

         read: asyc_read,

         write: asyc_write,

         fasync: asyc_io_fasync,

         release: asyc_io_release,

};

 

static int __init asyc_init(void)

{

    int ret,err;

        

    ret = alloc_chrdev_region(&(asyc_device.dev),0,1,DEVICE_NAME) ;

    if (ret)

    {

        printk("globalvar register failure");

    }

    else

    {

                   cdev_init(&(asyc_device.dev_c),&asyc_fops);

                  

                   err = cdev_add(&(asyc_device.dev_c),asyc_device.dev,1);

                  

                   if(err)

                   {

                            printk(KERN_NOTICE "error %d adding FC_dev\n",err);

                            unregister_chrdev_region(asyc_device.dev, 1);

                            return err;

                   }

                   else

                   {

                            printk("device register success! \n");

                   }

                  

                   cdev_class = class_create(THIS_MODULE,DEVICE_NAME);

                   if(IS_ERR(cdev_class))

                   {

                            printk("ERR:cannot create a cdev_class\n"); 

                            unregister_chrdev_region(asyc_device.dev, 1);

                            return -1;

                   }

                   device_create(cdev_class, NULL, asyc_device.dev, 0, DEVICE_NAME);

                  

                   asyc_device.flag = 0;

                   init_MUTEX(&(asyc_device.sem));

                   init_waitqueue_head(&(asyc_device.r_wait));

    }

    return ret;

 

static void __exit asyc_exit(void)

{

         device_destroy(cdev_class,asyc_device.dev);

         class_destroy(cdev_class);

         unregister_chrdev_region(asyc_device.dev,1);

         printk(" device exit! \n");

}

 

module_init(asyc_init);

module_exit(asyc_exit);

 

應用程序實現寫入功能:

/* async_io_app_w.c*/

#include <sys/types.h>

#include <sys/stat.h>

#include <stdio.h>

#include <fcntl.h>

#include <string.h>

int main()

{

    int fd, num;

    char buffer[100] = {0};

 

    fd = open("/dev/CDEV_ZHU", O_RDWR, S_IRUSR | S_IWUSR);

    printf("open /dev/CDEV_ZHU fd = %d \n",fd);

 

    if (fd != -1)

    {

        while (1)

        {

             memset(buffer,0,sizeof(buffer));

            printf("Please input the buffer:\n");

            scanf("%s", buffer);

             if (buffer[0] == '0') //如果輸入 0,退出

            {

                close(fd);

                break;

            }

            write(fd, buffer, strlen(buffer));   

            printf("We have written: %s\n",buffer);

        }

    }

    else

    {

        printf("device open failure\n");

    }

         return 0;

}

 

將上面的“async_io_app.c”、“async_io_driver.c”、“async_io_app_w.c”進行編譯,加載驅動之后,開兩個終端,分別運行async_io_app 和 async_io_app_w,當async_io_app_w有數據寫入的時候,async_io_app的終端會打印所寫入的數據,當然內核也會打印數據,下面是結果:

 

 說明:上面圖是三個終端的打印結果,從左到右一次是async_io_app_w , async_io_app 和使用dmesg 打印內核的結果。

注:我本來也想用代碼格式,但是感覺在vim上排版很舒服的,上來用代碼格式反而還不好看了,於是就這樣了 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM