linux系统在访问设备的时候,存在以下几种IO模型:
今天我们来分析下IO多路复用机制,在Linux中是通过select/poll/epoll机制来实现的。
先看一下阻塞IO模型与非阻塞IO模型的特点:
对单个设备IO操作时,问题并不严重,如果有多个设备呢?比如,在服务器中,监听多个Client的收发处理,这时候IO多路复用就显得尤为重要了,来张图:
如果这个图,让你有点迷惑,那就像个男人一样,man一下select/poll函数吧:
简单来说,select/poll能监听多个设备的文件描述符,只要有任何一个设备满足条件,select/poll就会返回,否则将进行睡眠等待。看起来,select/poll像是一个管家了,统一负责来监听处理了。
已经迫不及待来看看原理了,由于底层的机制大体差不多,我将选择select来做进一步分析。
从select的系统调用开始:
所以,来看看__pollwait()函数喽。
这一顿操作,其实就是驱动向select维护的struct poll_wqueue中注册,并将调用select的任务添加到驱动的等待队列中,以便在合适的时机进行唤醒。所以,本质上来说,这是基于等待队列的机制来实现的。
是不是还有点抽象,来看看数据结构的组织关系吧。
如果驱动中要支持select的接口调用,那么需要做哪些事情呢?如果理解了上文中的内容,你会毫不犹豫的大声说出以下几条:
示例代码中的逻辑:
#include <linux/init.h>
#include <linux/module.h>#include <linux/poll.h>#include <linux/wait.h>#include <linux/cdev.h>#include <linux/mutex.h>#include <linux/slab.h>#include <asm/ioctl.h>#define POLL_DEV_NAME "poll"
#define POLL_MAGIC 'P'
#define POLL_SET_COUNT (_IOW(POLL_MAGIC, 0, unsigned int))
struct poll_dev {
struct cdev cdev;
struct class *class;
struct device *device;
wait_queue_head_t wq_head; struct mutex poll_mutex;
unsigned int count; dev_t devno;};struct poll_dev *g_poll_dev = NULL;
static int poll_open(struct inode *inode, struct file *filp)
{ filp->private_data = g_poll_dev; return 0;
}static int poll_close(struct inode *inode, struct file *filp)
{ return 0;
}static unsigned int poll_poll(struct file *filp, struct poll_table_struct *wait)
{ unsigned int mask = 0;
struct poll_dev *dev = filp->private_data;
mutex_lock(&dev->poll_mutex); poll_wait(filp, &dev->wq_head, wait); if (dev->count > 0) {
mask |= POLLIN | POLLRDNORM; /* decrease each time */
dev->count--; } mutex_unlock(&dev->poll_mutex); return mask;
}static long poll_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg){ struct poll_dev *dev = filp->private_data;
unsigned int cnt; switch (cmd) { case POLL_SET_COUNT: mutex_lock(&dev->poll_mutex); if (copy_from_user(&cnt, (void __user *)arg, _IOC_SIZE(cmd))) {
pr_err("copy_from_user fail:%dn", __LINE__);
return -EFAULT;
} if (dev->count == 0) {
wake_up_interruptible(&dev->wq_head); } /* update count */
dev->count += cnt; mutex_unlock(&dev->poll_mutex); break;
default: return -EINVAL;
} return 0;
}static struct file_operations poll_fops = {
.owner = THIS_MODULE, .open = poll_open, .release = poll_close, .poll = poll_poll, .unlocked_ioctl = poll_ioctl, .compat_ioctl = poll_ioctl,};static int __init poll_init(void)
{ int ret; if (g_poll_dev == NULL) {
g_poll_dev = (struct poll_dev *)kzalloc(sizeof(struct poll_dev), GFP_KERNEL);
if (g_poll_dev == NULL) {
pr_err("struct poll_dev allocate failn");
return -1;
} } /* allocate device number */
ret = alloc_chrdev_region(&g_poll_dev->devno, 0, 1, POLL_DEV_NAME);
if (ret < 0) {
pr_err("alloc_chrdev_region fail:%dn", ret);
goto alloc_chrdev_err; } /* set char-device */
cdev_init(&g_poll_dev->cdev, &poll_fops); g_poll_dev->cdev.owner = THIS_MODULE; ret = cdev_add(&g_poll_dev->cdev, g_poll_dev->devno, 1);
if (ret < 0) {
pr_err("cdev_add fail:%dn", ret);
goto cdev_add_err; } /* create device */
g_poll_dev->class = class_create(THIS_MODULE, POLL_DEV_NAME); if (IS_ERR(g_poll_dev->class)) {
pr_err("class_create failn");
goto class_create_err; } g_poll_dev->device = device_create(g_poll_dev->class, NULL, g_poll_dev->devno, NULL, POLL_DEV_NAME); if (IS_ERR(g_poll_dev->device)) {
pr_err("device_create failn");
goto device_create_err; } mutex_init(&g_poll_dev->poll_mutex); init_waitqueue_head(&g_poll_dev->wq_head); return 0;
device_create_err: class_destroy(g_poll_dev->class);class_create_err: cdev_del(&g_poll_dev->cdev);cdev_add_err: unregister_chrdev_region(g_poll_dev->devno, 1);
alloc_chrdev_err: kfree(g_poll_dev); g_poll_dev = NULL; return -1;
}static void __exit poll_exit(void)
{ cdev_del(&g_poll_dev->cdev); device_destroy(g_poll_dev->class, g_poll_dev->devno); unregister_chrdev_region(g_poll_dev->devno, 1);
class_destroy(g_poll_dev->class); kfree(g_poll_dev); g_poll_dev = NULL;}module_init(poll_init);module_exit(poll_exit);MODULE_DESCRIPTION("select/poll test");
MODULE_AUTHOR("LoyenWang");
MODULE_LICENSE("GPL");
测试代码逻辑:
#include <stdio.h>
#include <string.h>
#include <fcntl.h>#include <pthread.h>#include <errno.h>#include <unistd.h>#include <sys/ioctl.h>#include <sys/stat.h>#include <sys/types.h>#include <sys/time.h>static void *set_count_thread(void *arg){ int fd = *(int *)arg;
unsigned int count_value = 1;
int loop_cnt = 20;
int ret;
while (loop_cnt--) { ret = ioctl(fd, NOTIFY_SET_COUNT, &count_value); if (ret < 0) {
printf("ioctl set count value fail:%sn", strerror(errno));
return NULL;
} sleep(1);
} return NULL;
}int main(void)
{ int fd;
int ret;
pthread_t setcnt_tid; int loop_cnt = 20;
/* for select use */
fd_set rfds; struct timeval tv;
fd = open("/dev/poll", O_RDWR);
if (fd < 0) {
printf("/dev/poll open failed: %sn", strerror(errno));
return -1;
} /* wait up to five seconds */
tv.tv_sec = 5;
tv.tv_usec = 0;
ret = pthread_create(&setcnt_tid, NULL, set_count_thread, &fd); if (ret < 0) {
printf("set_count_thread create fail: %dn", ret);
return -1;
} while (loop_cnt--) { FD_ZERO(&rfds); FD_SET(fd, &rfds); ret = select(fd + 1, &rfds, NULL, NULL, &tv);
//ret = select(fd + 1, &rfds, NULL, NULL, NULL);
if (ret == -1) {
perror("select()");
break;
}
else if (ret)
printf("Data is available now.n");
else {
printf("No data within five seconds.n");
}
}
ret = pthread_join(setcnt_tid, NULL);
if (ret < 0) {
printf("set_count_thread join fail.n");
return -1;
}
close(fd);
return 0;
}