`
dawuafang
  • 浏览: 1105027 次
文章分类
社区版块
存档分类
最新评论

Linux驱动学习10(异步通知 )

 
阅读更多

一、基本概念

1、步通知的意思是:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上“中断”的概念,比较准确的称谓是

“信号驱动的异步 I/O”。

2、信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过

任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。

3、阻塞 I/O 意味着一直等待设备可访问后再访问, 非阻塞 I/O 中使用 poll()意味着查询设备是否可访问,而异步通知则意味着设备通知自身可访问,实现了异步 I/O。

由此可见,这几种方式 I/O 可以互为补充。

下面这张图对着几个概念描述的很清楚



二、重点代码分析

异步通知编程在底层需要实现一个相应的方法,如下:

static unsigned int mem_pool_fasync(int fd,struct file *filp, int mode)
{
  /*获得设备结构体指针*/
  struct mem_pool_dev *dev = filp->private_data;
  printk("[1]dev->async_queue = %x\n",dev->async_queue);
  return fasync_helper(fd,filp,mode,&dev->async_queue);    
} 

就像我们看到的那样,确实,看起来,这个方法里面好像什么都没有做,所有工作似乎都是由fasync_helper这个函数完成,但是,但是如果咩有驱动程序

中提供的这个方法,那么可想而知,fasync_helper函数也不可能完成这个功能的,因为辅助函数需要访问正确的dev->async_queue结构体,在我编程实验

的时候,就遇到了一个问题:

我们都知道,内核中结构体指针初始化都需要为其分配内存,在内核模块加载函数中,我们都能够找到为设备结构体分配内存的函数语句,但是要知道,该

dev->async_queue结构体在我们设备结构体中也是一个结构体指针,所以如果,没有其他的情况的话,我们也需要为其分配内存,但是一直让我好奇地是

为什么,我们并没有为其分配内存,但是系统依然不会挂掉,最开始的时候,我也是一直想不明白,甚至于,执着的硬性给他分配了内存,由于对指针处理

还不是很熟悉,中途调试,l虚拟机挂了几次,很明显是内核出现指针访问出错的问题。经过代码跟踪,终于发现,原来是在fasync_helper这个函数中会给

dev->async_queue结构体分配地址的,所以立马有种拨开云雾见青天的感觉。


三、示例代码分析

/*======================================================================
    A mem_pool driver as an example of char device drivers  
======================================================================*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>

#include <linux/wait.h>
#include <linux/sched.h>

#include <linux/poll.h>
#include "mem_pool.h"
	
static mem_pool_major = mem_pool_MAJOR;
/*设备结构体指针*/
struct mem_pool_dev *mem_pool_devp; 
/*文件打开函数*/

int mem_pool_open(struct inode *inode, struct file *filp)
{
  /*将设备结构体指针赋值给文件私有数据指针*/
  filp->private_data = mem_pool_devp;
  return 0;
}


static unsigned int mem_pool_fasync(int fd,struct file *filp, int mode)
{
  /*获得设备结构体指针*/
  struct mem_pool_dev *dev = filp->private_data;
  printk("[1]dev->async_queue = %x\n",dev->async_queue);
  return fasync_helper(fd,filp,mode,&dev->async_queue);    
} 

/*读函数*/
static ssize_t mem_pool_read(struct file *filp, char __user *buf, size_t size,
  loff_t *ppos)
{
  unsigned long p =  *ppos;
  unsigned int count = size;
  int ret = 0;
  /*1. 获得设备结构体指针*/
  struct mem_pool_dev *dev = filp->private_data; 

  /*2. 分析和获取有效的写长度*/
  if (p >= mem_pool_SIZE)
    return count ?  - ENXIO: 0;
  if (count > mem_pool_SIZE - p)
    count = mem_pool_SIZE - p;
  printk("dev->cur_size[%d] \n",dev->cur_size);
  /*
  if(!wait_event_interruptible(mem_pool_devp->test_queue, dev->cur_size))  //进入睡眠
  {
	printk("<1>""Sleeping ... ...");
  }
  */
  /*3. 内核空间->用户空间*/
  if (copy_to_user(buf, (void*)(dev->mem + p), count))
  {
    ret =  - EFAULT;
  }
  else
  {
    *ppos += count;
    ret = count;
    
    printk("<1>" "read %d bytes(s) from %d\n", count, p);
  }
  dev->cur_size -= ret;
  return ret;
}

/*写函数*/
static ssize_t mem_pool_write(struct file *filp, const char __user *buf,
  size_t size, loff_t *ppos)
{
  unsigned long p =  *ppos;
  unsigned int count = size;
  int ret = 0;
  /*1. 获得设备结构体指针*/
  struct mem_pool_dev *dev = filp->private_data; 

  /*2. 分析和获取有效的写长度*/
  if (p >= mem_pool_SIZE)
    return count ?  - ENXIO: 0;
  if (count > mem_pool_SIZE - p)
    count = mem_pool_SIZE - p;
    
  /*3. 用户空间->内核空间*/
  if (copy_from_user(dev->mem + p, buf, count))
    ret =  - EFAULT;
  else
  {
    *ppos += count;
    ret = count;
    
    printk("<1>" "written %d bytes(s) from %d\n", count, p);
  }
  /* 当设备可读的时候,驱动发送信号给内核 */
  if(dev->async_queue)
  {
    printk("<1>""writing  ...\n");
    printk("<1>""[2]dev->async_queue = %x \n",dev->async_queue);
    kill_fasync(&dev->async_queue,SIGIO,POLLIN);  
  }
  return ret;
}

/*文件释放函数*/
int mem_pool_release(struct inode *inode, struct file *filp)
{
  mem_pool_fasync(-1,filp,0);   //文件关闭的时候,将该结构体从队列中删除 
  return 0;
}
/*文件操作结构体*/
static const struct file_operations mem_pool_fops =
{
  .owner = THIS_MODULE,
  .read = mem_pool_read,
  .write = mem_pool_write,
  .open = mem_pool_open,
  .release = mem_pool_release,
  .fasync = mem_pool_fasync,
};
/*初始化并注册cdev*/
static void mem_pool_setup_cdev(struct mem_pool_dev *dev, dev_t devno)
{
  int err;
  /*1. 初始化cdev,绑定设备和文件操作函数*/
  cdev_init(&dev->cdev, &mem_pool_fops);
  dev->cdev.owner = THIS_MODULE;
  /*2. 为文件操作提供具体实现方法*/
  dev->cdev.ops = &mem_pool_fops;
  /*3. 添加该cdev至内核*/
  err = cdev_add(&dev->cdev, devno, 1);
  if (err)
    printk("<1>" "Error %d adding %d", err, devno);
}

/*设备驱动模块加载函数*/
int mem_pool_init(void)
{
  int result;
  dev_t devno = MKDEV(mem_pool_major, 0);

  printk("<1>" "mem_pool_init !\n");
  /*1. 申请设备号*/
  if (mem_pool_major)
    result = register_chrdev_region(devno, 1, "mem_pool");
  else  
  {
	/*2. 动态申请设备号 */  	
    result = alloc_chrdev_region(&devno, 0, 1, "mem_pool");
    mem_pool_major = MAJOR(devno);
  }  
  if (result < 0)
    return result;
    
  /*3. 动态申请设备结构体的内存*/
  mem_pool_devp = kmalloc(sizeof(struct mem_pool_dev), GFP_KERNEL);
  /*4. 申请失败*/
  if (!mem_pool_devp)    
  {
    result =  - ENOMEM;
    goto fail_malloc;
  }


  /*5. 内存初始化*/
  memset(mem_pool_devp, 0, sizeof(struct mem_pool_dev));
  printk("<1>""mem_pool_devp = %x\n",mem_pool_devp);
  printk("<1>""[0]mem_pool_devp->async_queue = %x\n",mem_pool_devp->async_queue);  
  /*6. 注册初始化设备*/
  mem_pool_setup_cdev(mem_pool_devp, devno);
  return 0;

  fail_malloc: unregister_chrdev_region(devno, 1);
  return result;
}

/*模块卸载函数*/
void mem_pool_exit(void)
{
  printk("<1>" "mem_pool_exit !\n");
	/*1. 注销cdev*/
  cdev_del(&mem_pool_devp->cdev);
	/*2. 释放设备结构体内存*/   
  kfree(mem_pool_devp);     
  /*3. 释放设备号*/
  unregister_chrdev_region(MKDEV(mem_pool_major, 0), 1); 
}

MODULE_AUTHOR("shopping");
MODULE_LICENSE("Dual BSD/GPL");

module_param(mem_pool_major, int, S_IRUGO);

module_init(mem_pool_init);
module_exit(mem_pool_exit);


简单介绍在内核驱动中的相关流程

1)实现mem_pool_fasync方法,上述已经提到

2)当设备可读的时候,驱动发送信号给内核

  if(dev->async_queue)
  {
    printk("<1>""writing  ...\n");
    printk("<1>""[2]dev->async_queue = %x \n",dev->async_queue);
    kill_fasync(&dev->async_queue,SIGIO,POLLIN);  
  }
这里就实现了内核的通知机制

2、在应用程序中

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>

int flag = 0;

void signal_handler(int m)
{
	printf("hello,world ! \n");
	flag = 1;
}
int main()
{
	int fd = 0;
	fd_set rds;
	int fd_num = 0;
	char buf[4096];
	int f_flag;
	
	/*1. 打开设备文件*/
	fd = open("/dev/mem_pool",O_RDWR);
	if (fd < 0)
	{
		printf("Open Dev mem_pool Error!\n");
		return -1;
	}
    signal(SIGIO,signal_handler);
    fcntl(fd,F_SETOWN,getpid());
    f_flag = fcntl(fd,F_GETFL);
    fcntl(fd,F_SETFL,f_flag | FASYNC);
    
    while(1)
    {
      printf("waiting to write ... \n");    
      sleep(3);
      if(flag)
        break;
    }  
	/*2. 读设备文件 */
	if(read(fd,buf,sizeof(buf)) < 0 )
	{
		printf("read error  ... \n");		
	}
	
	printf("read buf is %s \n",buf);	  		
	/*3. 关闭文件*/	
	close(fd);
	return 0;	
}


简单梳理下流程

1)打开设备文件,获取文件符

2)通过signal(SIGIO,signal_handler);实现SIGIO信号和signal_handler处理函数绑定

3)通过文件符控制函数fcntl(fd,F_SETOWN,getpid());实现将当前进程号设置为该文件符号的属主,意思是让内核知道将SIGIO发给当前进程,当前进程收到SIGIO后,

将会执行signal_handler函数,在(2)指出了。

4)

    f_flag = fcntl(fd,F_GETFL);
    fcntl(fd,F_SETFL,f_flag | FASYNC);
这上面两句,将会启动异步通知机制,也就是说,标志位一旦被设定,那么就会执行底层的mem_fasync驱动函数。

分享到:
评论

相关推荐

    Linux驱动开发——异步通知驱动程序

    Linux驱动开发——异步通知驱动程序

    zynq的linux驱动14-异步通知

    zynq的linux驱动14-异步通知

    linux 设备驱动异步通知总结

    Linux设备驱动异步通知个人总结 ,纯属个人笔记

    linux驱动开发异步通知源码

    linux驱动开发异步通知源码globalfifo.

    异步通知-MX6U嵌入式linux驱动开发学习笔记基于正点原子阿尔法开发板

    异步通知-MX6U嵌入式linux驱动开发学习笔记基于正点原子阿尔法开发板

    Linux设备驱动详解第二版

    第9章 Linux设备驱动中的异步通知与异步I/O 176 第10章 中断与时钟 193 第11章 内存与I/O访问 213 第12章 工程中的Linux设备驱动 242 第3篇 Linux设备驱动实例 第13章 Linux块设备驱动 272 第14章...

    RV1126实现异步通知测试【Linux驱动】.zip

    RV1126 Linux驱动程序。 项目代码可直接编译运行。

    ZYNQ 7010-7020实现异步通知测试(Linux驱动).zip

    ZYNQ 7010-7020驱动程序,Linux驱动库。 项目代码可完美编译运行~

    Linux设备驱动程序开发详解

    《Linux设备驱动开发详解(第2版)》基于LDD6410开发板,以Linux2.6 版本内核为蓝本,详细介绍自旋锁、信号量、完成量、中断顶/底半部、定时器、内存和I/O映射以及异步通知、阻塞I/O、非阻塞I/O等Linux设备驱动理论;...

    linux设备驱动详解(宋宝华 高清 非影印版)

    《Linux设备驱动开发详解》以Linux 2.6版本内核为蓝本,详细介绍自旋锁、信号量、完成量、中断顶/底半部、定时器、内存和I/O映射以及异步通知、阻塞I/O、非阻塞I/O等Linux设备驱动理论;字符设备、块设备、TTY...

    linux驱动开发详解

    本书是一本介绍Linux设备驱动开发理论、框架与实例的书,本书以Linux 2.6版本内核为蓝本,详细介绍自旋锁、信号量、完成量、中断顶/底半部、定时器、内存和I/O映射以及异步通知、阻塞I/O、非阻塞I/O等Linux 设备驱动...

    《Linux 设备驱动开发详解》第一版第一次印刷勘误

    本书是一本介绍Linux设备驱动开发理论、框架与实例的书,以Linux 2.6版本内核为蓝本,详细介绍自旋锁、信号量、完成量、中断顶/底半部、定时器、内存和I/O映射以及异步通知、阻塞I/O、非阻塞I/O等Linux设备驱动...

    Linux设备驱动开发

    字符设备、块设备、TTY设备、I2C设备、LCD设备、音频设备、USB设备、网络设备、PCI设备等Linux设备驱动的架构和框架中各个复杂数据架构和函数的关系,并讲解了Linux驱动开发的大量实例,使读者能够独立开发各类Linux...

    STM32MP135实现异步通知【支持STM32MP1系列单片机_Linux驱动】.zip

    STM32MP135 Linux驱动程序,支持STM32MP1系列单片机。 项目代码可直接编译运行~

    STM32MP157实现异步通知【支持STM32MP1系列单片机_Linux驱动】.zip

    STM32MP157 Linux驱动程序,支持STM32MP1系列单片机。 项目代码可直接编译运行。

    linux驱动开发详解4

    本书是一本介绍Linux设备驱动开发理论、框架与实例的书,本书以Linux 2.6版本内核为蓝本,详细介绍自旋锁、信号量、完成量、中断顶/底半部、定时器、内存和I/O映射以及异步通知、阻塞I/O、非阻塞I/O等Linux 设备驱动...

    linux驱动开发详解3

    本书是一本介绍Linux设备驱动开发理论、框架与实例的书,本书以Linux 2.6版本内核为蓝本,详细介绍自旋锁、信号量、完成量、中断顶/底半部、定时器、内存和I/O映射以及异步通知、阻塞I/O、非阻塞I/O等Linux 设备驱动...

    linux驱动开发详解2

    本书是一本介绍Linux设备驱动开发理论、框架与实例的书,本书以Linux 2.6版本内核为蓝本,详细介绍自旋锁、信号量、完成量、中断顶/底半部、定时器、内存和I/O映射以及异步通知、阻塞I/O、非阻塞I/O等Linux 设备驱动...

    linux驱动开发详解5

    本书是一本介绍Linux设备驱动开发理论、框架与实例的书,本书以Linux 2.6版本内核为蓝本,详细介绍自旋锁、信号量、完成量、中断顶/底半部、定时器、内存和I/O映射以及异步通知、阻塞I/O、非阻塞I/O等Linux 设备驱动...

    华清远见驱动教程

    -第9章、Linux设备驱动中的异步通知与异步IO -第10章、中断与时钟 -第11章、内存与I-O访问 -第12章、Linux字符设备驱动综合实例 -第13章、Linux块设备驱动 -第14章、Linux终端设备驱动 -第15章、Linux的I2C核心、...

Global site tag (gtag.js) - Google Analytics