文章插图
注:rbr表示rb_root,rbn表示rb_node 上文给出了其在内核中的定义
- epoll_wait的数据拷贝
常见错误观点:epoll_wait返回时,对于就绪的事件,epoll使用的是共享内存的方式,即用户态和内核态都指向了就绪链表,所以就避免了内存拷贝消耗网上抄来抄去的观点关于epoll_wait使用共享内存的方式来加速用户态和内核态的数据交互,避免内存拷贝的观点,并没有得到2.6内核版本代码的证实,并且关于这次拷贝的实现是这样的:
文章插图
5.ET模式和LT模式
- 简单理解
LT和ET模式下都可以通过epoll_wait方法来获取事件,LT模式下将事件拷贝给用户程序之后,如果没有被处理或者未处理完,那么在下次调用时还会反馈给用户程序,可以认为数据不会丢失会反复提醒;
ET模式下如果没有被处理或者未处理完,那么下次将不再通知到用户程序,因此避免了反复被提醒,却加强了对用户程序读写的要求;
- 深入理解
- LT的读写操作
所以必须保证没有数据要发送的时候,要把fd的写事件监控从epoll列表中删除,需要的时候再加入回去,如此反复 。
天下没有免费的午餐,总是无代价地提醒是不可能的,对应write的过度提醒,需要使用者随用随加,否则将一直被提醒可写事件 。
- ET的读写操作
若发送缓冲区未满,epoll通知write事件,直到开发者填满发送缓冲区,epoll才会在下次发送缓冲区由满变成未满时通知write事件 。
ET模式下只有socket的状态发生变化时才会通知,也就是读取缓冲区由无数据到有数据时通知read事件,发送缓冲区由满变成未满通知write事件 。
- 一道面试题
使用Linux epoll模型的LT水平触发模式,当socket可写时,会不停的触发socket可写的事件,如何处理?网络流传的腾讯面试题这道题目对LT和ET考察比较深入,验证了前文说的LT模式write问题 。
普通做法:
当需要向socket写数据时,将该socket加入到epoll等待可写事件 。接收到socket可写事件后,调用write()或send()发送数据,当数据全部写完后, 将socket描述符移出epoll列表,这种做法需要反复添加和删除 。
改进做法:
向socket写数据时直接调用send()发送,当send()返回错误码EAGAIN,才将socket加入到epoll,等待可写事件后再发送数据,全部数据发送完毕,再移出epoll模型,改进的做法相当于认为socket在大部分时候是可写的,不能写了再让epoll帮忙监控 。
上面两种做法是对LT模式下write事件频繁通知的修复,本质上ET模式就可以直接搞定,并不需要用户层程序的补丁操作 。
- ET模式的线程饥饿问题
解决办法:为每个已经准备好的描述符维护一个队列,这样程序就可以知道哪些描述符已经准备好了但是并没有被读取完,然后程序定时或定量的读取,如果读完则移除,直到队列为空,这样就保证了每个fd都被读到并且不会丢失数据,流程如图:
推荐阅读
- 看都不懂的三层架构,到底要怎么理解?
- 谈谈Linux网络协议以及网络栈结构
- 共识算法Raft为什么这么流行,及原理解析
- Windows和Linux通吃,最新远控木马“Dacls”来袭
- 细品Linux系统中文件的三个时间属性
- 活死人意思 活死人的理解
- 开源交换机操作系统
- 如何在 Ubuntu 和其它 Linux 发行版上更新 grub
- Linux发行版之一CentOS的安装与网卡配置
- Linux系统管理命令:openssl