IO多路复用之select/poll/epoll总结

机制介绍

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 <sys/epoll.h>
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对象。剩下内容参考这篇博文。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值