IO复用
IO 复用也称多路 IO 就绪通知,这是一种进程预先告知内核的能力,让内核发现进程指定的一个或多个 IO 条件就绪了,就通知进程。IO 复用的实现方式目前主要有 select,poll 和 epoll。
IO复用典型使用场景
Select
该函数允许进程指定内核等待多个事件中的任何一个发生,并只有在一个或多个事件发生或经历一段指定的时间后才唤醒它。
传给select的参数告诉内核
(1)进程关心的描述符
(2)对于每个描述符进程关心的条件条件(是否想从一个给定的描述符读,是否想写一个给定的描述符,是否关心一个给定描述符的异常条件)
(3)愿意等待多长时间(可以永远等待,等待一个固定的时间或根本不等待)
从select返回时,内核告诉我们:
(1)已准备好的描述符的总数量
(2)对于读,写/异常这3个条件中的每一个,哪些描述符已经准备好。
select只返回准备好的描述符的个数,并不告知具体哪个文件描述符可以进行IO操作,需要轮询去查看。
Readfs,writefds,exceptfds是指向描述符集的指针。这三个描述符集说明了进程关心的可读,可写或处于异常条件的描述符集合。每个描述符集存储在一个fd_set数据类型中,可以认为它是一个很大的字节数组。
第一个参数maxfdp1是最大的文件描述符编号加1.
select的可能返回值有3个。
select的缺点:
(1)最大支持的描述符有限,一般最大为1024
(2)select的内部操作是等待描述符集合,必须进入内核态,因此必须要把描述符集合拷贝到内核空间,消耗资源。
(3)select返回值如果是正数表示已经准备好的描述符数,内核会把已准备好的描述符集的相应位置置1,其余置0 ,因此进程还需要扫描描述符集,测试哪一个描述符已经准备好再进行对应的IO操作。
(4)每次重新调用select函数时,进程需要把描述符集内所关心的位重新置1。
Poll
poll函数类似于select,但程序接口有所不同。
poll相对于select的优点是解决了描述符的限制问题,但函数返回时,也需要遍历描述符数组来判断描述符的准备状态。
描述符就绪条件
图片
Epoll
(1)epoll与select和poll不同,epoll将监听的文件描述符集存放在内核区,免去了select和poll每次调用都必须要把描述符集从用户空间拷贝到内核空间的消耗。
(原理:epoll通过内核与用户空间映射同一块内存加速信息的传递,在epoll_ctl 中,每次注册新事件时,会把所有的文件描述符拷贝进内核,而不是在epoll_wait时重复拷贝,保证了每个fd在整个过程中只会拷贝一次)。
(2)同时epoll对于已经准备好的文件描述符的处理比较简单。select和poll都需要遍历描述符集来判断描述符的准备状态,但epoll内部维护一个双向链表来存储所有满足条件的事件,遍历这个链表即可判断描述符的准备状态,非常高效。
(原理:)
(3)epoll的监听描述符没有限制,它所支持的文件描述符上限是最大可以打开文件的数目,这个数字远大于2048
(4)epoll还增加了一些新的特性,比如EF(水平触发)和LT(边缘触发)两种触发模式。
(5)IO效率不随文件描述符数目增加而线性下降。
(6)epoll提供了三个函数
Epoll_create/epoll_ctl/epoll_wait
(1)epoll_create
(2)epoll_ctl 注册监听的事件类型
(3) epoll_wait 等待事件的发生
epoll高效是因为内部用一个红黑树记录需要监听的socket,用了一个双向链表接收内核触发的事件。
ET只有在事件从未就绪到就绪这样的状态转换时才被提醒,如果提醒这一次没有被处理,有可能出现问题,即阻塞,所以ET一般与非阻塞搭配。且必须使用非阻塞。
以避免由于一个文件句柄的阻塞读/写操作把处理多个文件描述符的任务饿死。
Epoll的ET模式和LT模式
Linux下的I/O复用与epoll详解 - junren - 博客园
Epoll模式下如何保证数据读完或写完?
彻底学会使用epoll(五)—— ET模式下的注意事项_fei的专栏-CSDN博客
EPOLLONESHOT
epoll的LT和ET使用EPOLLONESHOT_迷路的专栏-CSDN博客