一、基本知识介绍
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一下,仔细查看。