关闭

Linux驱动学习8(非阻塞IO的实现--select/poll方法)

标签: 非阻塞IO的实现selectpoll方法
1219人阅读 评论(0) 收藏 举报
分类:

一、基本知识介绍

1、在用户程序中,select()和poll()也是与设备阻塞与非阻塞访问息息相关的论题。使 用非阻塞I/O  的应用程序通常会使用select()和poll()系统调用查询是否可对设备进行

阻塞的访问。select()和poll()系统调用最终会引发设备驱动中的poll()函数被执行, 在2.5.45  内核中还引入了epoll(),即扩展的poll()。 

2、select()和poll()系统调用的本质一样,前者在BSD UNIX 中引入的,后者在System V 中引入的。 应用程序使用 select() 或 poll() 调用设备驱动程序的 poll() 函数,该函

数把输入输出复用处理的等待队列追加到由内核管理的进程的 poll_table()上。此时,poll() 函数上传递的参数包括含有设备文件信息的 struct file 结构体的指针参数 struct 

file *filp ,以及追加到设备驱动上的 poll_table结构体指针参数 poll_table *wait 。使用这两个参数,然后通过poll_wait()函数,在内核上注册输入输出复用条件。poll_wait() 

函数表示如下:

#include <linux/poll.h>
static inline void poll_wait (struct file *filp, wait_queue_head_t *wait_address, poll_table *P);


3、简单来说,其实这个pool_wait函数是没有做什么东西的(除了把一些设备信息传递给内核),从传递的参数就能够看到并没有从用户空间传递东西进来。select的系统

调用依赖于底层poll的实现,同时poll的实现是依赖于等待队列的。

二、重要代码分析

1、首先还是一样初始化一个等待队列头,详细看代码

  /* 等待队列头初始化定义(poll方法实现) */
  init_waitqueue_head(&mem_pool_devp->read_queue);

假设我们这里在读文件的时候实现阻塞(写文件阻塞是一样的道理)

2、既然是依赖于poll方法,那么poll方法肯定要我们自己去实现,先看我的实现方式
static unsigned int mem_pool_poll(struct file *filp, poll_table* wait)
{
  /*获得设备结构体指针*/
  struct mem_pool_dev *dev = filp->private_data;
  unsigned int mask = 0;
  poll_wait(filp,&dev->read_queue,wait);/* 将read_queue队列添加到wait表中 */
  printk("dev->cur_size = %d\n",dev->cur_size);	 
  if(dev->cur_size > 0)
  {   
    mask |= POLLIN | POLLRDNORM;        //设备数据可读   
  }
  if(dev->cur_size < mem_pool_SIZE)
  {
    mask |= POLLOUT | POLLWRNORM;        //设备数据可写      
  }
  printk("mask[%d] \n",mask);
  return mask;
}

mem_pool_poll的实现有两个步骤:
2.1调同poll_wait,将进程添加到指定的等待队列(注意,仅仅是添加,没有休眠)。

poll_wait的原型是:

unsigned int mem_pool_poll (struct file *filp, poll_table *table)
注意:这里的两个参数都不是用户传给它的,全部都是有内核传的。可以这样说,poll没有做实际的什么操作,只是返回些信息给内核来操作。

2.2对应设备的状态,返回相应的掩码。那就是说,如果设备可读,那就返回可读的掩码。

什么是掩码?有什么掩码?



3、唤醒等待队列

为什么需要唤醒?哪里会阻塞呢?毕竟poll_wait函数并不会导致休眠。我上面的驱动函数,test_poll返回掩码,如果掩码为0,则表示设备不可读,

这时,内核接到返回的掩码,知道设备不可读,此时select函数就会阻塞,进程休眠,等待有数据时被唤醒。所以,在写入数据后,需要唤醒等待

队列头read_queue。此时设备可读了,就会再次调用mem_pool_poll 函数,返回掩码POLLIN,select调用成功。

所以,这里得出两个结论:

1.test_poll并不会导致休眠,进程阻塞是系统调用select搞的鬼。

2.系统调用select的阻塞会导致test_poll被调用多次。


三、对于select/poll机制的理解

以下内容为我自己的理解,有误之处,请指出

1.假设有A、B、C、D四个设备文件

2.如果我们在一个应用程序中打开了这四个设备文件

3.如果这四个设备文件都不具备读写属性的时候,那么该进程阻塞

4.假如四个设备中,至少有一个设备文件是可读或者可写(这个由poll返回值表示)的话,那么改进程将不会发生阻塞,同时select函数会返回具有

可读或是写属性的设备文件的个数。值得注意的是:select函数会遍历各个设备文件中的底层poll函数,以探测其属性。


那么,这样做有什么原因呢?

1.在之前已经提到过,如果采用阻塞IO访问方式,当我们程序中的设备文件A不具读写属性(我暂且这样叫),而其他几个是具有这种属性的话。那么当我们读取设备文件A的

时候,将会引起整个进程的阻塞,这样将会使得BCD 四个设备文件也无法读取,极大影响系统的执行效率。

2.当引入poll机制的话, 那么在select函数的探测中,只要含有可读写设备文件,那么该进程就不会阻塞,我们可以在进程中,对各个设备文件进行轮流read。


四、程序解读

1.在内核空间的驱动程序

/*======================================================================
    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;
}
/*文件释放函数*/
int mem_pool_release(struct inode *inode, struct file *filp)
{
  return 0;
}

static unsigned int mem_pool_poll(struct file *filp, poll_table* wait)
{
  /*获得设备结构体指针*/
  struct mem_pool_dev *dev = filp->private_data;
  unsigned int mask = 0;
  poll_wait(filp,&dev->read_queue,wait);/* 将read_queue队列添加到wait表中 */
  printk("dev->cur_size = %d\n",dev->cur_size);	 
  if(dev->cur_size > 0)
  {   
    mask |= POLLIN | POLLRDNORM;        //设备数据可读   
  }
  if(dev->cur_size < mem_pool_SIZE)
  {
    mask |= POLLOUT | POLLWRNORM;        //设备数据可写      
  }
  printk("mask[%d] \n",mask);
  return mask;
}

/* ioctl设备控制函数 */
static int mem_pool_ioctl(struct inode *inodep, struct file *filp, unsigned
  int cmd, unsigned long arg)
{
	/*获得设备结构体指针*/
  struct mem_pool_dev *dev = filp->private_data;
  /*检验命令是否有效*/
	if(_IOC_TYPE(cmd) != TEST_MAGIC) return - EINVAL;
  switch (cmd)
  {
    case MEM_SET:
      break;

    default:
    	printk("<1>" "cmd is error \n");
      return  - EINVAL;
  }
  return 0;
}

/*读函数*/
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);
  }
  /* 唤醒等待队列(阻塞IO) */
  //wake_up_interruptible(&mem_pool_devp->test_queue);
  /* 唤醒等待读队列(poll方法) */
  wake_up_interruptible(&mem_pool_devp->read_queue);
  dev->cur_size += ret;
  return ret;
}

/* seek文件定位函数 */
static loff_t mem_pool_llseek(struct file *filp, loff_t offset, int orig)
{
  loff_t ret = 0;
  switch (orig)
  {
    case 0:   /*相对文件开始位置偏移*/
      if (offset < 0)
      {
        ret =  - EINVAL;
        break;
      }
      if ((unsigned int)offset > mem_pool_SIZE)
      {
        ret =  - EINVAL;
        break;
      }
      filp->f_pos = (unsigned int)offset;
      ret = filp->f_pos;
      break;
    case 1:   /*相对文件当前位置偏移*/
      if ((filp->f_pos + offset) > mem_pool_SIZE)
      {
        ret =  - EINVAL;
        break;
      }
      if ((filp->f_pos + offset) < 0)
      {
        ret =  - EINVAL;
        break;
      }
      filp->f_pos += offset;
      ret = filp->f_pos;
      break;
    default:
      ret =  - EINVAL;
      break;
  }
  return ret;
}

/*文件操作结构体*/
static const struct file_operations mem_pool_fops =
{
  .owner = THIS_MODULE,
  .llseek = mem_pool_llseek,
  .read = mem_pool_read,
  .write = mem_pool_write,
  .ioctl = mem_pool_ioctl,
  .open = mem_pool_open,
  .release = mem_pool_release,
  .poll = mem_pool_poll,
};

/*初始化并注册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));
  
  /* 等待队列头初始化定义(阻塞IO) */
  //init_waitqueue_head(&mem_pool_devp->test_queue);
  /* 等待队列头初始化定义(poll方法实现) */
  init_waitqueue_head(&mem_pool_devp->read_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);




总结下驱动中关于poll机制的流程

1)定义一个等待队列用于实现poll机制

  wait_queue_head_t read_queue; 		//定义等待队列头(用于实现poll
这里我将等待队列放入设备结构体中

2)初始化等待队列

  /* 等待队列头初始化定义(poll方法实现) */
  init_waitqueue_head(&mem_pool_devp->read_queue);
这个初始化在模块加载的时候执行

3)实现poll方法,该部分代码在上面已经提到

4)由于应用程序调用select的时候,如果没有人和设备文件可读写,那么将会导致睡眠,所以我们应该,在底层函数中实现唤醒功能,一般唤醒功能的实现

都是在读写函数中的

  /* 唤醒等待读队列(poll方法) */
  wake_up_interruptible(&mem_pool_devp->read_queue);

至此,底层驱动的流程介绍完毕

2、在用户空间的应用程序

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>		
int main()
{
	int fd = 0;
	fd_set rds;
	int fd_num = 0;
	char buf[4096];
	
	/*1. 打开设备文件*/
	fd = open("/dev/mem_pool",O_RDWR);
	if (fd < 0)
	{
		printf("Open Dev Mem0 Error!\n");
		return -1;
	}
	FD_ZERO(&rds);
	FD_SET(fd,&rds);
	fd_num = select(fd+1,&rds,NULL,NULL,NULL);
	printf("fd_num[%d] \n",fd_num);
	
	/*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)首先会打开一个设备文件,以获取它的文件描述符fd。

2)定义了一个文件描述符的集合,并且对此集合做了一些初始化处理:清空

3)讲(1)中获取到的文件描述符添加到此集合中。

4)接下来是关键的一步,slect上场了,执行此系统调用函数后,立即会关联到底层的poll方法,通过返回值监视是否满足读写设备的要求。

具体关于select的用法,可以在linux中man一下,仔细查看。



0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:289827次
    • 积分:4399
    • 等级:
    • 排名:第7068名
    • 原创:132篇
    • 转载:51篇
    • 译文:1篇
    • 评论:74条
    博客专栏
    最新评论