read the fucking source code!
--by 鲁迅a picture is worth a thousand words.
--by 高尔基linux系统在访问设备的时候,存在以下几种io模型:
blocking io model,阻塞io模型
;nonblocking i/o model,非阻塞io模型
;i/o multiplexing model,io多路复用模型
;signal driven i/o model,信号驱动io模型
;asynchronous i/o model,异步io模型
;今天我们来分析下io多路复用机制,在linux中是通过select/poll/epoll
机制来实现的。
先看一下阻塞io模型与非阻塞io模型的特点:
对单个设备io操作时,问题并不严重,如果有多个设备呢?比如,在服务器中,监听多个client的收发处理,这时候io多路复用就显得尤为重要了,来张图:
如果这个图,让你有点迷惑,那就像个男人一样,man
一下select/poll
函数吧:
select
:
poll
简单来说,select/poll
能监听多个设备的文件描述符,只要有任何一个设备满足条件,select/poll
就会返回,否则将进行睡眠等待。
看起来,select/poll
像是一个管家了,统一负责来监听处理了。
已经迫不及待来看看原理了,由于底层的机制大体差不多,我将选择select
来做进一步分析。
从select
的系统调用开始:
select
系统调用,最终的核心逻辑是在do_select
函数中处理的,参考fs/select.c
文件;do_select
函数中,有几个关键的操作:
poll_wqueues
结构,包括几个关键函数指针的初始化,用于驱动中进行回调处理;f_op->poll()
函数,如果有监测条件满足,则会跳出循环;poll_schedule_timeout
让当前进程进行睡眠,超时唤醒,或者被所属的等待队列唤醒;do_select
函数的循环退出条件有三个:
poll()
函数,会在do_select()
中被调用,而驱动中的poll()
函数,需要调用poll_wait()
函数,poll_wait
函数本身很简单,就是去回调函数p->_qproc()
,这个回调函数正是poll_initwait()
函数中初始化的__pollwait()
;所以,来看看__pollwait()
函数喽。
__pollwait
poll_wait
函数回调__pollwait
,这个函数完成的工作是向struct poll_wqueue
结构中添加一条poll_table_entry
;poll_table_entry
中包含了等待队列的相关数据结构;pollwake
;wake_up_interruptile
等接口来唤醒处理;这一顿操作,其实就是驱动向select
维护的struct poll_wqueue
中注册,并将调用select
的任务添加到驱动的等待队列中,以便在合适的时机进行唤醒。所以,本质上来说,这是基于等待队列的机制来实现的。
是不是还有点抽象,来看看数据结构的组织关系吧。
select
系统调用的进程/线程,会维护一个struct poll_wqueues
结构,其中两个关键字段:
pll_table
:该结构体中的函数指针_qproc
指向__pollwait
函数;struct poll_table_entry[]
:存放不同设备的poll_table_entry
,这些条目的增加是在驱动调用poll_wait->__pollwait()
时进行初始化并完成添加的;如果驱动中要支持select
的接口调用,那么需要做哪些事情呢?
如果理解了上文中的内容,你会毫不犹豫的大声说出以下几条:
wait_queue_head_t
,用于收留等待队列任务;struct file_operations
结构体中的poll
函数需要实现,比如xxx_poll()
;xxx_poll()
函数中,当然不要忘了poll_wait
函数的调用了,此外,该函数的返回值mask
需要注意是在条件满足时对应的值,比如epollin/epoll/epollerr
等,这个返回值是在do_select()
函数中会去判断处理的;wake_up_interruptible
唤醒任务,当然也可以使用wake_up
,区别是:wake_up_interruptible
只能唤醒处于task_interruptible
状态的任务,而wake_up
能唤醒处于task_interruptible
和task_uninterruptible
状态的任务;select/poll
的差异select
与poll
本质上基本类似,其中select
是由bsd unix
引入,poll
由systemv
引入;select
与poll
需要轮询文件描述符集合,并在用户态和内核态之间进行拷贝,在文件描述符很多的情况下开销会比较大,select
默认支持的文件描述符数量是1024;epoll
机制,改进了select
与poll
在效率与资源上的缺点,未深入了解;示例代码中的逻辑:
ioctl
来进行设置;#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:%d\n", __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 fail\n"); 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:%d\n", 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:%d\n", 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 fail\n"); 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 fail\n"); 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");
测试代码逻辑:
select
函数监听,当设值线程设置了count值后,select便会返回;#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:%s\n", 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: %s\n", 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: %d\n", 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; }
如对本文有疑问, 点击进行留言回复!!
linux下文本编辑器vim的使用方法(复制、粘贴、替换、行号、撤销、多文件操作)
网友评论