深入理解Linux IO复用之epoll


深入理解Linux IO复用之epoll

文章插图
作者:后端技术指南针 来自:后端技术指南针
0.概述通过本篇文章将了解到以下内容:
  • I/O复用的定义和产生背景
  • linux系统的I/O复用工具
  • epoll设计的基本构成
  • epoll高性能的底层实现
  • epoll的ET模式和LT模式
 
1.复用技术和I/O复用
  • 复用的概念
复用技术(multiplexing)并不是新技术而是一种设计思想,在通信和硬件设计中存在频分复用、时分复用、波分复用、码分复用等,在日常生活中复用的场景也非常多,因此不要被专业术语所迷惑 。
从本质上来说,复用就是为了解决有限资源和过多使用者的不平衡问题,且此技术的理论基础是资源的可释放性 。
 
  • 资源的可释放性
举个实际生活的例子:
不可释放场景:ICU病房的呼吸机作为有限资源,病人一旦占用且在未脱离危险之前是无法放弃占用的,因此不可能几个情况一样的病人轮流使用 。
可释放场景:对于一些其他资源比如医护人员就可以实现对多个病人的同时监护,理论上不存在一个病人占用医护人员资源不释放的场景 。
 
  • 理解IO复用
I/O的含义:在计算机领域常说的IO包括磁盘IO和网络IO,我们所说的IO复用主要是指网络IO,在Linux中一切皆文件,因此网络IO也经常用文件描述符FD来表示 。
复用的含义:那么这些文件描述符FD要复用什么呢?在网络场景中复用的就是任务处理线程,所以简单理解就是多个IO共用1个线程 。
IO复用的可行性:IO请求的基本操作包括read和write,由于网络交互的本质性,必然存在等待,换言之就是整个网络连接中FD的读写是交替出现的,时而可读可写,时而空闲,所以IO复用是可用实现的 。
综上认为,IO复用技术就是协调多个可释放资源的FD交替共享任务处理线程完成通信任务,实现多个fd对应1个任务处理线程 。
现实生活中IO复用就像一只边牧管理几百只绵羊一样:
深入理解Linux IO复用之epoll

文章插图
 
  • IO复用的设计原则和产生背景
高效IO复用机制要满足:协调者消耗最少的系统资源、最小化FD的等待时间、最大化FD的数量、任务处理线程最少的空闲、多快好省完成任务等 。
在网络并发量非常小的原始时期,即使per req per process地处理网络请求也可以满足要求,但是随着网络并发量的提高,原始方式必将阻碍进步,所以就刺激了IO复用机制的实现和推广 。
2.Linux中IO复用工具在Linux中先后出现了select、poll、epoll等,FreeBSD的kqueue也是非常优秀的IO复用工具,kqueue的原理和epoll很类似,本文以Linux环境为例,并且不讨论过多select和poll的实现机制和细节 。
 
  • 开拓者select
select大约是2000年初出现的,其对外的接口定义:
深入理解Linux IO复用之epoll

文章插图
 
作为第一个IO复用系统调用,select使用一个宏定义函数按照bitmap原理填充fd,默认大小是1024个,因此对于fd的数值大于1024都可能出现问题,看下官方预警:
深入理解Linux IO复用之epoll

文章插图
 
也就是说当fd的数值大于1024时在将不可控,官方不建议超过1024,但是我们也无法控制fd的绝对数值大小,之前针对这个问题做过一些调研,结论是系统对于fd的分配有自己的策略,会大概率分配到1024以内,对此我并没有充分理解,只是提及一下这个坑 。
存在的问题:
  • 可协调fd数量和数值都不超过1024 无法实现高并发
  • 使用O(n)复杂度遍历fd数组查看fd的可读写性 效率低
  • 涉及大量kernel和用户态拷贝 消耗大
  • 每次完成监控需要再次重新传入并且分事件传入 操作冗余
综上可知,select以朴素的方式实现了IO复用,将并发量提高的最大K级,但是对于完成这个任务的代价和灵活性都有待提高 。无论怎么样select作为先驱对IO复用有巨大的推动,并且指明了后续的优化方向,不要无知地指责select 。
 
  • 继承者epoll
epoll最初在2.5.44内核版本出现,后续在2.6.x版本中对代码进行了优化使其更加简洁,先后面对外界的质疑在后续增加了一些设置来解决隐藏的问题,所以epoll也已经有十几年的历史了 。
在《Unix网络编程》第三版(2003年)还没有介绍epoll,因为那个时代epoll还没有出现,书中只介绍了select和poll,epoll对select中存在的问题都逐一解决,简单来说epoll的优势包括:


推荐阅读