Select、poll、epoll
首先这三个函数的作用都是进行I/O复用,最早被使用的是select。Select的原理是用位数组存储要监听的套接字,然后将位数组发送给内核监听起来。但是基于它的实现,select有以下缺点:
1、监听的描述符很有限,1024
2、监听的事件也只有读、写、异常
3、每次有数据就绪,内核就将其对应的位置为1,表示有数据就绪,然后内核需要将整个位数组发送给用户,用户遍历位数组寻找有数据就绪的描述符,并做相应处理,做了处理之后又得将为数据发送给内核监听起来。这样存在着大量的换入换出,效率低下。
Poll对epoll做了一些改进,poll将每一个需要监听的描述符都存在一个结构体中,并申请一个结构体数组来存储所有需要监听的描述符结构体,注意每个结构体中可以声明需要监听的事件,poll中可以监听的事件就不止读写异常了。
Poll解决了监听事件限制的问题,并且舍弃位数组,可以监听的描述符数量得到了提高,但是最主要的问题没有解决,在监听时还是存在大量的换入换出,效率不高。
Epoll则对效率问题做了解决。Epoll有三个函数接口:
(1)epoll_create
Epoll_create的功能时创建出一个内核事件表,实际上就是创建文件,这其中就包括文件描述符的分配、文件实体的分配等,文件描述符中有一个private_data域,该域是epoll的核心,这个域中存有内核事件表、就绪描述符队列等信息。
(2)epoll_ctl
这个函数的功能是对内核事件表的操作,有插入(添加文件描述符)、删除(删除被监听的描述符)、修改(修改被监听的描述符)。该函数的主要步骤是:
1、遍历内核事件表,看所要操作的描述符是否在内核事件表中
2、判断所要做的操作是什么?插入、删除、修改。
3、根据操作做相应的处理。
这里最主要的一点是插入文件描述符的时候,对描述符注册了回调函数,当该描述符上有数据就绪时自动调用回调函数将该描述符加入到就绪队列。
(3)epoll_wait
首先内核事件表的底层数据结构是红黑树,因为红黑树具有查找效率高的特点。就绪队列的底层数据结构是链表。
Epoll_wait的功能就是不断查看就绪队列中有没有描述符,如果没有就一直检查,直到超时。如果有就将就绪描述符通知给用户。
LT和ET模式的实现就在将就绪描述符通知给用户的过程中。就绪队列是一个链表,在给用户发送时还有一个链表l,首先将就绪队列链表中的描述符断链交给另一个链表l,用户读取l链表中的描述符,如果是Lt模式,用户没有读完链表l上的描述符,内核则将链表中未读取的描述符加回到就绪队列中,这样就可以做到重复通知用户处理数据;如果是et模式,内核则不做处理,这样就实现了只通知一次给用户的高效模式。
来源:https://blog.csdn.net/qq_42438552/article/details/98996883