epoll 或者 kqueue 的原理是什么?

作者:张彦飞
链接:https://www.zhihu.com/question/20122137/answer/2134896876

刚回答完一个 epoll,又刷到一个,那我就再来回答一遍吧。

仔细看了一下问题,题主的主要困惑是就是没有看过内核源码,不清楚 epoll 内部究竟是咋工作的。其实我之前和题主一样,也是这个困惑。由于实在是好奇心太强,所以我就抽空撸起袖子,把 epoll 的源码给扒了一遍。

我把我分析 epoll 的过程都放在我的这本电子书里了,《理解了实现再谈网络性能》。需要的同学戳这里下载,传送门:《理解了实现再谈网络性能》

在介绍 epoll 的实现之前。我想先聊聊传统的同步阻塞 IO。

深入理解高性能网络开发路上的绊脚石 - 同步阻塞网络 IO - 知乎

这种网络 IO 模型在对网络性能要求不高的地方现在用的也还是挺多的,我把这个整体流程画了一个图。

这种网络IO模型的问题是每次一个进程专门为了等一个 socket 上的数据就得被从 CPU 上拿下来。然后再换上另一个进程。等到数据 ready 了,睡眠的进程又会被唤醒。总共两次进程上下文切换开销,根据之前的测试来看,每一次切换大约是 3-5 us(微秒)左右。 如果是网络 IO 密集型的应用的话,CPU 就不停地做进程切换这种无用功。

所以多路IO复用,包括 epoll 就是为了解决上述问题而生的。当然 epoll 由于要支持海量的连接,为了高效地插入和删除 socket,在2.6 以后的版本里引入了红黑树。

epoll 作为多路复用技术中的代表,和传统的阻塞网络 IO 相比,最大的性能提升就是节约掉了大量的进程上下文切换。 epoll 内部又涉及出了一套复杂的数据结构,包括一棵红黑树和一个就绪链表(以及一个epollwait等待队列)。全部都工作在内核态。

使用 epoll 大体上由三个函数配合而成,分别是 epoll_create 、epollctl 和 epoll_wait,另外内核的网络模块也会参与其中。接下来我就分这四块来讲。这些都是我看过源码后整理出来的,源码版本是 3.10.0。

一、epoll_create 创建 epoll 内核对象

在用户进程调用 epoll_create 时,内核会创建一个 struct eventpoll 的内核对象。当然了这个对象,并不是由一个结构体构成的,而是一组数据结构。它的结构如下。

当然了,刚刚创建的 epoll 中就绪队列、红黑树都是空的,我画出来是为了方便大家理解。

就绪队列中存放事件发生,需要处理的 socket。这样后面和用户进程协作的时候就非常的容易了,简单地去就绪队列中查看有没有 Ready,需要被处理的 socket。有就拿走处理。

通过红黑树,高效地管理海量的连接,这样即使有百万条连接,插入和删除起来也是能保证效率。

二、epoll_ctl 添加 socket

epoll_ctl 所做的事情包括如下三件:

  • 1.分配一个红黑树节点对象 epitem,
  • 2.添加等待事件到 socket 的等待队列中,其回调函数是 ep_poll_callback
  • 3.将 epitem 插入到 epoll 对象的红黑树里

通过 epoll_ctl 添加两个 socket 以后,这些内核数据结构最终在进程中的关系图大致如下:

当内核将数据收到 socket 的接收队列后,将会通过 socket 上注册的这个 ep_poll_callback 函数来回调,进而通知到 epoll 对象。然后进一步将Ready 的socket 放到就绪链表中。

三、epoll_wait 等待事件

epoll_wait 做的事情其实不复杂,当它被调用时它观察 eventpoll->rdllist 就绪链表里有没有数据即可。有数据就返回,没有数据就创建一个等待队列项,将其添加到 eventpoll 的等待队列上,然后把自己阻塞掉就完事。

四、内核网络模块接收过程

内核的网络模块要做很多工作。首先是接收数据到 socket 的接收队列

接着需要查找 socket 上的就绪回调函数

找到了 socket 等待队列项里注册的函数 ep_poll_callback后调用该函数,把当前的 epitem 添加到 epoll 的就绪队列中。接着它又会查看 eventpoll 对象上的等待队列里是否有等待项,如果没有进程阻塞在 epoll 上,内核的一次工作就算是完成了。如果有进程在阻塞,那就唤醒它。

五、epoll 整体工作流程图

我们来总结一下,这是 epoll_wait 的工作流程图。

在这种模型下,只需要一个进程就可以维护成千上万甚至是百万级别的连接。正常情况下只要活儿足够的多,用户进程会一直干活,一直干活。大量地减少了进程切换次数,这就是 epoll 高效的地方所在!除非这个进程的除非实在是没有需要处理的连接了,epoll_wait才会将当前进程阻塞掉。

回答篇幅所限,很多内核源码就没办法贴了。想更详细地理解 epoll 的话建议把我这本《理解了实现再谈网络性能》下载下来慢慢看。这本 PDF 里除了 epoll 以外,还有很多关于网络是如何收包的,如何使用 CPU,如何使用内存的技术细节,非常值得一读。

下载链接传送门:《理解了实现再谈网络性能》

公众号:开发内功修炼

GitHub - yanfeizhang/coder-kung-fu: 开发内功修炼

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值