💓博主CSDN主页:杭电码农-NEO💓
⏩专栏分类:Linux从入门到精通⏪
🚚代码仓库:NEO的学习日记🚚
🌹关注我🫵带你学更多操作系统知识
🔝🔝
Linux高级IO
- 1. 前言
- 2. 初识epoll
- 3. epoll的工作原理
- 4. epoll的优缺点
- 5. epoll的工作模式
- 6. ET模式和LT模式的对比
- 7. 总结以及拓展
1. 前言
其实在select和epoll之间还夹了一个poll, 但是由于epoll过于优秀, 所以poll基本没人使用(甚至没有select使用的多), 这里就直接跳过poll了, epoll本质上也是一种多路转接的实现方案
本章重点:
本篇文章着重讲解epoll的概念和它的底层原理, 最后会讲解epoll的两种工作模式: ET模式和LT模式, 本篇文章没有epoll编码, 全是干货概念
2. 初识epoll
在centos下的man手册中, 对epoll是这样介绍的: 是为处理大批量句柄而作了改进的poll, 相信聪明的你一定发现了, select和poll的编程非常麻烦, 它需要我们程序员自行维护一些数据结构, 并且还有输入输出型参数, 编程成本高, 并且学习成本也高. 所以有了epoll
epoll相关的三个系统调用:
epoll_create, 用于创建epoll模型
epoll_ctl,用于设置关心的文件描述符和关心的事件
events可以是以下几个宏的集合:
- EPOLLIN : 表示对应的文件描述符可以读 (包括对端SOCKET正常关闭);
- EPOLLOUT : 表示对应的文件描述符可以写;
- EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据到来);
- EPOLLERR : 表示对应的文件描述符发生错误;
- EPOLLHUP : 表示对应的文件描述符被挂断;
- EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的.
- EPOLLONESHOT:只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个socket的话, 需要再次把这个socket加入到EPOLL队列里…
epoll_wait,用于等待是否有事件就绪
3. epoll的工作原理
当程序中调用epoll_create时, 会在底层创建一个eventpoll结构体, 此结构体中有两个非常重要的字段:
一颗红黑树的根节点指针
一个队列的头指针
红黑树的用处:
红黑树中存放着所有正在关心的文件描述符, 当我们调用epoll_ctl设置关心事件时, 实际上会在底层的这颗红黑树中添加/删除/修改节点
队列的用处:
队列中存放的是所有存在就绪事件的文件描述符, 当我们调用epoll_wait时, 实际上就是在等待此队列中是否有就绪的文件描述符到来
关于epoll的几个细节:
- 红黑树的节点是需要key值的, 而文件描述符恰好可以充当这一值
- 用户只需要设置关心, 获取结果即可, 不用再关心任何对fd和event的管理
- 底层只要有fd就绪, OS会自动给我构建节点, 并且插入到就绪队列中, 上层只需不断从就绪队列中将数据拿走, 就完成了获取就绪事件的任务(生产者消费者模型)
struct epitem{ struct rb_node rbn;//红黑树节点 struct list_head rdllink;//双向链表节点 struct epoll_filefd ffd; //事件句柄信息 struct eventpoll *ep; //指向其所属的eventpoll对象 struct epoll_event event; //期待发生的事件类型
}
当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可. 如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户. 这个操作的时间复杂度是O(1)
总结一下, epoll的使用过程就是三部曲:
- 调用epoll_create创建一个epoll句柄;
- 调用epoll_ctl, 将要监控的文件描述符注册到红黑树;
- 调用epoll_wait, 等待文件描述符就绪后, 去队列中拿;
4. epoll的优缺点
对比select的优点:
- 接口使用方便: 虽然拆分成了三个函数, 但是反而使用起来更方便高效. 不需要每次循环都设置关注的文件描述符, 也做到了输入输出参数分离开
- 数据拷贝轻量: 只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中, 这个操作并不频繁(而select/poll都是每次循环都要进行拷贝)
- 事件回调机制: 避免使用遍历, 而是使用回调函数的方式, 将就绪的文件描述符结构加入到就绪队列中,
- epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪. 这个操作时间复杂度O(1). 即使文件描述符数目很多, 效率也不会受到影响.
- 没有数量限制: 文件描述符数目无上限.
正是上诉的优点,使得epoll成为了现代高并发服务器中, 使用的最多的方法, 没有之一!
5. epoll的工作模式
epoll有两种工作方式:
- 水平触发(LT模式)
- 边缘触发(ET模式)
水平触发模式讲解:
想象一下你一次性买了5个快递, 快递员张三把快递送到你家楼下了, 打电话让你下来拿快递, 但是这个时候你正在和室友开黑, 你就对快递员说: 你等等我, 我打完这把就下来取, 张三也没说什么, 就在楼下等你. 你终于打完了这把游戏, 现在你下去取快递, 但是你一次性只能拿4个快递, 还剩一个在下面, 你给快递员张三说: 你能不能再等我一下, 我上楼后再下来取剩下的一个快递, 张三也没说什么, 就在楼下等你. 你不下来取, 张三就一直等你, 一直给你打电话.
这就是水平触发模式:
聪明的你也能想到, 水平触发模型下, 效率会很低, 因为快递员张三一直在楼下等你, 不能做其他事, 所以有了边缘触发来替代它
边缘触发模式详解:
想象一下, 假设是李四给你送外卖, 他和张三不一样, 他脾气非常火爆, 你快递到来后他给你打了个电话说: 你快递到了, 我只等你十分钟, 如果你不下楼取快递, 我就把你的快递放在路边了, 并且我只给你打一次电话. 这个时候你会怎么办? 当然你会选择挂机坑队友, 直接跑下楼取快递, 因为你不去你的快递可能就丢失了!
这就是边缘触发模型:
6. ET模式和LT模式的对比
首先, 一定是ET模式更加高效.
具体表现为下面几点:
- ET模式通知用户的次数变少, 减少了从内核态到用户态的转换, 提高了效率
- ET模式会使程序员一次性将所有数据取走, 减少了拷贝的次数
- ET模式会倒逼程序员尽快将接收缓冲区的数据取走, 那么就可以给对方发送一个更大的接受窗口(TCP协议), 对方就可以有一个更大的滑动窗口, 一次性给我们发送更多数据, 提高IO吞吐量
7. 总结以及拓展
本篇文章是epoll的理论知识, 后面会带大家实践编写代码, 如果你对epoll感兴趣, 你可以去了解一下reactor模式, 也称为反应堆模式, 很多大型框架的底层都是通过reactor来提高服务器效率的!