聂永的博客

记录工作/学习的点点滴滴。

Fastsocket学习笔记之内核篇

前言

前面分析Fastsocket慢慢凑成了几篇烂文字,要把一件事情坚持做下来,有时味同爵蜡,但既然选择了,也得硬着头皮做下去。闲话少说,文归正文。本文接自上篇内核模块篇,继续记录学习Fastsocket内核的笔记内容。

Fastsocket建立在SO_REUSEPORT支持基础上

Linux kernel 3.9包含TCP/UDP支持多进程、多线程绑定同一个IP和端口的特性,即SO_REUSEPORT;在内核层面同时也让线程/进程之间各自独享SOCKET,避免CPU核之间以锁资源争夺accept queue的调用。在fastsocket/kernel/net/sock.h定义sock_common结构时,可以看到其身影:

unsigned char          skc_reuse:4;
unsigned char          skc_reuseport:4;

在多个socket.h文件中(比如fastsocket/kernel/include/asm/socket.h),定义了SO_REUSESORT的变量值:

#define SO_REUSEPORT     15

在fastsocket/kernel/net/core/sock.c的sock_setsockopt和sock_getsockopt函数中,都有SO_REUSEPORT的身影:

sock_setsockopt函数中:

case SO_REUSEADDR:
  sk->sk_reuse = valbool;
  break;
case SO_REUSEPORT:
  sk->sk_reuseport = valbool;
  break;

sock_getsockopt函数体中:

case SO_REUSEADDR:
  v.val = sk->sk_reuse;
  break;
case SO_REUSEPORT:
  v.val = sk->sk_reuseport;
  break;

SO_REUSEPORT特性支持之前的事件驱动驱动服务器资源竞争:

之后呢,可以看做是并行的了:

Fastsocket没有重复发明轮子,在SO_REUSEPORT基础上进行进一步的优化等。

嗯,后面准备写一个动态链接库小程序,打算让以前的没有硬编码SO_REUSEPORT的程序也能够在Linux kernel >= 3.9系统上享受真正的端口重用的新特性的支持。

Fastsocket架构图

Image

下面按照其架构图所示内核层面从上到下一一列出。

虚拟文件系统VFS的改进

因为Linux Kernel VFS的同步损耗严重

  • VFS对文件节点Inode和目录Dentry有同步需求
  • 但SOCKET只需要在内存中存在即可,非严格意义上文件系统,其不需要路径,不需要为Inode和Dentry加锁
  • 代码层面略过不必须的常规锁,但又保持了足够的兼容性

提交记录:

a209dfc vfs: dont chain pipe/anon/socket on superblock s_inodes list
4b93688 fs: improve scalability of pseudo filesystems

对VFS的改进,在所提升的性能中占有超过60%的比例,效果非常明显:

Local Listen Table

对于多核多接收队列来说,linux原生的协议栈只能listen在一个socket上面,并且所有完成三次握手还没来得及被应用accept的套接字都会放入其附带的accept队列中,accept系统调用必须串行的从队列取出,当并发量较大时多核竞争,这将成为性能瓶颈,影响建立连接处理速度。

Local Listen Table,fastsocket为每一个CPU核克隆监听套接字,并保存到其本地表中,CPU核之间不会存在accept的竞争关系。下面为引用描述内容:

  • 每个core有一个listen socket table。应用程序建立连接的时候,执行过程会调用local_listen()函数,有两个参数,一个是socket FD,一个是core number. new socket从原始的listen socket(global)拷贝到per-core local socket table. 这些对于应用程序来说都是透明的,提供给应用程序的socketFD是抽象过的,隐藏了底层的实现。
  • 当一个TCP SYN到达本机,kernel首先去local listen table中找匹配的listen socket,如果找到,就通过网卡RSS传递这个socket到一个core,否则就去global listen table中找。
  • 容错方面,当进程崩溃的话,local listen socket会被关闭,进入的连接将会被引导到global Listen socket, 这样的话,别的process可以处理这些连接。由于local listen socket和global listen socket共享FD,所以kernel将会把新的connet通知到相应的process。
  • 如果应用程序进程使用accept()系统调用,那么处理过程是首先去global listen table中查找和操作(因为是读操作,没有使用锁),如果没有找到,那么去core的local table中查找。如果找到,就返回给应用程序。由于listen的时候把socket绑定到了一个core,所以查找的时候也去这个core的local table中查找。
  • epoll兼容性,如果应用程序使用epoll_ctl()系统调用,来把一个listen socket添加到Epoll set中,那么local的listen socket和global的listen socket都被epoll监控。事件发生的时候,epoll_wait()系统调用会返回listen socket,accept()系统调用就会处理这个socket。这样就保证了epoll实现的兼容性。

使用流程图概括上面所述:

Image(9)

Local Established Table

Linux内核使用一个全局的hash表以及锁操作来维护establised sockets(被用来跟踪连接的sockets)。Fastsocket 想法是把全局table分散到per-Core table,当一个core需要访问socket的时候,只在隶属于自己的table中搜索,因此不需要锁操纵,也不存在资源竞争。由fastsocket建立的socket本地local established table中,其他的regular sockets保存在global的table中。core首先去自己的local table中查找(不需要锁),然后去global中查找。

Image(10)

Receive Flow Deliver

默认情况下,应用程序主动发包的时候,发出去的包是通过正在执行本进程的那个CPU 核(系统分配的)来完成的;而接收数据包的时CPU 核是由前面提到的RSS或RPS来传递。这样一来,连接可能由不同的两个CPU核来完成。连接应该在本地化处理。RFS和Intel网卡的FlowDirector可以从软件和硬件上缓解这种情况,但是不完备。

RFD(Receive Flow Deliver)主要的思想是CPU核数主动发起连接的时候可以把CPU core的标识和连接的source port编码到一起。CPU cores和ports的关系由一个关系集合来决定【cores,ports】, 对于一个port,有唯一的一个core与之对应。当一个core来建立connection的时候,RFD随机选择一个跟当前core匹配的port。接收包的时候,RFD负责决定这个包应该让哪一个core来处理,如果当前core不是被选中的cpu core,那么就deliver到选中的cpu core。

Image

一般来说,RFD对代理程序收益比较大,单纯的WEB服务器可以选择禁用。

小结

以上参考了大量的外部资料进行整理而成,进而可以获得一个较为整体的Fastsocket内核架构印象。

Fastsocket的努力,在单个TCP连接的管理从网卡触发的硬中断、软中断、三次握手、数据传输、四次挥手等完整的过程在完整在一个CPU核上进行处理,从而实现了每一个CPU核心TCP资源本地化,这样为多核水平扩展打好了基础,减少全局资源竞争,平行化处理连接,同时降低文件锁的副作用,做到了极为高效的短连接处理方案,不得不赞啊。

引用资料:

posted on 2015-02-04 14:22 nieyong 阅读(4506) 评论(1)  编辑  收藏 所属分类: Socket

评论

# re: Fastsocket学习笔记之内核篇 2015-02-06 09:20 京山游侠

顶。  回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航:
 

公告

所有文章皆为原创,若转载请标明出处,谢谢~

新浪微博,欢迎关注:

导航

<2015年2月>
25262728293031
1234567
891011121314
15161718192021
22232425262728
1234567

统计

常用链接

留言簿(58)

随笔分类(130)

随笔档案(151)

个人收藏

最新随笔

搜索

最新评论

阅读排行榜

评论排行榜