细说技术系统中的公平性问题-03-其他epoll相关函数

细说技术系统中的公平性问题-03-其他epoll相关函数

1.前言

上一篇中,我们着重分析了epoll_wait函数。这一篇中,我们将以动态的视角分析下一包tcp数据到达服务端后将会发生什么。

2.用户程序

2.1 epoll_create1

使用epoll 的第一步是使用epoll_create1函数创建epoll套接字,函数定义如下:

这个函数的关键是创建一个struct eventpoll结构体。

这个结构体上有很多字段,我们简单挑几个用到的解释一下:

  • mtx:互斥量。我们知道,内核是并发的,有并发就需要有并发控制,mtx就是用来干这个的。要想访问eventpoll结构体上的一些变量,就要先使用mtx获得对这些变量独占访问权。
  • wq:当用户态进程调用epoll_wait等待io事件发生时,如果没有任何事件触发,用户态进程会进入休眠状态,直到被io事件唤醒,wq就是做这个的。
  • rdllist:这个我们非常熟悉,在前一篇中反复提到,是待处理的套接字就绪事件列表。
  • rbr:这是个红黑树。我们知道,epoll能管理巨量的套接字,每个套接字都有一个对应的数据结构,这个红黑树就是保存这些套接字的数据结构的。

2.2 epoll_ctl

epoll_ctl可以被用来向epoll对象中添加/删除/修改套接字列表。

比如服务端accept连接之后,获得了客户端peer的套接字fd,可以调用epoll_ctl接口将其加入到epoll的io事件关注列表中。这样当客户端发送来数据后,通过epoll_wait就能获得有关该客户端连接的io事件通知。

函数定义如下:

首先我们关注下函数中的这两个关键变量:

前者就是上面我们使用epoll_create1创建的epoll对象,后面是这个具体的套接字对应的epollitem对象。当我们调用epoll_ctl将套接字加入epoll关注列表时,其实就是将上面的epitem加入到ep中了。

知道这一点后,相关的代码就比较容易看懂了:

ep_insert函数的关键是这一句:

即:将epitem加入到ep的红黑树中。

2.3 总结

画个图总结下:

3.系统程序

这部分我们主要探讨当网卡收到客户端连接发来的数据后发生了什么。

3.1 软中断处理

数据到达网卡后,会触发软中断事件,linux系统使用独立的内核线程(ksoftirqd)处理软中断事件。

如上图中的ksoftirqd/0线程,表示该线程负责处理位于CPU0上的软中断事件。

在多核系统中,这样的线程会有多个:

系统在处理软中断的时候,过程比较复杂,最终会调用到ep_poll_callback这个函数。

函数逻辑经过简化后,非常简单:

即:如果从网络上收到数据了,软中断线程会直接找到数据所属的套接字的epitem,并将其加入套接字的就绪列表。

插入就绪列表时,采用的是尾插法,如果每次epoll_wait的时候,套接字都不在就绪列表中(适用于低速客户端或高速服务端,能很及时地将套接字的事情处理完),则epoll_wait返回给服务端的套接字列表就是客户端数据实际到达服务端的顺序,是比较公平的。

然而一旦上个场景中的条件不满足,即当有数据到来时,套接字还在就绪列表中,那么新到来的数据并不会导致套接字在就绪列表中的顺序发生变化,这样epoll_wait返回给服务端的套接字列表就可能不太公平了,这时候,是否有必要让服务器端再做一次公平性处理呢?

导致新数据到来时,套接字仍然存在于就绪列表中的实际场景包括但不限于:

  • 客户端发送数据太快,服务端接收速度赶不上
  • 服务端处理速度太慢,接收不及时
  • 服务端故意降低接收速度(限速、公平策略等)

另外,即使客户端和服务端的速度匹配,即epoll_wait每次调用前,epoll_wait的就绪列表总是空,也还是有一些问题的。例如当客户端发送的业务数据包在底层传输数据时进行了分包时,服务端会以最先到达的那个小包为依据进行排序,而不会以整包到齐的顺序进行排序。

3.2 总结

画个图总结下:

4.总结

  • epoll_create1会创建一个eventpoll结构,关联到epollfd,该结构内部包含一个套接字就绪列表rdllist,和一个用于保存所有注册套接字信息的红黑树rbr
  • epoll_ctl(EPOLL_CTL_ADD)会创建一个epitem结构,关联到客户端套接字,并将该套接字的epitem结构插入epollfd所属的红黑树中
  • 网络数据接收事件由内核的软中断线程处理,当网卡上有数据到来时,软中断线程会一步步找到该数据对应的epitem结构和epollfd结构,如果该epitem不在对应的就绪列表rdllist中时,软中断线程会将其追加到rdllist中,这会造成两个效果:
    • 套接字在rdllist的顺序,为套接字io事件发生的顺序,即客户端数据到达服务器网卡的顺序(如果底层有TCP分包,那么会以第一个分组到达的顺序为准,而不是以整个业务数据包全部到齐的顺序为准)
    • 如果套接字的io事件没有在上轮epoll_wait中处理完,那么新数据的到来不会导致该套接字在就绪列表中的位置发生改变(该场景可能导致一些公平性问题)

5.参考资料

  • 参考博文:https://zhuanlan.zhihu.com/p/487497556
  • 相关图表:点我下载

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注