【linux】高级IO,I/O多路转接之epoll,接口和原理讲解
一、前置知识二、epoll接口epoll_createepoll_waitepoll_ctl三、原理讲解四、epoll的优点五、epoll的接口和epoll模型关联讲解
·
小编个人主页详情<—请点击
小编个人gitee代码仓库<—请点击
linux系统编程专栏<—请点击
linux网络编程专栏<—请点击
倘若命中无此运,孤身亦可登昆仑,送给屏幕面前的读者朋友们和小编自己!
目录
前言
【linux】高级IO,I/O多路转接之poll,接口和原理讲解,poll版本的TCP服务器——书接上文 详情请点击<——,本文会在上文的基础上进行讲解,所以对上文不了解的读者友友请点击前方的蓝字链接进行学习
本文由小编为大家介绍——【linux】高级IO,I/O多路转接之epoll,接口和原理讲解
一、前置知识
- 要讲解epoll,那么离不开poll,那么虽然poll解决了select的两个硬伤,即fd有上限,以及在调用的时候需要对关心的fd上的事件进行重置,但是poll还有效率的问题
- 即poll的效率还是不够极致,因为poll中需要用户维护关心fd的数组_event_fds,要进行大量的遍历,例如在分派器Dispatcher判断_event_fds中已经就绪的fd需要进行遍历数组,在连接管理器Accepter中寻找合适的位置放入连接对应的文件描述符sock的时候同样需要遍历数组,同样的在内核检测fd就绪也需要遍历底层的文件描述符表进而判断对应的fd上的事件是否就绪
- 但是小编,这里我有一个疑问,遍历数组的事件复杂度并不高呀,仅仅是O(N),感觉并没有影响效率,那么小编在这里说如果今天要关心的fd有1亿个,乃至10亿个,你poll的接口可以设置的fd没有上限,那么理想情况下,在硬件内存支持的情况下,poll是可以允许关心高达10亿多个fd上的事件的,那么此时进行多次的遍历就很影响效率
- 所以poll也是有硬伤的,那么poll的硬伤如何优化呢?那么就要使用到本文小编讲解的epoll这个多路转接方案了,下面我们来讲解一下多路转接方案之epoll
二、epoll接口
- epoll的接口有三个,分别是epoll_create,epoll_wait,epoll_ctl,下面我们来依次认识一下
epoll_create

- epoll_create用于创建epoll模型,然后返回epoll模型对应的文件描述符,对于epoll_create的参数已经被忽略,我们可以传入任意int类型的值,如果epoll_create的返回值大于等于0,那么表示创建epoll模型成功,否则就会返回-1表示创建epoll模型失败
- 我们知道linux下一切皆文件,所以系统会将epoll模型和文件打开对象关联,而文件打开对象会和进程的文件描述符表关联起来,文件描述符表中对应文件打开对象的数组下标就是文件描述符epfd,所以epoll_create会返回epoll文件描述符返回给上层
- 所以上层就可以通过文件描述符epfd在文件描述符表中找到和epoll模型关联的文件打开对象,所以此时文件打开对象就可以通过指针找到epoll模型
- 那么关于epoll模型是什么,以及这些具体的流程,小编会在第三点的原理讲解中进行讲解
epoll_wait

- epoll也是多路转接的一种方案,所以也就意味着epoll可以一次等待多个文件描述符,而epoll_event就可以将epoll一次等待的多个已经就绪的文件描述符从epoll模型中连续获取上来,所以此时也就不需要用户层和内核层去进行频繁的遍历,所以poll的效率这个硬伤问题也就可以被epoll解决,关于epoll如何进行处理的,小编会在epoll的原理进行讲解,别忘了要找到epoll模型就需要使用到对应的文件描述符epfd,所以如上图,epoll_create的第一个参数是epoll_create的返回值对应的文件描述符epfd我们也就可以理解了
- 那么对于epoll_create的第二个参数表面上是一个指针,实际上是一个数组,那么这个数组有多少个可以从epoll_create的返回值获悉,那么我们来看这个数组中对象的类型struct epoll_event,这个结构体类型中有两个成员变量,第一个成员变量是uint32_t的events,而这个events是使用位图的形式传递标志位
- 所以,那么用户传入的时候是关心某事件,例如关心读事件,关心写事件等,那么内核传出的时候是某时间就绪,例如读事件就绪,写事件就绪等,那么这个uint32_t类型的events都可以通过位图的形式传递什么标识位对应的事件呢?如下
(一)EPOLLIN,表示对应的文件描述符可以读,或者对端的调用close正常关闭连接
(二)EPOLLOUT,表示对应的文件描述符可以写
(三)EPOLLPRI,表示对应的文件描述符上有紧急的数据可读
(四)EPOLLERR,表示对应的文件描述符发生错误,此时我们需要关闭这个fd
(五)EPOLLHUP,表示对应的文件描述符被挂断,此时我们需要关闭这个fd
(六)EPOLLET,将epoll的工作模式由默认的LT(Level Triggered)水平触发模式,设置为ET(Edge Triggered)边缘触发模式,关于LT和ET的相关讲解,小编会在下一篇的Reactor中进行讲解
(七)EPOLLONTSHOT,只监听一次事件,当监听完这次事件后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到epoll模型中
- 那么在看完struct epoll_event中的第一个成员变量events之后,下面我们来看第二个成员变量epoll_data_t类型的data,这个data用于存储用户的自定义数据,那么我们看上图,epoll_data_t这个类型是一个联合体union
- 在这个类型中我们只关心int类型的文件描述符fd,所以当我们拿到一个struct epoll_event的时候,即可通过epoll_data_t类型的data找到是哪一个文件描述符fd,也可以通过uint32_t类型的events得知这个文件描述符上的什么事件就绪了
- 那么小编,小编,既然epoll_wait的第二个参数events可以以数组的形式返回已经就绪的fd和event,并且数组中有多少个成员我们可以通过epoll_wait的返回值得知,那么我们还要epoll_wait的第三个参数maxevents有什么用呢?maxevents表示本次调用epoll_wait处理的最大事件就绪fd的个数
- epoll可以一次等待多个文件描述符,那么如果本次epoll底层一次等待了有520个文件描述符上的事件都就绪了,可是上层一次最大限度并且期望仅处理50个,所以此时我们就可以将epoll_wait的第三个参数maxevents设置为50,那么此时epoll_wait返回值就会为50,并且epoll_wait的第二个参数events中也只会包含50个struct epoll_event对象,那么在epoll的默认处于的LT模式下,处理完成本次就绪的50个fd上的事件后,余下的470个fd上的事件会在下次继续进行处理,那么由于maxevents设置为50,所以下次也会只处理50个,以此重复
- 那么等到最后,epoll底层等待的已经就绪的fd只有20个了,那么此时maxevents仍然为50,那么将epoll_wait的第三个参数maxevents以及epoll_wait返回值会为什么呢?别忘了maxevents即最大处理事件的个数,也就代表着处理的事件个数可以小于等于这个maxevents,所以此时将epoll_wait的第三个参数maxevents设置为20,那么此时epoll_wait返回值就会为20

- epoll_wait的最后一个参数timeout是超时时间,如果这个超时时间同poll的超时时间,超时时间timeout的单位为毫秒us,1秒等于1000毫秒,所以如果我们想要设置epoll的超时时间为3秒,所以就应该给timeout传参3000
- epoll也可以支持非阻塞等待,那么我们只需要将超时时间timeout设置为0,epoll也可以支持阻塞等待,那么我们只需要将超时时间timeout设置为负数,例如-1,这些小编会在epoll版本的TCP服务器中进行逐个验证
epoll_ctl

- 在上面我们已经学习了epoll_create用于创建epoll模型,获取epoll对应的文件描述符epfd,epoll_wait用于获取epoll等待的事件就绪的文件描述符fd,所以接下来自然的,我们就要学习向epoll模型中添加,修改,删除文件描述符fd以及对应的事件event
- 所以此时epoll_create就可以实现向epoll模型中添加,修改,删除文件描述符fd以及对应的事件event,那么epoll_create的第一个参数epfd也就是epoll_create的返回值,epoll模型对应的文件描述符epfd
- epoll_create的第二个参数是op,op的类型如上,分别有EPOLL_CTL_ADD添加文件描述符fd以及对应的事件event,EPOLL_CTL_MOD修改文件描述符fd以及对应的事件event,EPOLL_CTL_DEL删除文件描述符fd以及对应的事件event
- 那么epoll如何才能得知要op操作哪个文件描述上的那个事件呢? 那么就是通过epoll_ctl的第三个参数fd和第四个参数event,那么第三个参数fd就是告知epoll本次要op操作的是fd这个文件描述符,第四个参数event就是告诉epoll本次要op操作的是event,即fd上的这个事件
- 如果epoll_ctl进行op操作设置文件描述符fd和事件event成功,那么epoll_ctl就会返回0,如果操作失败,那么就会返回-1并且设置错误码
- 下面小编来讲解一下epoll的原理,学习epoll模型,当epoll的原理讲解完成之后,小编会将epoll的接口对照到epoll的原理中帮助大家理解,epoll的接口究竟在epoll模型中做了什么
三、原理讲解
- epoll模型分为三个机制,红黑树机制,就绪队列机制,网卡驱动回调机制,在正式讲解epoll的原理前,请大家先学习select,详情请点击<——和poll,详情请点击<——,有了select和poll的前置性铺垫之后我们理解epoll的原理更为容易,下面小编逐一讲解一下epoll模型的三个机制

- 那么第一个是红黑树机制,我们回顾一下上面的select和poll,这两个多路转接方案为了便于管理fd都在用户层建立了一个第三方数组,那么epoll实际上是不需要我们自己在用户层建立第三个数组的,而是由内核层建立一个红黑树用于维护并管理fd,关于红黑树的讲解,详情请点击<——
- 我们知道我们是为了维护fd,fd即文件描述符,文件描述符本质是文件描述符表这个数组的下标,而数组的下标是连续的,不重复的,0,1,2,3,4……,那么此时我们就完全可以让fd当做红黑树节点的键值key,让上图的struct rb_node当做value,所以此时我们就可以给每一个用户要关心的fd及其对应事件都构建一个红黑树节点

- 那么第二个机制是就绪队列机制,处于红黑树的节点能否也处于队列中呢?完全可以,我们在节点中增加指针链接字段就可以,所以当检测到红黑树上的fd的event事件就绪之后,就可以将这个节点连接到就绪队列中即可,这个就绪队列是一个双链表的形式关于双链表,详情请点击<——
- 所以就绪队列中的fd上的event事件全部都是就绪的,所以未来用户想要获取就绪的fd就只需要从就绪队列中获取就绪节点即可,所以从此以后epoll寻找就绪的fd并不需要select和poll一样遍历查找就绪的fd

- 接下来我们看epoll的第三个机制网卡驱动回调机制,那么当网卡上检测到有数据的时候,网卡就会发生硬件中断 关于硬件中断在键盘数据如何输入给内核,ctrl + c如何变为信号的——谈谈硬件中进行的讲解,详情请点击<——,网卡发生硬件中断,就会告诉系统,我网卡上有数据了,所以此时网卡驱动就会调用回调函数将网卡的数据读上去
- 即网卡驱动将数据向上交付,交付到tcp的接收队列中关于tcp的接收队列在打通文件和socket的关系中进行的讲解,详情请点击<——,当数据到了tcp的接受队列中之后,tcp的接收队列是处于sock中,那么sock是socket的成员变量,并且在socket中有指针回指向上层的文件对象struct file,所以操作系统自然可以知道这个数据是发送给哪一个文件描述符fd的
- 那么此时再向操作系统维护的红黑树中根据fd查找这个fd对应的关心的event上有没有关心读事件,如果关心了,那么代表此时fd上的event上的读事件已经就绪了,所以此时就会将红黑树的fd对应的节点链接到就绪队列中去,所以上层如果想要获取就绪的fd就从就绪队列获取就绪节点即可
- 所以如上就是epoll模型的三个机制,即红黑树机制,就绪队列机制,网卡驱动的回调机制
- 如果今天的进程调用多个epoll_create那么就创建出了多个epoll模型,所以此时在系统中就有了多个epoll模型,那么系统要不要管理多个epoll模型,要,如何管理?先描述,再组织
struct eventpoll
{
....
/*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/
struct rb_root rbr;
/*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/
struct list_head rdlist;
....
};
- 所以当我们每次调用epoll_create的时候,就会在内核层面创建一个struct eventepoll的结构体将epoll模型描述起来,那么在这个结构体的成员就包括有红黑树以及就绪队列等字段,然后再将这个struct eventepoll和文件对象struct file关联起来,即让struct file中的指针指向struct eventpoll即可,然后我们使用数组将文件对象struct file组织起来即可,所以从此以后管理多个epoll模型就只需要管理好fd对应的struct eventepoll即可
四、epoll的优点
- 所以此时在了解了epoll的原理之后,下面我们就可以来总结一下epoll的优点了
- 检测是否有fd上的事件就绪的时间复杂度是O(1),那么只需要判断就绪队列是否为空即可,如果就绪队列为空,那么代表此时fd上的事件没有就绪的,如果就绪队列不为空,那么代表此时fd上的事件有就绪的,所以我们才可以说检测fd上的事件是否就绪的时间复杂度是O(1)

- 获取就绪的fd的时间复杂度是O(N),在epoll的第三个参数maxevents大于就绪队列的节点个数的情况下,我们获取就绪的fd要从就绪队列中进行获取,就绪队列的节点有N个,这个N始终不超过maxevents,所以获取获取就绪的fd的时间复杂度是O(N)
- epoll中的fd,event没有上限,你用户无论要关心多少的fd,event,只需要向epoll模型的红黑树中添加即可,操作系统就可以自动完成epoll模型的三个机制进行关心fd上的event事件
- epoll_wait的返回值n,表示有几个fd的event事件就绪了,即就绪事件是连续的,就绪事件有返回值n个
五、epoll的接口和epoll模型关联讲解
- 此时我们已经学习的epoll的三个接口,分别是epoll_create,epoll_wait,epoll_ctl,并且我们也已经学习了epoll模型,这三个epoll的接口本质就是在操作epoll模型,下面小编将epoll的接口和epoll模型关联讲解,帮助大家更好的理解epoll的原理

- 首先我们来看epoll_create,那么这个接口是创建epoll模型,那么就是先创建空红黑树,创建空就绪队列,将回调函数注册到网卡驱动中即可,然后再将空的红黑树和空的就绪队列放到struct eventepoll中描述起来,然后再将这个struct eventepoll和文件对象struct file关联起来,即让struct file中的指针指向struct eventpoll即可
- 文件对象需要放到文件描述符表中,然后epoll_create会将这个文件对象在文件描述符表数组的下标epfd返回让上层拿到,所以此时epoll模型的三个机制已经准备好了,即epoll模型就已经创建出来了
- 那么从此以后,如果我们想要找到epoll模型,那么只需要拿着epoll_create返回的文件描述符epfd从文件描述符表中找到文件对象,文件对象中有指针指向描述epoll模型struct eventepoll结构体的指针,所以此时就可以找到描述epoll模型struct eventepoll结构体,自然也就找到了struct eventepoll的成员变量红黑树和就绪队列进行访问和操作了

- 接下来我们看epoll_wait,那么就是根据epfd找到epoll模型中的就绪队列,先判断就绪队列是否为空,如果为空,那么代表此时没有就绪的fd,如果就绪队列不为空说明此时有就绪的fd,那么就直接从就绪队列中拿就绪节点即可,然后以返回值n的形式返回拿的就绪节点的个数,这个拿的就绪节点的个数不能超过第三个参数maxevents,可以小于等于maxevents
- 接下来我们看epoll_ctl,那么就是根据epfd找到epoll模型中的红黑树,那么就可以根据参数中的fd和event以及要进行的操作op对红黑树的节点进行新增,修改和删除操作
总结
以上就是今天的博客内容啦,希望对读者朋友们有帮助
水滴石穿,坚持就是胜利,读者朋友们可以点个关注
点赞收藏加关注,找到小编不迷路!
更多推荐



所有评论(0)