最近学 muduo 和 nginx 写网络库,总结一下 epoll 上遇到的一些问题和学习的笔记,主要是对 LT、ET 和平滑升级里的一些点理解一下。可以看作是上一次根据2.6 源码写的意识流(大白话)垃圾笔记(EPOLL 原理分析线索 SELECT POLL 原理_我说我谁呢 --CSDN博客)的翻新。

socket 驱动和系统调用

  • 数据包缓冲区,每当 listen 和 bind 了之后,内核就要接收对应 socket 地址(src_ip:src_port::dst_ip:dst_port四元组)的数据包,意味着不能直接发送 RST 或者静默丢弃(一般 UNIX 对没有 bind 没有 listen 的地址,或者对端提前 close 都可能发送 RST)。从而存在一个缓冲区,如果是 TCP,这个会是一个带流量控制和拥塞控制的滑动窗口,如果是 UDP,这个会是一个块缓冲区。一般 TCP listen 的来说,不用分配空间,可以等收到了甚至等到握手结束后再根据 meta 建立窗口缓冲区。
  • listen 的缓冲区,内核会存在一个栈表存储入站请求,等待用户应用程序调用 accept 取走请求。
  • 数据的复制,每当一个应当接受的数据包到达,内核的驱动中断程序(或者 NAPI 的 polling 程序)会进行 ring buffer 的复制,可能有 DMA,这里有两次复制,一次从硬件到 ring,一次解包从 ring 到滑动窗口/块缓冲区(这里会触发红黑树/哈希表 知识点,快速查找 ip:port)。每当应用层调用 accept、read 、recv 等慢系统调用时,
    • 如果缓冲区没有可用的数据(非就绪态),就要睡觉。睡觉过程中如果程序收到信号,会直接返回,设置 errno 为  EINTR。
    • 如果缓冲区有可用的数据,将会把缓冲区的数据复制到
    • 如果启用了 NONBLOCK 方式而且没有可用数据,就会返回 -1,errno 为 Resource temporarily unavailable (EAGAIN、EWOULDBLOCK)
  • poll 接口,linux 的 VFS 通过 file 结构体中的一个 file_operator 结构体成员实现自定义各种操作的函数指针。这个东西类似虚函数表,如果驱动(网卡供应商的硬件驱动、kernel 的 TCP 协议栈都是这里“驱动”的范围,或者说是 VFS 的底层更准确)不支持,就是 NULL 了。其中的 op 包括 fseek 的指针、aio(淘汰了)、ioctl、mmap、fsync、readv、writev 等。这些都是特定 file 下层驱动需要实现的,比如对于磁盘文件和 socket 文件,实现当然是不一样的。当然,最重要的(针对本文来说)是 poll 接口,这个是用来查询该 file 是否有数据的,实际对于 一个 socket file 来说,这个东西是 TCP 协议栈实现的,内核应当存储了一个巨大的表(红黑树或者 hashmap)可以直接根据 ip:port 查询,然后设置对应的 metadata。
  • 硬件中断后的回调,同样是 file 结构体,里面有一个 private_data 的内存块指针,这里不同的驱动会不同的方式访问。对于可能阻塞的文件,需要解决 wait queue 的问题,Linux 通过这里实现 wake up 的功能,总之,数据包到达之后,kernel 拆包找到特定的 file 之后,通过 private_data 里面的内容进行回调,epoll、select 这些函数绑定的时候,会在 private_data 里面注册一个回调函数,驱动将会触发他们。对于 select 和 poll 来说,做的事情很简单就是唤醒程序,之后在 kernel side 醒来,做一些标记之类的事情,就能返回到用户了。对于 epoll 来说,这个回调函数会把当前 fd 的某个结构体连上一个 double linked list 里面,之后就能直接返回成功的给。
  • epoll 的数据结构优化,epoll 的红黑树是用来查询用户关心的 fd (epitem)用的(epoll_ctl 的操作都走红黑树)。对于要返回给用户的链表(rdlist),他的钩子是在 file 结构体里面的,上面说的双链表就是这个东西:
#ifdef CONFIG_EPOLL/* Used by fs/eventpoll.c to link all the hooks to this file */struct list_head    f_ep_links;spinlock_t        f_ep_lock;#endif /* #ifdef CONFIG_EPOLL */
  • 没有 mmap网上有些博客有毒,说 epoll 用了 mmap。其实一直没有用。实际直到 5.6 的 io_uring 才引入了 mmap 的优化。

旧 api - select 和 poll

  • select 是有动静触发返回,然后用户要遍历检查整个 set (一种 bitmap 数据结构)。线性复杂度。简单的实现是,循环
  • pselect 增加了避免信号缺失的处理(记住一般 handler 不能有 states,所以一般用全局变量做 flag 之后再在 eventloop 里面检查这个 flag),可以睡前检查信号。
  • poll 改进数据结构和超时精度。由于 set 的数据结构不好用,以及之前的 ms 超时精度太小,poll 提供了支持 ns 超时的链表结构体,方便,但是实际还是线性复杂度。
  • 2.6 源码
static int do_poll(unsigned int nfds,  struct poll_list *list,struct poll_wqueues *wait, long timeout){int count = 0;poll_table* pt = &wait->pt;if (!timeout)pt = NULL;for (;;) {struct poll_list *walk;walk = list;while(walk != NULL) {do_pollfd( walk->len, walk->entries, &pt, &count);walk = walk->next;}// count代表有没有就绪事件,timeout 说明没有设置超时或者已经超时,signal_pending代表有信号需要处理if (count || !timeout || signal_pending(current))break;// 挂起进程,timeout后被唤醒timeout = schedule_timeout(timeout);}return count;
}
  • 需要注意实际 poll 和 select 会有两次 O(n) 操作,一次是 kernel side 的轮询,一次是用户态需要看结构体里面的 revent (a bunch of AND operations),这个对大量空闲连接的来说,是花时间的的,所以实际还可能被 yield 出去。

ET LT 的区别

  • 针对事件的设置,注意 ET 和 LT 是可以针对每个 fd 设置的。
  • linux 2.6 代码(裁剪了一部分内容,直接去 github 看完整源代码)
    static int ep_send_events(struct eventpoll *ep,struct epoll_event __user *events, int maxevents){/** We can loop without lock because we are passed a task private list.* Items cannot vanish during the loop we are holding ep->mtx.*/list_for_each_entry_safe(epi, tmp, &txlist, rdllink) {struct wakeup_source *ws;__poll_t revents;if (res >= maxevents)break;list_del_init(&epi->rdllink);/** If the event mask intersect the caller-requested one,* deliver the event to userspace. Again, we are holding ep->mtx,* so no operations coming from userspace can change the item.*/revents = ep_item_poll(epi, &pt, 1);if (!revents)continue;events = epoll_put_uevent(revents, epi->event.data, events);if (!events) {list_add(&epi->rdllink, &txlist);ep_pm_stay_awake(epi);if (!res)res = -EFAULT;break;}res++;if (epi->event.events & EPOLLONESHOT)epi->event.events &= EP_PRIVATE_BITS;else if (!(epi->event.events & EPOLLET)) {/** If this file has been added with Level* Trigger mode, we need to insert back inside* the ready list, so that the next call to* epoll_wait() will check again the events* availability. At this point, no one can insert* into ep->rdllist besides us. The epoll_ctl()* callers are locked out by* ep_scan_ready_list() holding "mtx" and the* poll callback will queue them in ep->ovflist.*/list_add_tail(&epi->rdllink, &ep->rdllist);ep_pm_stay_awake(epi);}}ep_done_scan(ep, &txlist);mutex_unlock(&ep->mtx);return res;}
来自 <https://github.com/torvalds/linux/blob/42eb8fdac2fc5d62392dcfcf0253753e821a97b0/fs/eventpoll.c#L1700> 
  • 对于 LT 来说,就是他要返回用户态,他还是保留在 kernel side 的链表中的,从而下一次还会再检查一次,如果还是没有 ready 的话,下一次就会把他删除掉了。

一些其他知识

  • epoll_wait 惊群,如果采用 oneshot 就能保证只唤醒一个。
  • epoll 再 fork 之前的话,fork 会复制同一个 epoll fd,所以理论上是安全的。如果是多个 epoll fd 来的话,还是可能惊群,解决方案是用锁,nginx 旧用了锁。
  • linux 无法覆盖监听端口,重复绑定/listen 会引发 bind error。SO_RESUEPORT 是内核 3.9 引入的内核空间里进行负载均衡的方案。

nginx 的 master-worker 模型

  • master 平时都处于睡觉状态。
  • 所有修改状态机的行为都通过信号机制和全局 flag 来实现。

Nginx 的 epoll 选择

  • listenfd 用 LT
  • connectfd 用 ET

为什么 ET 可能会丢失连接(程序员的锅)

  • 循环,如果应用程序没有正确的处理 epoll wait 醒来后的处理(accept、read、write 都要套 while),就会丢失连接。
  • 立即返回,理论上 epoll_wait 睡觉之前会检查一下有没有能立即返回的。但是 et 可能本身是高电平的话,不会返回(5.6 后某个 patch 之后可能更改了逻辑:The edge-triggered misunderstanding [LWN.net])

edge 并不是指 "有人写入了更多的数据"。edge 的意思是 "以前没有数据,现在有数据了"。

而一个 level triggered 事件 也不是 指 "有人写了更多的数据"。它只是表示 "这里有数据"。

请注意,edge 和 level 都没有提到 "更多的数据" 这个信息。其中一个是指从 "没有数据"->"有数据 "的这个变化,而另一个只是表示 "有数据"。

---- Linus Torvalds

  • 要理解这个,就要理解 socket 的回调挂上double linked 列表是怎么做的,我们之前的笔记里说了,实际是 file 的 private data 里面被驱动 reinterpret_cast 为 struct socket,然后会有一个管理 wait queue 的结构体,回调函数里面做的事情很重要。下面这个函数就是回调函数

ep_ptable_queue_proc(Linux 2.9)

来自 <https://github.com/torvalds/linux/blob/42eb8fdac2fc5d62392dcfcf0253753e821a97b0/fs/eventpoll.c#L1700>

仔细查看源码就能理解,回调挂上 double linked list 的时候并没有什么电平检查,就直接连上链表了(当然,本身tcp栈是已经检查过了是不是这个进程绑定的了)。所以实际上,后续如果有了新的数据,还是能保证挂上来的。丢失的问题主要在于一开始的情况,以及后续没有新数据来的情况。上面 LWN 讲 Android bug 的时候也是主要是 pipe 一开始没处理好的问题。

平滑升级和回滚的方案

  • 首先思想上要允许中间状态(新 worker 未启动完成的时候)一部分连接进入旧的,直到我们关掉旧 worker 的 listen d。
  • 对于同一个 nginx 版本的,只是更新服务器配置(nginx 只是一个 web 服务器+负载均衡,本身不是网络库),我们开新的 worker,然后关掉旧 worker 的 listen fd (epoll_ctl) 就行了。
  • 更新 nginx 版本的要麻烦一点。思想上允许中间状态存在。所以此时可以并行存在新 worker 和旧 worker 保证不间断服务,直到新的设置好了之后,再关掉旧的就好了,就是这么简单,要实现这个,必须处理好各种全局结构的访问,比如 nginx 的 pid 文件。
  • 为了安全,实际运行的时候,并不会马上关闭旧的 master+workers,而是让运维手动控制,从而保证能够 revive,这时发送信号则主要是控制 worker 恢复 accept。

Nginx listenfd 为什么要用 LT

  • nginx 的 worker 都是 master fork 出来的。
  • nginx 更新配置文件是用旧 master fork 升级的,更新二进制则是停掉 accept,然后用新启动 master。
  • 我的总结是,一个是为了方便统一编写平滑升级的模块,以及方便控制最大并发连接数。
  • 首先,理解一下 nginx 进行 configure file 更新的方法,是通过 HUP 信号来实现的,一种实现方法是,设置一个 flag,说 worker 不能再 accept 新的连接了,必须先更新一下配置文件,然后设置准备新的 worker 了,这样要求 listen fd 的内容要 backlog。
  • say 负载均衡的应用,我们在某个 worker 里面,此时收到了更新配置文件的请求,worker 需要暂停 accept。对于 ET 来说,没有办法跳出 epoll_wait 的循环,我们必须把处理 reload 的代码放到 accept 的 loop 内部(目前是一层 epoll_wait 一层做 accept 的)。
  • 具体其实用 ET 也能实现,但是会复杂一点。而且 Nginx 本身也要兼容 select 和 poll 的(具体的说,在适配 IOCP 之前,windows 的版本一直用的是 select)。
  • 还有一个好处是,对于 listen fd 来说,使用 LT 可以控制同时并发数,比如,我们可以限制一次 accept N 个连接,等 worker 都处理完了,我们再 accept。用 ET 的话,当然也能实现,但是要更加复杂。

epoll 和 poll/select 的选择

  • 没有什么事情的话其实当然是选 epoll 的。
  • 但是!!epoll 每次修改一个 fd 的关注都要进行一次 context switch !(epoll_ctl 是 system call,修改的数据结构是内核的红黑树,而且一次只能修改一个 fd)。while select/poll 只需要在 user space 修改好数据结构,然后 switch 过去复制一遍。

LT 和 ET 的一些问题(面试题)

  • 对于 EPOLL_OUT 可写事件,LT 会不断的触发。解决方案当然就是直接移除他,等需要些了再监听。实际还是要维护一个表。或者说,处理完了(生成数据),需要写的时候就非阻塞写一下,如果 EWOULDBLOCK 了的话,就注册 epoll 进去睡觉,写完了又丢掉(epoll_ctl)就行了。这个会引发很多的 context switch。(腾讯、快手)

我又一篇经典有头无尾文章。有时候我在想,为什么有的人写的东西搜出来这么难懂,或者各种跳步就点点东西,他自己看的吗?他自己能看明白吗?还有那些抄来抄去的东西,又有什么意义。慢慢好像感觉没有资格这样想。因为自己试着学一遍某个知识点,最后又会发现,感觉好像会了,但是要写出来,又花时间又要重新表达一些东西,实际别人可能精品表达已经够好了,东拼西凑一个笔记是不是更方便复习和快速学习呢。做笔记费劲,用拙略的话再说一遍更加离谱,想起以前学东西都是直接忠于原书,如果忘记了细节,那就应该完整的回味他,如果需要概要,是不是一个目录、思维导图就够了呢?而不是去写一些这缺一块那缺一块的不完备的离奇总结(都不能成为综述的质量)。而且要写的完备的自己也能复习看懂也不是那么简单的更何况让别人看懂,但是又感觉自己需要复习使用。我之前写的笔记都是各种知识点的杂糅怪,标题概括不了内容,而且常常是标题本来研究的东西有头无尾一笔带过,而旁支末节又钻牛角尖。之后我感觉再做没有意义的笔记是没有意义的(==),润。

epoll LT ET 区别 | Nginx epoll 原理 listend 用 LT相关推荐

  1. Epoll的本质(内部实现原理)

    转自:https://zhuanlan.zhihu.com/p/63179839 作者:罗培羽 从事服务端开发,少不了要接触网络编程.epoll作为linux下高性能网络服务器的必备技术至关重要,ng ...

  2. 深入理解异步I/O+epoll+协程,附上epoll原理解析以及协程现实与原理剖析视频

    前言 同步和异步的概念描述的是用户线程与内核的交互方式:同步是指用户线程发起IO请求后需要等待或者轮询内核IO操作完成后才能继续执行:而异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后会 ...

  3. IO多路复用中select、poll、epoll之间的区别

    本文来说下IO多路复用中select.poll.epoll之间的区别 文章目录 什么是IO多路复用 为什么有IO多路复用机制 同步阻塞(BIO) 同步非阻塞(NIO) IO多路复用(现在的做法) 3种 ...

  4. epoll和select区别

     先说下本文框架,先是问题引出,然后概括两个机制的区别和联系,最后介绍每个接口的用法 一.问题引出 联系区别 问题的引出,当需要读两个以上的I/O的时候,如果使用阻塞式的I/O,那么可能长时间的阻 ...

  5. 【c++】27.事件驱动、IO复用、sellect、poll、epoll三者的区别

    一.事件驱动的理解: 1.要理解事件驱动和程序,就需要与非事件驱动的程序进行比较.实际上,现代的程序大多是事件驱动的,比如多线程的程序,肯定是事件驱动的.早期则存在许多非事件驱动的程序,这样的程序,在 ...

  6. (转)select、poll、epoll之间的区别

    本文来自:https://www.cnblogs.com/aspirant/p/9166944.html (1)select==>时间复杂度O(n) 它仅仅知道了,有I/O事件发生了,却并不知道 ...

  7. select及触发方式,select监听多链接,select与epoll的实现区别

    select及触发方式 触发方式:1.水平触发 2.边缘触发 触发:达到某种情况发生某种状态 心得:阻塞是等待,IO是输入输出,I/O接口 select监听多链接 IO多路复用优势:同时可以监听多个链 ...

  8. select、poll、epoll之间的区别

    select.poll.epoll之间的区别(搜狗面试) - aspirant - 博客园 (cnblogs.com)

  9. select、poll、epoll之间的区别(搜狗面试)

    (1)select==>时间复杂度O(n) 它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对 ...

最新文章

  1. python中使用zip函数基于两个列表数据list创建字典dict数据(Create a dictionary by passing the output of zip to dict)
  2. L型四驱越野模型车初期磨合指南
  3. ResultSet用法集锦 (转)
  4. Linux DMA 驱动学习总结
  5. [Google Guava] 11-事件总线
  6. 【深度学习的数学】用神经网络进行图像分类时,为什么输出层的神经单元数量要跟分类数相同?可以采用二进制的表示方式么?
  7. Android开发之购物车添加商品实现抛物线动画
  8. 这几天有django和python做了一个多用户博客系统(可选择模板) 没完成,先分享下...
  9. ssm使用全注解实现增删改查案例——Emp
  10. VM虚拟机显示不能铺满问题
  11. 一文总结Java\JDK 17发布的新特性
  12. python3库查看调用_Python 3 中调用 COM 的库文件 | 学步园
  13. 数控镗铣床行业调研报告 - 市场现状分析与发展前景预测
  14. bes配置oracle数据源,安装 ZFS 根文件系统(Oracle Solaris 初始安装)
  15. ST电机库api使用——获取电机状态
  16. Web应用程序的开发方法
  17. linux配置cookie认证,Nginx配置修改网页cookie属性
  18. 转行做程序员,月薪过万需要多久?
  19. [DUBBO] disconnected from 问题
  20. hdf heg 批量拼接_MODIS处理工具MRT已被HEG代替

热门文章

  1. WPS插件 - 保存Unicode编码的CSV文件
  2. 微信小程序获取WIFI列表可手动输入密码连接
  3. android imageview 拉伸图片大小,【教程】安卓保证图片长宽比的同时拉伸图片
  4. 河南专升本公共英语语法考点分析---非谓语动词
  5. 使用STM32F4浮点运算(FPU)功能开启+使用DSP库
  6. JS Array.slice 截取数组的实现方法
  7. 【Vue】Vue 判断插槽内容是否加载完成
  8. c++ 反射_基于飞凌FETA40i-C核心板在光时域反射仪中的应用原理
  9. 数据结构课后习题答案
  10. 计算机辅助出版的英文缩写是,计算机辅助设计的英文缩写为