一、基本知识介绍
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);
调用依赖于底层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一下,仔细查看。