博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
IO多路复用之select/poll/epoll总结
阅读量:7237 次
发布时间:2019-06-29

本文共 3231 字,大约阅读时间需要 10 分钟。

机制介绍

IO multiplexing就是我们说的select,poll,epoll,有些地方也称这种IO方式为event driven IO。select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。I/O多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,这里的复用指的是复用一个进程(或线程)。

select

  • 函数原型:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);返回值:就绪描述符的数目,超时返回0,出错返回-1复制代码

参数说明:

nfds: 要监听的文件描述符的范围, 通常使用select监听的所有描述符的最大值加1。

readfds: 要监听是否有可读事件的文件描述符集
writefds:要监听是否有可写事件的文件描述符集
exceptfds:要监听是否有异常事件的文件描述符集
timeout: 阻塞时间

  • 实现原理:
    理解select I/O复用模型的关键点在于 fd_set,这是一个整形数组,数组的每个成员的每一位都表示一个文件描述符。 在调用select的时候,用户空间将所有要检测的各种事件的文件描述符以fd_set 整型数组的形式传递给内核空间。 内核空间通过轮询监听的描述符集判断是否有监听事件发生。如果某个描述符上有对应的监听事件发生,则该描述符对应的位置1, 跳出阻塞,执行select函数之后的代码; 否则,select继续阻塞,CPU资源让出给其他进程。
    这里牵涉到两次copy,一是从用户态拷贝到内核态,内容为所有监听的文件描述符资源,一是从内核态拷贝到用户态,内容为有事件发生的文件描述符资源。
  • 缺点:
    1、每一次执行select函数都要重新向内核空间传递表示监听描述符集的整型数组。
    2、select模型使用整型数组来描述要监听的描述符集,因为它能监听的描述符的个数是有限的,默认32位1024,64位2048个。
    3、select模型在检测是否有事件发生时,需要轮询所有的监听描述符,效率较低。FD越多开销越大。

poll

poll模型在实现原理上和select模型差不多,只是在对监听描述符集的描述上有所差异。 poll模型使用struct pollfd结构体描述要监听的描述符集。没有了1024数量限制。

从性能或者是系统开销上看,poll和select的差别不大,目前使用select模型的服务器较多,相比之下poll的使用并不广泛。

epoll

  • 函数原型:
#include 
int epoll_create(int size); //int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);复制代码

这组函数的作用如下:

epoll_create用于创建一个额外的文件描述符,该描述符用语标识服务器要监听的事件在内核中对应的事件表。

epoll_ctl 用于操作参数epfd(由之前的epoll_create函数生成)对应的内核事件表,如向事件表中注册、修改、删除事件。
epoll_wait是epoll模型的主要函数,它用于(在超时时间timeout内)阻塞监听是否有我们关注(注册)的事件发生。

  • 实现原理
    用户空间通过epoll_ctl函数讲要监听的的各个描述符的事件传递给内核空间。
    另外,epoll_ctl会为每个描述符指定回调函数,当该描述符上有事件发生时,会调用回调函数,将文件描述符添加到一个就绪队列中去。
    epoll_wait用于阻塞监听各个文件描述符是否有监听的事件发生,因为在epoll_ctl中我们为每个要监听的描述符指定了回调函数,因此epoll_wait不需要轮询检测各个文件描述符了,只需要判断就绪队列是否为空即可。
  • 优点
    相比select和poll,优点如下:

1、如果有新的文件描述符需要加入监听队列中,只需要针对该描述符执行epoll_ctl的add即可,不用将所有的文件描述符都重新拷贝至内核空间,大大提高了效率。

2、epoll模型并非像select模型那样使用整型数组来表示监听的描述符集,因而能够监听的文件描述符较大,理论上只与内存大小有关。
3、select模型在阻塞监听时需要轮询每个监听的文件描述符,而epoll只需要判断就绪队列是否为空。相比之下,在没有监听的事件发生时,epoll能更早地让出系统资源,让CPU调度运行其他进程。

  • epoll的水平触发(LT)和边缘触发(ET)

LT模式下,只要内核缓冲区中还有未读数据,就会一直返回描述符的就绪状态,即不断地唤醒应用进程,即每次执行epoll_wait都会直接返回

在ET模式下, 缓冲区从不可读变成可读,会唤醒应用进程,缓冲区数据变少的情况,则不会再唤醒应用进程。因此需要应用程序自身做好处理,保证每次read一个fd的时候一定要把它的buffer读光,也就是说一直读到read的返回值小于请求值,或者遇到EAGAIN错误。

  • 源码讲解
    参考文章: 讲解的非常透彻!
    其中有一个很重要的结构体如下:
struct eventpoll {	/* Protect the access to this structure */	spinlock_t lock;	struct mutex mtx;	/* 用于收集调用了epoll_wait()系统调用的用户态应用程序 */	wait_queue_head_t wq;	/* Wait queue used by file->poll() */	wait_queue_head_t poll_wait;	/* 用于收集已经就绪了的item对象 */	struct list_head rdllist;	/* 用来挂载struct epitem类型对象的红黑树的根 */	struct rb_root rbr;	/*	 * 这个单向链表也是用来收集就绪了item对象的,那这个成员什么时候会被使用呢?	 * 这个成员是在对rellist成员进行扫描操作获取就绪事件返还给用户态时被用来存放	 * 扫描期间就绪的事件的。为什么需要这样做呢?因为在对rellist扫描期间需要保证	 * 数据的一致性,如果此时又有新的就绪事件发生,那么就需要提供临时的空间来存	 * 储,所以ovflist就扮演了这个角色。	 */	struct epitem *ovflist;	struct wakeup_source *ws;	struct user_struct *user;	/* struct eventpoll类型对象对应的文件对象 */	struct file *file;	int visited;	struct list_head visited_list_link;}复制代码

struct eventpoll类型的成员很多,到目前为止,我们只需要关注两个成员,一个是类型为struct rb_root的rbr,一个是类型为struct list_head的rdllist。其中rbr成员是一棵红黑树的根节点,这棵树中存放着所有通过epoll_ctl()系统调用添加到epoll中的事件对应的类型为struct epitem的对象;而rdllist链表则存放了将要通过epoll_wait()系统调用返回给用户态应用程序的就绪事件对应的struct epitem对象。剩下内容参考这篇博文。

转载地址:http://mamfm.baihongyu.com/

你可能感兴趣的文章
使用jfreechart导致JSP页面汉字出现乱码
查看>>
Chrome浏览器快捷键hotkey
查看>>
“undefined reference to `vtable for”出现原因
查看>>
Short films
查看>>
我的友情链接
查看>>
技术学习道路的盲区与误区
查看>>
CentOS VI命令自动显示行号
查看>>
Android核心分析28篇,强烈推荐android初学者,android进阶者看看这个系列教程
查看>>
thinkphp缓存的使用
查看>>
Android第三天
查看>>
【将门创投】图像搜索的前世今生,让你买买买停不下来的淘宝是如何做到所拍即所得的?...
查看>>
文件编辑器 vi
查看>>
用VB.NET实现定时关机
查看>>
[转载] New Concept English 1——Lesson 9 How are you today?
查看>>
清除linux系统自带jdk java
查看>>
Scala 函数式编程_偏函数_Partial Functions
查看>>
[转载] 七龙珠第一部——第083话 天下第一武道会
查看>>
Linux 下Nagios的安装和配置
查看>>
Spring学习总结(5)——IOC注入方式总结
查看>>
The Little Prince-12/07
查看>>