Linux的I/O模型 —— select/poll/epoll|8月更文挑战

1,542 阅读4分钟

这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战

select/poll/epoll对比:

select、poll、epoll对比.png

注意遍历相当于查看所有的位置,回调相当于查看对应的位置。

1 select

select工作流程.jpg

POSIX所规定,目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理

缺点

  • 单个进程可监视的fd数量被限制,即能监听端口的数量有限,数值存在如下文件里:cat /proc/sys/fs/file-max
  • 对socket是线性扫描,即采用轮询的方法,效率较低
  • select采取了内存拷贝方法来实现内核将FD消息通知给用户空间,这样一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大

select是第一版IO复用,提出后暴漏了很多问题。

  • select 会修改传入的参数数组,这个对于一个需要调用很多次的函数,是非常不友好的
  • select 如果任何一个sock(I/O stream)出现了数据,select 仅仅会返回,但不会告诉是那个sock上有数据,只能自己遍历查找
  • select 只能监视1024个链接
  • select 不是线程安全的,如果你把一个sock加入到select, 然后突然另外一个线程发现这个sock不用,要收回,这个select 不支持的

2 poll

poll工作流程.jpg

本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态

  • 其没有最大连接数的限制,原因是它是基于链表来存储的
  • 大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义
  • poll特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd
  • 边缘触发:只通知一次,epoll用的就是边缘触发

poll 修复了 select 的很多问题:

  • poll 去掉了1024个链接的限制
  • poll 从设计上来说不再修改传入数组

但是poll仍然不是线程安全的, 这就意味着不管服务器有多强悍,你也只能在一个线程里面处理一组 I/O 流。你当然可以拿多进程来配合了,不过然后你就有了多进程的各种问题。

3 epoll

epoll工作流程.jpg

在Linux2.6内核中提出的select和poll的增强版本

  • 支持水平触发和边缘触发,最大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就绪态,并且只会通知一次
  • 使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知

优点

  • 没有最大并发连接的限制:能打开的FD的上限远大于1024(1G的内存能监听约10万个端口)
  • 效率提升:非轮询的方式,不会随着FD数目的增加而效率下降;只有活跃可用的FD才会调用callback函数,即epoll最大的优点就在于它只管理“活跃”的连接,而跟连接总数无关
  • 内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销
  • 文件映射内存直接通过地址空间访问,效率更高,把文件映射到内存中

epoll 可以说是 I/O 多路复用最新的一个实现,epoll 修复了poll 和select绝大部分问题, 比如:

  • epoll 现在是线程安全的
  • epoll 现在不仅告诉你sock组里面数据,还会告诉你具体哪个sock有数据,你不用自己去找了
  • epoll 内核态管理了各种IO文件描述符, 以前用户态发送所有文件描述符到内核态,然后内核态负责筛选返回可用数组,现在epoll模式下所有文件描述符在内核态有存,查询时不用传文件描述符进去了