0%

select-poll-epoll比较

本小结源于B站freecoder同学的慷慨分享, 连接地址:

freecoder-IO多路复用select/poll/epoll介绍

1. select

image-20200910115128372

1.1 关键词描述

fds=(文件描述符集合)监听的文件描述符的数组

max=监听的fds中->最大的文件描述符值

fdset/rset=bitmap[1024]=select函数的入参rset

select函数= select(max+1, &rset, &wset, &eset, timeout )

rset=读fds; wset=写fds; eset=异常的fds,

1.2 select存在的4个问题(缺点):

1. select的rset的1024个fd的限制;

bitmap默认大小: 1024个位, 只能处理1024个fds

2. select的rset不可重用;

fdset不可重用->每次都要赋值一个新的(传参rset);

3. select的rset拷贝:用户态<–>内核态的切换开销;

4. select的rset在内核置位后返回, 检查哪些fd置1, 仍需要O(n)遍历;

2. poll

image-20200910115500446

poll引入了pollfd结构体(fd+events+revents)解决了select的前两个问题

poll函数=poll()

2.1 没有最大文件描述符数量的限制

不使用固定的1024大小的bitmap数组, 而是使用结构体数组;

2.2 pollfds结构体数组只改变revents的状态, 结构体可重用

比select优秀的是, select的fdset/rset不可重用; pollfds可以重用, 只是有数据准备好可读时, 将置位revents字段为POLLIN

2.3 问题3-用户态-内核态的切换仍存在, 没有解决;

2.4 问题4-返回后仍需要遍历结构体数组, O(n)问题仍存在;

3. epoll

image-20200910144711218

epoll解决了select的四个问题;

后两个问题:

3.1 epfd是用户态和内核态共享的, 不存在赋值的问题

3.2 数据在内核标识置位后, 增加了重排操作, ready的fd都会排前,epoll返回时会返回ready的size;

4. epoll的扩展


4.1 epoll函数

4.1.1 epoll_create: 创建一个白板 存放fd_events

4.1.2 epoll_ctl: 向内核注册新的fd或改变某个fd的状态

已注册的fd在内核中会被维护在一棵红黑树上

4.1.3 epoll_wait: 进程调用 epoll_wait() 得到事件完成的fd

通过回调函数内核会将 I/O 准备好的fd加入到一个链表中管理,进程调用 epoll_wait() 便可以得到事件完成的描述符

4.2 epoll的2种工作模式

4.2.1 LT=level trigger=水平触发

当epoll_wait检测到fd事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。

下次调用epoll_wait时,会再次响应应用程序并通知此事件。

是默认的一种模式,并且同时支持 Blocking 和 No-Blocking

4.2.2. ET=edge trigger=边缘触发

当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。

如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。

LT 模式不同的是,通知之后进程必须立即处理事件。下次再调用 epoll_wait() 时不会再得到事件到达的通知。

ET: 很大程度上减少了 epoll 事件被重复触发的次数,因此效率要比 LT 模式高。

只支持 No-Blocking,必须使用非阻塞套接口, 以避免由于一个fd的阻塞读/阻塞写操作把处理多个fd的任务饿死。