epoll过人之处

2024-05-07 07:48
文章标签 epoll 过人之处

本文主要是介绍epoll过人之处,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

select 和 epoll的任务

关键词:应用程序、 文件句柄、 用户态、 内核态、 监控者

要比较epoll 相较select 高效在什么地方,就需要比较二者做相同的事情的方法。
要完成对 I/O 流的复用需要完成如下几个事情:

  • 用户态怎么将文件句柄传递到内核态?

    • select:select创建3个文件描述符集,并将这些文件描述符拷贝到内核中,这里限制了文件句柄的最大数量为1024(注意是全部拷入—>第一次拷入);

    • epoll:首先执行epoll_create 在内核专属于epoll 的高速cache 区,并在该缓冲区建立红黑树和就绪链表,用户态对传入的文件句柄将被放到红黑树中(第一次拷入);

  • 用户态怎么判断 I/O 流可读可写?

    • select:内核针对读缓冲区和写缓冲区来判断是否可读可写,这个动作和select无关;

    • epoll:内核针对读缓冲区和写缓冲区来判断是否可读可写,这个动作与epoll无关;

  • 内核怎么通知监控者有I/O流可读可写?

    • select:内核在检测到文件句柄可读/可写时就产生中断通知监控者select,select被内核触发之后,就返回可读可写的文件句柄总数;

    • epoll:epoll_ctl执行add 动作时除了将文件句柄放到红黑树上之外,还向内核注册了该句柄的回调函数,内核在检测到某句柄可读可写时则调用回调函数, 回调函数将句柄放到就绪链表;

  • 监控者如何找到可读可写的I/O流并传递给用户态应用程序?

    • select:select会将之前传递给内核的文件句柄再次从内核态传到用户态(第二次拷贝),select返回给用户态的只是可读可写的文件内句柄总数,再使用FD_ISSET宏函数来检测哪些文件I/O可读可写(遍历);

    • epoll_wait:epoll_wait只监控就绪链表就可以,如果就绪链表有文件句柄,则表示该文件句柄可读可写,并返回到用户态(少量的拷贝);

  • 继续循环时监控者怎么重复上述步骤?

    • select:select对于事件的监控是建立在内核的修改之上的,也就是说经过一次监控之后,内核会修改位,因此再次监控时需要再次从用户态到内核态进行拷贝(第N次拷贝);

    • epoll:由于内核不修改文件句柄的位,所以只需要第一次传入就可以重复监控,直到epoll_ctl删除,否则不需要重新传入,因此无多次拷贝;

简单说:epoll是继承了select/poll的I/O复用的思想,并在两者基础上从监控I/O流,查找I/O事件等角度来提高效率,具体地说就是内核句柄列表,红黑树、就绪list链表来实现的。

epoll详解

先回顾一下如何使用 C 库封装的3个epoll系统调用吧。

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 对象,参数size 是内核保证能够正确处理的最大句柄数,多于这个最大数内核可不保证效果;

  • epoll_ctl可以操作上面建立的epoll,例如,将刚建立的socket加入到epoll 中让其监控,或者把epoll正在监控的某个socket句柄移出epoll,不再监控它等等(也就是将I/O流放到内核);

  • epoll_wait在调用时,在给定的timeout时间内,当在监控的所有句柄中有事件发生时,就返回用户态的进程(也就是在内核层面捕获可读写的I/O事件);

从上面的调用方式就可以看到 epoll 比 select/poll 的优越之处:

因为后者每次调用时都将传递所要监控的所有socket给 select/poll 系统调用,这意味着需要将用户态的socket列表拷贝到内核态,如果以万计的句柄会导致每次都要拷贝几十几百KB的内存到内核,非常低效。而我们调用epoll_wait 时就相当于以往调用 select/poll,但是这时却不传递socket 句柄给内核,因为内核已经在epoll_ctl 中拿到了要监控的句柄列表;

select监控的句柄列表在内核态,每次调用都需要从用户态将句柄拷贝到内核态,但是epoll中句柄就是建立在内核中的,这样就减少了内核和用户态的拷贝,高效的原因之一

所以,实际在你调用epoll_create后,内核就已经在内核态开始帮你准备要监控的句柄了,每次调用epoll_ctl只是在往内核的数据结构里塞入新的socket句柄;

在内核里,一切皆文件。所以,epoll向内核注册了一个文件系统,用于存储上述的被监控socket,当你调用epoll_create时,就会在这个虚拟的epoll文件系统创建一个file结点,当然这个file不是普通文件,它只服务于epoll。

epoll在被内核初始化时,同时会开辟出epoll自己的内核高速cache区,用于安置每一个我们想监控的socket,这些socket会以红黑树的形式保存在内核cache里,以支持快速的查找、插入、删除。这个内核高速cache区,就是建立连续的物理内存区,然后在之上建立slab层,简单的说,就是物理上分配好你想要的size的内存对象,每次使用时都是使用空闲的已分配号的对象;

epoll高效的原因

这里由于我们在调用epoll_create时,内核除了帮我们在epoll文件系统里建立了file结点,在内核cache里建立了红黑树用于存储以后epoll_ctl传来的socket外,还会建立一个list链表,用于存储就绪的事件;

epoll_wait调用时,仅仅观察这个list链表有没有数据即可。有数据就返回,没有数据就sleep,等到timeout时间到达后即使链表没有数据也返回,所以epoll非常高效。而且,通常情况下即使我们要监控百万计的句柄,大多一次也只返回很少量的准备就绪的句柄而已,所以,epoll_wait仅需要从内核态拷贝少量的句柄到用户态而已;

那么,这个准备就绪list链表是怎么维护的呢?

当我们执行epoll_ctl 时,除了把socket放到epoll文件系统的file对象对应的红黑树上之外,还会给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪list链表里。所以,当一个socket上有数据到了,内核在把网卡上的数据拷贝到内核中后就把socket插入到准备就绪list链表里面;

epoll综合的执行过程

如此,一颗红黑树,一张准备就绪链表,少量的内核cache,就帮我们解决了大并发下的socket处理问题。执行epoll_create时,创建了红黑树和就绪链表,执行epoll_ctl时,如果增加socket句柄,则检查在红黑树中是否存在,存在则立即返回,不存在添加到树干上,然后向内核注册回调函数,用于当中断事件来临时向准备就绪链表中插入数据,执行epoll_wait时,立刻返回准备就绪链表的数据即可。

epoll水平触发和边缘触发的实现

当一个socket句柄上有事件时,内核会把该句柄插入到上面所说的准备就绪list链表,这时我们调用epoll_wait,会把准备就绪的socket拷贝到用户态内存,然后清空准备就绪list链表,最后,epoll_wait干了件事,就是检查这些socket,如果不是ET模式(就是LT模式的句柄了),并且这些socket上确实有未处理的事件时,又把这些句柄放回到刚刚清空的准备就绪链表了,所以,非ET的句柄,只要它上面还有事件,epoll_wait每次都会返回。而ET模式下的句柄,除非有新中断到,即使socket上的事件没有处理完,也是不会次次从epoll_wait返回的。

每个epollfd在内核中有一个对应的eventpoll结构对象.其中关键的成员是一 readylist(eventpoll:rdllist) 和一棵红黑树(eventpoll:rbr)。

一个fd被添加到epoll之后(EPOLL_ADD),内核会为它生成一个对应的epitem结构对象 .epitem 被添加到eventpoll 的红黑树中,红黑树的作用是使用者调用EPOLL_MOD的时候可以快速找到fd 对应的epitem。调用epoll_wait时,将将readylist中的epitem出列,将触发的事件拷贝到用户空间。之后判断epitem是否需要重新添加回readylist。

EPOLLONESHOT

如果epitem被设置为EPOLLONESHOT模式,则当这个epitem上的事件拷贝到用户空间之后,会将这个epitem上的关注事件清空(只是关注事件被清空,并没有从epoll中删除,要删除必须对那个描述符调用EPOLL_DEL),也就是说即使这个epitem上有触发事件,但是因为没有用户关注的事件也不会被重新添加到readylist中。

区别就在于epoll_wait将socket返回到用户态时是否清空就绪链表。

epoll高效的本质

  • 减少用户态和内核态之间的文件句柄拷贝;
  • 减少了对可读可写文件句柄的遍历;

本文参考:

  • epoll原理

深入学习可参考:

  • The Implementation of epoll (1)
  • The Implementation of epoll (2)
  • The Implementation of epoll (3)
  • The Implementation of epoll (4)

这篇关于epoll过人之处的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/966757

相关文章

C++ I/O多路复用 select / poll / epoll

I/O多路复用:在网络I/O中,用 1个或1组线程 管理 多个连接描述符。             如果有至少一个描述符准备就绪,就处理对应的事件             如果没有,就会被阻塞,让出CPU给其他应用程序运行,直到有准备就绪的描述符 或 超时

select、poll、epoll的区别

select、poll、epoll均为linux中的多路复用技术。3种技术出现的顺序是select、poll、epoll,3个版本反应了多路复用技术的迭代过程。我们现在开发网络应用时, 一般都会使用多路复用,很少有用一个线程来监听一个fd的,其中epoll又是最常使用的。关于epoll的实现和常见问题可以参考epoll实现原理和常见问题总结。 当我们在使用epoll的时候,会想当然的认为这种技术

select poll epoll之间的区别比较

select,poll,epoll都是IO多路复用的机制。I/O多路复用就是通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核

记录ssl epoll的tcp socket服务端在客户端断开时崩溃的问题

文章目录 当客户端关闭后,Epoll 的 TCP socket 服务端会收到两次断开事件可能有以下原因及解决方法:原因分析解决方法 问题ssl socket服务端代码出错现象第一次尝试修改正确改法附上客户端代码 记录ssl epoll的tcp socket服务端在客户端断开时接收到多次disconnect事件导致崩溃的问题. 流程:在linux服务器上跑socke服务, 客户端连

select、poll、epoll的原理

目录 1.IO多路复用 2.select原理 3.poll原理 4.epoll原理 5.select、poll、epoll总结 6.epoll原理详解 6.1内核收包的过程 6.2进程调度时的阻塞 6.3再来看一下内核收网络数据的过程 6.4select的原理 6.5epoll的设计原理 6.6补充 6.7总结 1.IO多路复用 IO多路复用就是一个线程同时监

IO多路复用,select、poll、epoll区别

IO多路复用是一种同步IO模型,一个线程监听多个IO事件,当有IO事件就绪时,就会通知线程去执行相应的读写操作,没有就绪事件时,就会阻塞交出cpu。多路是指网络链接,复用指的是复用同一线程。 select fd_set数据结构定义如下,可以看出fd_set是一个整型数组,用于保存socket文件描述符typedef long int __fd_mask;/* fd_set for select

解决android系统唤醒时间偏长------healthd里的epoll以及socket

在healthd中,有一个很好的例子,特地截取下来作为参考: 首先是 static int uevent_fd; static int eventct; static int epollfd; int uevent_open_socket(int buf_sz, bool passcred) {     struct sockaddr_nl addr;     int on = pas

java epoll网络编程

java epoll网络编程 从通信开始 人类社会的发展离不开相互协作,一起围猎、抵御野兽,一起扛起锄头夯地、夯人,再到你与好兄弟之间征战峡谷。在这一切互相协作的背后,都离不开信息的传递,也就是通信。一群人聚在一起,想搞点事情,少不得统一个想法,不然,你往左,他往右,还未开始,便已结束。又或者你抬手想打个招呼,人家还以为你想扇他,率先一个巴掌甩过来,大业未成身先卒,呜呼! 彼此之间的想法要

Linux——IO模型_多路转接(epoll)

目录  0.往期文章 1.epoll的三个接口 1.epoll_create 2.epoll_ctl 结构体 epoll_event 3.epoll_wait 2. epoll的工作原理,和接口对应 1.理解数据到达主机 2.epoll的工作原理  3.基于epoll的TCP服务器(代码)  辅助库 基于TCP的Socket封装 服务器代码 测试  4.epoll的工

Linux下IO多路复用—select,poll,epoll

一.概述 1.IO多路复用介绍   IO多路复用是一种操作系统的技术,用于在单个线程或进程中管理多个输入输出操作。它的主要目的是通过将多个IO操作合并到一个系统调用中来提高系统的性能和资源利用率,避免了传统的多线程或多进程模型中因为阻塞IO而导致的资源浪费和低效率问题。 在IO多路复用中,通常使用的系统调用有 select()、poll()、epoll() 等,它们允许程序等待多个文件描述符