歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux技術 >> arm驅動linux異步通知與異步IO

arm驅動linux異步通知與異步IO

日期:2017/3/3 12:01:23   编辑:Linux技術
  《[ arm驅動] linux異步通知與 異步IO》涉及內核驅動函數二個,內核結構體一個,分析了內核驅動函數二個;可參考的相關應用程序模板或內核驅動模板二個,可參考的相關應用程序模板或內核驅動三個
  描述:設備文件IO訪問:阻塞與非阻塞io訪問,poll函數提供較好的解決設備訪問的機制,但是如果有了異步通知整套機制就更加完整了
  一、阻塞 I/O,非阻塞IO,異步I/O
  1、阻塞 I/O :掛起進程一直等待設備可訪問後再訪問
  2、非阻塞IO:進程進行對設備訪問一次,不可訪問時,繼續執行下一條指令
  3、異步I/O:非常類似於硬件上“中斷”的概念(硬件去call軟件,內核去call應用程序);信號是在軟件層次上對中斷機制的一種模擬;
  a)原理:信號是異步的,一個進程不必通過任何操作來等待信號的到達;事實上:進程也不知道信號到底什麼時候到達;“一個進程收到一個異步通知信號"與"處理器收到一個中斷請求"原理是一樣的;
  4、異步I/O通知隊列(async_queue):內核通過“內核異步通知的程序 fasync()函數”將設備文件fd描述符加入異步通知隊列(內核異步通知的鏈表)。當fd有I/O操作發生時內核通過kill_fasync()釋放(產生) SIGIO 信號,從而達到主動通知注冊過SIG_IO信號的應用程序。
  5、異步通知對象:首先它是設備文件,其次要注冊過fasync()函的文件;異步通知對象不是不是普通文件(不是隨便的/tmp/text.txt),因為普通文件沒有在內核中實現fasync()函數和kill_fasync()
  二、異步通訊應用程序部分
  模板一)設備文件的異步通知應用程序
  voidinput_handler(intnum){//信號處理函數
  }
  //打開目標設備
  fd = open("設備文件路徑如/dev/xxx", O_RDWR);
  //設置好目標設備的SIGIO信號處理程序;等待內核kill_fasync()釋放 SIGIO 信號
  signal(SIGIO,input_handler);
  //使當前進程變成文件的主人,這樣才能使文件中的信號發到當前進程
  fcntl(fd, F_SETOWN, getpid());
  //獲得當前fd的flag值
  oflags = fcntl(fd, F_GETFL);
  /*設置設備文件描述符號fd的FASYNC異步通知標志,
  即給fd添加異步通知模式,fasync()函數將fd加入異步IO通知隊列*/
  fcntl(fd, F_SETFL, oflags | FASYNC);
  圖示一、異步通知工作過程圖
  實例一)以標准輸入輸出設備異步通知
  #include <signal.h>
  #include <unistd.h>
  #include <stdio.h>
  #include <fcntl.h>
  #include <signal.h>
  #define MAX_LEN 100
  voidinput_handler(intnum)
  {
  chardata[MAX_LEN];
  intlen;
  len = read(STDIN_FILENO, &data, MAX_LEN);
  data[len] = 0;
  printf("input available :%s\n", data);
  }
  voidsetFdAsync(intfd){
  intoflags;
  //當前進程變成文件的主人
  fcntl(fd, F_SETOWN, getpid());
  //本程序中fd = STDIN_FILENO標准輸入設備設備文件描述符號;普通文件內核中沒有實現FASYNC,不能使用異步通知
  oflags = fcntl(fd, F_GETFL);//
  //FASYNC在glibc 的fcntl.h文件中可以看到這樣的定義 #define FASYNC O_ASYNC
  fcntl(fd, F_SETFL, oflags | FASYNC);
  }
  voidmain(){
  intfd = STDIN_FILENO;//STDIN_FILENO輸入輸出設備描述符號,一般是鍵盤
  signal(SIGIO,input_handler);//設置好目標設備的SIGIO信號處理程序;等待內核kill_fasync()釋放 SIGIO 信號
  setFdAsync(fd);
  while(1);
  }
  運行結果:
  efgwrfgregr
  input available :efgwrfgregr
  sfsdf
  input available :sfsdf
  //本程序電腦上運行時,由於系統對STDIN_FILENO有特殊保護,while裡面的程序運行了兩次,進程就被系統掛機休眠,此時cpu消耗為0;
  //但我在arm開發板上的linux2.6內核運行時,while正常,進程不被掛起,估計是沒鍵盤的原因...,也待解
  三、驅動程序部分
  驅動程序:一項數據結構和兩個函數
  結構體一)一項數據結構----- fasync_struct結構體
  內核源碼一)fasync_struct結構體內核源碼
  struct fasync_struct {
  int magic;//啟用設備文件鏡像,監聽文件是否變化(這個說法我猜的)
  int fa_fd;//文件描述符
  struct fasync_struct *fa_next; /* 異步通知單鏈表 */
  //filp是進程通過PCB中的文件描述符表找到該fd所指向的文件指針;在fopen流操作中使用file結構體指針它的優點是帶有I/O緩存
  struct file *fa_file;
  //struct file表示該進程打開的文件,其中有一個owner屬性,用來表示打開設備文件的進程
  };
  兩個函數
  內核部分函數一)fasync_helper處理設備文件異步通知的標志(O_ASYNC或FASYNC),將fd加入異步通知隊列函數
  fasync_helper(int fd, struct file * filp, int on, struct fasync_struct * * fapp);
  內核源碼二)fasync_helper內核源碼分析
  //第一次因為on = MODE = oflag | FASYNC,on!=0所以執行if (on)對struct fasync_struct **fapp進行初始化,
  //當程序釋放設備使用myfasync_drv_fasync(-1, file, 0),就執行goto out釋放中斷
  int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
  {
  struct fasync_struct *fa, **fp;
  struct fasync_struct *new = NULL;
  int result = 0;
  if (on) {//第一次分配fapp空間
  new = kmem_cache_alloc(fasync_cache, GFP_KERNEL);
  if (!new)
  return -ENOMEM;
  }
  write_lock_irq(&fasync_lock);
  for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) {//第一次初始化fapp
  if (fa->fa_file == filp) {
  if(on) {
  fa->fa_fd = fd;
  kmem_cache_free(fasync_cache, new);
  } else {
  *fp = fa->fa_next;
  kmem_cache_free(fasync_cache, fa);
  result = 1;
  }
  goto out;
  }
  }
  if (on) {
  new->magic = FASYNC_MAGIC;
  new->fa_file = filp;
  new->fa_fd = fd;
  new->fa_next = *fapp;
  *fapp = new;
  result = 1;
  }
  out:
  write_unlock_irq(&fasync_lock);
  return result;
  }
  EXPORT_SYMBOL(fasync_helper);
  釋放信號函數
  內核部分函數二)kill_fasync(struct fasync_struct * * fp, int sig, int band)
  參數:sig就是我們要發送的信號;band(帶寬),一般都是使用POLL_IN,表示設備可讀,如果設備可寫,使用POLL_OUT
  內核源碼三)釋放(產生)異步讀信號函數
  void __kill_fasync(struct fasync_struct *fa, int sig, int band)
  {
  while (fa) {
  struct fown_struct * fown;
  //如果設備文件鏡像不存在如設備文件不存在(被刪除或改名)或取消了注冊FASYNC;鏡像映射失敗跳出kill_fasync,不產生信號
  if (fa->magic != FASYNC_MAGIC) {
  printk(KERN_ERR "kill_fasync: bad magic number in "
  "fasync_struct!\n");
  return;
  }
  fown = &fa->fa_file->f_owner;
  /* Don't send SIGURG to processes which have not set a
  queued signum: SIGURG has its own default signalling
  mechanism. */
  if (!(sig == SIGURG && fown->signum == 0))
  send_sigio(fown, fa->fa_fd, band);
  fa = fa->fa_next;
  }
  }
  EXPORT_SYMBOL(__kill_fasync);
  模板二)信號的異步通知機制模板
  struct VirtualDisk{
  struct cdev cdev;
  //...其他全局變量....
  struct fasync_struct *async_queue;//異步結構體指針
  };
  /*異步讀信號*/
  static int myfasync_drv_fasync(int fd, struct file *file, int mode){
  struct VirtualDisk *devp = file->private_data; /*獲得設備結構體指針*/
  //....................
  return fasync_helper(fd, file, mode, &devp->async_queue);
  }
  static ssize_t myfasync_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos){
  struct VirtualDisk *devp = file->private_data; /*獲得設備結構體指針*/
  //...............
  //產生異步讀信號SIGIO
  if(devp->async_queue)kill_fasync(&devp->async_queue, SIGIO, POLL_IN);
  return 0;
  }
  static int myfasync_drv_release(struct inode *inode, struct file *file)
  {
  /*當設備關閉時,需要將fasync_struct從異步隊列中刪除/*
  myfasync_drv_fasync(-1, file, 0);
  return 0;
  }
  實例二)驅動程序完整實例:
  //“myfasync_drv”,"myfasync_","myfasync_drv"
  #include <linux/module.h>//模塊所需的大量符號和函數定義
  #include <linux/kernel.h>
  #include <linux/fs.h>//文件系統相關的函數和頭文件
  #include <linux/init.h> //指定初始化和清除函數
  #include <linux/delay.h>
  #include <linux/cdev.h> //cdev結構的頭文件包含<linux/kdev_t.h>
  #include <linux/device.h>
  #include <linux/mm.h>
  //#include <linux/sched.h>//包含驅動程序使用的大部分內核API的定義,包括睡眠函數以及各種變量聲明
  #include <asm/uaccess.h>//在內核和用戶空間中移動數據的函數
  #include <asm/irq.h>
  #include <asm/io.h>
  #include <asm/arch/regs-gpio.h>
  #include <asm/hardware.h>
  #define VIRTUALDISK_SIZE 0x1000//4k
  #define MEM_CLEAR 0x1
  #define VIRTUALDISK_MAJOR 250
  int VirtualDisk_major = VIRTUALDISK_MAJOR;
  struct fasync_struct *async_queue;//異步結構體指針
  struct VirtualDisk{
  struct cdev cdev;//詳細看cdev機制
  unsigned char mem[VIRTUALDISK_SIZE ];
  long count; /*記錄設備目前被多少設備打開*/
  };
  static struct class *myfasync_class;
  static struct class_device *myfasync_class_dev;
  struct VirtualDisk *VirtualDiskp;
  static int myfasync_drv_fasync(int fd, struct file *file, int mode){
  printk("myfasync_drv_fasync %d\n", fd);
  return fasync_helper(fd, file, mode, &async_queue);
  }
  static int myfasync_drv_open(struct inode *inode, struct file *file)
  {
  printk("myfasync_drv open\n");
  file->private_data = VirtualDiskp;
  VirtualDiskp->count++; /*增加設備打開次數*/
  return 0;
  }
  static int myfasync_drv_release(struct inode *inode, struct file *file)
  {
  printk("myfasync_drv release\n");
  VirtualDiskp->count--; /*減少設備打開次數*/
  myfasync_drv_fasync(-1, file, 0);//當設備關閉時,需要將fasync_struct從異步隊列中刪除
  return 0;
  }
  /*seek文件定位函數:seek()函數對文件定位的起始地址可以是文件開頭(SEEK_SET,0)、當前位置(SEEK_CUR,1)、文件尾(SEEK_END,2)*/
  static loff_t myfasync_drv_llseek(struct file *file, loff_t offset, int origin){
  loff_t ret = 0;/*返回的位置偏移*/
  switch (origin)
  {
  case SEEK_SET: /*相對文件開始位置偏移*/
  if (offset < 0)/*offset不合法*/
  {
  ret = - EINVAL; /*無效的指針*/
  break;
  }
  if ((unsigned int)offset > VIRTUALDISK_SIZE)/*偏移大於設備內存*/
  {
  ret = - EINVAL; /*無效的指針*/
  break;
  }
  file->f_pos = (unsigned int)offset; /*更新文件指針位置*/
  ret = file->f_pos;/*返回的位置偏移*/
  break;
  case SEEK_CUR: /*相對文件當前位置偏移*/
  if ((file->f_pos + offset) > VIRTUALDISK_SIZE)/*偏移大於設備內存*/
  {
  ret = - EINVAL;/*無效的指針*/
  break;
  }
  if ((file->f_pos + offset) < 0)/*指針不合法*/
  {
  ret = - EINVAL;/*無效的指針*/
  break;
  }
  file->f_pos += offset;/*更新文件指針位置*/
  ret = file->f_pos;/*返回的位置偏移*/
  break;
  default:
  ret = - EINVAL;/*無效的指針*/
  break;
  }
  return ret;
  }
  /*設備控制函數:ioctl()函數接受的MEM_CLEAR命令,這個命令將全局內存的有效數據長度清零,對於設備不支持的命令,ioctl()函數應該返回-EINVAL*/
  static int myfasync_drv_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg){
  struct VirtualDisk *devp = file->private_data;/*獲得設備結構體指針*/
  switch (cmd)
  {
  case MEM_CLEAR:/*設備內存清零*/
  memset(devp->mem, 0, VIRTUALDISK_SIZE);
  printk(KERN_INFO "VirtualDisk is set to zero\n");
  break;
  default:
  return - EINVAL;
  }
  return 0;
  }
  /*讀函數:讀寫函數主要是讓設備結構體的mem[]數組與用戶空間交互數據,並隨著訪問字節數變更返回用戶的文件讀寫偏移位置*/
  static ssize_t myfasync_drv_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
  {
  unsigned long p = *ppos; /*記錄文件指針偏移位置*/
  unsigned int countt = count;/*記錄需要讀取的字節數*/
  int ret = 0; /*返回值*/
  struct VirtualDisk *devp = file->private_data; /*獲得設備結構體指針*/
  printk("myfasync_drv read\n");
  /*分析和獲取有效的讀長度*/
  if (p >= VIRTUALDISK_SIZE ) /*要讀取的偏移大於設備的內存空間*/
  return 0;/*讀取地址錯誤*/
  if (countt > VIRTUALDISK_SIZE - p)/*要讀取的字節大於設備的內存空間*/
  countt = VIRTUALDISK_SIZE - p;/*將要讀取的字節數設為剩余的字節數*/
  /*內核空間->用戶空間交換數據*/
  if (copy_to_user(buf, (void*)(devp->mem + p), countt))
  {
  ret = - EFAULT;
  }
  else
  {
  *ppos += countt;
  ret = countt;
  printk("read %d bytes(s) is %ld\n", countt, p);
  }
  printk("bytes(s) is %s\n", devp->mem);
  return ret;
  }
  /*
  file 是文件指針,count 是請求的傳輸數據長度,buff 參數是指向用戶空間的緩沖區,這個緩沖區或者保存要寫入的數據,或者是一個存放新讀入數據的空緩沖區,該地址在內核空間不能直接讀寫,ppos 是一個指針指向一個"long offset type"對象, 它指出用戶正在存取的文件位置. 返回值是一個"signed size type。寫的位置相對於文件開頭的偏移。
  */
  static ssize_t myfasync_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
  {
  unsigned long p = *ppos; /*記錄文件指針偏移位置*/
  int ret = 0; /*返回值*/
  unsigned int countt = count;/*記錄需要寫入的字節數*/
  struct VirtualDisk *devp = file->private_data; /*獲得設備結構體指針*/
  printk("myfasync_drv write\n");
  /*分析和獲取有效的寫長度*/
  if (p >= VIRTUALDISK_SIZE )/*要寫入的偏移大於設備的內存空間*/
  return 0;/*寫入地址錯誤*/
  if (countt > VIRTUALDISK_SIZE - p)/*要寫入的字節大於設備的內存空間*/
  countt = VIRTUALDISK_SIZE - p;/*將要寫入的字節數設為剩余的字節數*/
  /*用戶空間->內核空間*/
  if (copy_from_user(devp->mem + p, buf, countt))
  ret = - EFAULT;
  else
  {
  *ppos += countt;/*增加偏移位置*/
  ret = countt;/*返回實際的寫入字節數*/
  printk("written %u bytes(s) from%lu, buffer is %s\n", countt, p, devp->mem);
  }
  if(async_queue){
  kill_fasync(&async_queue, SIGIO, POLL_IN);
  printk("write kill_fasync\n");
  }
  return ret;
  }
  static struct file_operations myfasync_drv_fops = {
  .owner = THIS_MODULE, /* 這是一個宏,推向編譯模塊時自動創建的__this_module變量 */
  .open = myfasync_drv_open,
  .read = myfasync_drv_read,
  .write = myfasync_drv_write,
  .release = myfasync_drv_release,
  .llseek = myfasync_drv_llseek,
  .ioctl = myfasync_drv_ioctl,
  .fasync = myfasync_drv_fasync,
  };
  /*將 cdev 結構嵌入一個你自己的設備特定的結構,你應當初始化你已經分配的結構使用以上函數,有一個其他的 struct cdev 成員你需要初始化. 象 file_operations 結構,struct cdev 有一個擁有者成員,應當設置為 THIS_MODULE,一旦 cdev 結構建立, 最後的步驟是把它告訴內核, 調用:
  cdev_add(&dev->cdev, devno, 1);*/
  static void VirtualDisk_setup_cdev(struct VirtualDisk *dev, int minorIndex){
  int err;
  int devno = MKDEV(VirtualDisk_major, minorIndex);
  cdev_init(&dev->cdev, &myfasync_drv_fops);
  dev->cdev.owner = THIS_MODULE;
  err = cdev_add(&dev->cdev, devno, 1);
  if(err){
  printk("error %d cdev file added\n", err);
  }
  }
  static int myfasync_drv_init(void)
  {
  int result;
  dev_t devno = MKDEV(VirtualDisk_major, 0);
  if(VirtualDisk_major){
  result = register_chrdev_region(devno,
1, "myfasync_drv");
  }else{
  result = alloc_chrdev_region(&devno, 0, 1, "myfasync_drv");
  VirtualDisk_major = MAJOR(devno);
  }
  if(result < 0 ){
  return result;
  }
  VirtualDiskp = kmalloc(sizeof(struct VirtualDisk), GFP_KERNEL);
  if(!VirtualDiskp){
  result = -ENOMEM;
  goto fail_malloc;
  }
  memset(VirtualDiskp, 0, sizeof(struct VirtualDisk));
  VirtualDisk_setup_cdev(VirtualDiskp, 0);
  myfasync_class = class_create(THIS_MODULE, "myfasync_drv");
  if (IS_ERR(myfasync_class))
  return PTR_ERR(myfasync_class);
  myfasync_class_dev = class_device_create(myfasync_class, NULL, MKDEV(VirtualDisk_major, 0), NULL, "myfasync_drv"); /* /dev/xyz */
  if (IS_ERR(myfasync_class_dev))
  return PTR_ERR(myfasync_class_dev);
  return 0;
  fail_malloc:
  unregister_chrdev_region(devno, 1);
  return result;
  }
  static void myfasync_drv_exit(void)
  {
  cdev_del(&VirtualDiskp->cdev);
  kfree(VirtualDiskp);
  unregister_chrdev_region(MKDEV(VirtualDisk_major, 0), 1);
  class_device_unregister(myfasync_class_dev);
  class_destroy(myfasync_class);
  }
  module_init(myfasync_drv_init);
  module_exit(myfasync_drv_exit);
  MODULE_LICENSE("GPL");
  Makefile
  #myfasync_drv.c
  KERN_DIR = /workspacearm/linux-2.6.2.6
  all:
  make -C $(KERN_DIR) M=`pwd` modules
  cp myfasync_drv.ko /opt/fsmini/
  clean:
  make -C $(KERN_DIR) M=`pwd` modules clean
  rm -rf timerlists.order
  obj-m += myfasync_drv.o
  實例三)驅動程序對應的測試的應用程序部分
  #include <signal.h>
  #include <unistd.h>
  #include <stdio.h>
  #include <fcntl.h>
  #include <signal.h>
  int myfd;
  int lenthe;
  void input_handler(int num)
  {
  char data[80];
  int len;
  lseek(myfd, -lenthe, SEEK_CUR);//移動偏移量到寫之前位置
  len = read(myfd, data, lenthe);
  //data[len] = '';
  printf("myfd = %d, len = %d buffuer input available :%s\n",myfd, len, data);
  }
  void setFdAsync(int fd){
  int oflags;
  //當前進程變成文件的主人
  fcntl(fd, F_SETOWN, getpid());
  //本程序中fd = STDIN_FILENO標准輸入設備設備文件描述符號;普通文件內核中沒有實現FASYNC,不能使用異步通信
  oflags = fcntl(fd, F_GETFL);//
  //FASYNC在glibc 的fcntl.h文件中可以看到這樣的定義 #define FASYNC O_ASYNC
  fcntl(fd, F_SETFL, oflags | FASYNC);
  }
  int main(){
  myfd = open("/dev/myfasync_drv", O_RDWR);//STDIN_FILENO輸入輸出設備描述符號,一般是鍵盤
  printf("fd = %d,pid = %d", myfd, getpid());
  signal(SIGIO,input_handler);//設置好目標設備的SIGIO信號處理程序;等待內核kill_fasync()釋放 SIGIO 信號
  setFdAsync(myfd);
  printf("before while\n");
  while(1){
  char buffer[80];
  lenthe = read(STDIN_FILENO, buffer, 80);
  write(myfd, buffer, lenthe);
  }
  return 0;
  }
  我的Makefile
  objs := $(patsubst %c, %o, $(shell ls *.c))
  myarmgcc := /workspacearm/armlinuxgcc2626/bin/arm-linux-gcc
  mybutton.bin:$(objs)
  $(myarmgcc) -o $@ $^
  cp *.bin /opt/fsmini/
  %.o:%.c
  $(myarmgcc) -c -o $@ $<
  clean:
  rm -f *.bin *.o
  實驗結果
  # insmod myfasync_drv.ko
  # ./mybutton.bin
  myfasync_drv open//對應應用程序myfd = open("/dev/myfasync_drv",調用了內核驅動open函數
  myfasync_drv_fasync 3//對應應用程序fcntl(fd, F_SETFL, oflags | FASYNC);調用了內核驅動的myfasync_drv_fasync()函數
  //
  fd = 3,pid = 793before while//while前的進程信息輸出
  hello//鍵盤輸入hello
  myfasync_drv write//調用驅動程序write函數
  written 6 bytes(s) from0, buffer is hello//驅動程序write函數內部輸出
  write kill_fasync//內涵write函數中,執行kill_fasync(&async_queue, SIGIO, POLL_IN);釋放SIGIO信號
  myfasync_drv read//此時應用程序收到中斷,應用程序執行read函數,read對應內核驅動的read
  read 6 bytes(s) is 0//內核驅動read打印輸出
  bytes(s) is hello //內核驅動read打印輸出
  myfd = 3, len = 6 buffuer input available :hello//應用程序input_handler函數輸出驅動的寫入值
  //下面是while第二次執行
  it is ok
  myfasync_drv write
  written 9 bytes(s) from6, buffer is hello
  it is ok
  write kill_fasync
  myfasync_drv read
  read 9 bytes(s) is 6
  bytes(s) is hello
  it is ok
  myfd = 3, len = 9 buffuer input available :it is ok
  //按ctrl+c退出程序,會執行myfasync_drv_release中myfasync_drv_fasync(-1, file, 0),釋放本進程的異步通知
  myfasync_drv release
  myfasync_drv_fasync -1
  #
  四、異步IO缺陷:當有多個文件發送異步通知信號給一個進程時,進程無法知道是哪個文件發送的信號,這時候“設備文件 ”還是要借助poll機制完成IO;(應用程序中使用select)
Copyright © Linux教程網 All Rights Reserved