Linux网络数据包的揭秘以及常见的调优方式总结

栏目: IT技术 · 发布时间: 5年前

内容简介:可直接点击上方蓝字(网易游戏运维平台)关注我们,获一手游戏运维方案

可直接点击上方蓝字

(网易游戏运维平台)

关注我们,获一手游戏运维方案

Linux网络数据包的揭秘以及常见的调优方式总结

Linux网络数据包的揭秘以及常见的调优方式总结

lott

网易游戏业务 SRE, 专注于业务运维的质量和效率 , 喜欢研究 Linux 系统原理。目前负责《一梦江湖》、《猎魂觉醒》、《非人学园》等产品的运维工作。

总结是进步的阶梯、分享是快乐的源泉 , 技术人就是要不断总结、不断分享。

作为业务 SRE,我们所运维的业务,常常以 Linux+TCP/UDP daemon 的形式对外提供服务。SRE 需要对服务器数据包的接收和发送路径有全面的了解,以方便在服务异常时能快速定位问题。

以 tcp 协议为例,本文将对 Linux 内核网络数据包接收的路径进行整理和说明,希望对大家所有帮助。

Linux 数据包接收路径的整体说明

接收数据包是一个复杂的过程,涉及很多底层的技术细节 , 这里先做一下大概的说明 :

NIC (network interface card) 在系统启动过程中会向系统注册自己的各种信息,系统会分配专门的内存缓冲区,

NIC 接收到数据包之后,就会存放在内存缓冲区,通过硬件中断通知内核有新的数据包需要处理 .

内核从缓冲区取走 NIC 接收过来的数据,交给 TCP/IP 协议栈处理。

内核的 TCP/IP 协议栈代码进行处理后,更新协议的各种状态,然后交给应用程序的 socket buffer。

然后应用程序就可以通过 read() 系统调用,从对应的 socket 文件中,读取数据。

对内核数据包接收的路径做一下分层,总体可分为三层 :

  1. 网卡层面

  • 1.1 网卡接收到数据包

  • 1.2 将数据包从网卡硬件转移到主机内存中 .

  • 内核层面

    • 2.1 TCP/IP 协议逐层处理

  • 应用程序层面

    • 3.1 应用程序通过 read() 系统调用 , 从 socket buffer 读取数据

    如下图 :

    Linux网络数据包的揭秘以及常见的调优方式总结

    接下来解释一下什么是 NAPI

    什么是 NAPI

    系统启动时会为网卡分配  Ring Buffer (环形缓冲区 ), Ring Buffer 放的是一个个 Packet Descriptor(数据包描述符),是实际数据包的指针。实际的数据包是存放在另一块内存区域中(由网卡 Driver 预先申请好),称为 sk_buffers, sk_buffers 是可以由 DMA(https://en.wikipedia.org/wiki/DMA) 直接访问的 .

    Ring Buffer 里的 Packet Descriptor ,有两种状态:ready 和 used 。初始时 Descriptor 是空的,指向一个空的 sk_buffer,处在 ready 状态。当有数据时,DMA 负责从 NIC 取数据,并在 Ring Buffer 上按顺序找到下一个 ready 的 Descriptor,将数据存入该 Descriptor 指向的 sk_buffer 中,并标记 Descriptor 为 used。因为是按顺序找 ready 的 Descriptor, 所以 Ring Buffer 是个 FIFO 的队列。

    内核采用 struct sk_buffer(https://elixir.bootlin.com/linux/v4.4/source/include/linux/skbuff.h#L545) 来描述一个收到的数据包, sk_buffer 内有个 data 指针会指向实际的物理内存。

    当通过 DMA 机制存放完数据之后,NIC 会触发一个 IRQ(硬件中断) 让 CPU 去处理收到的数据。因为每次触发 IRQ 后 CPU 都要花费时间去处理 Interrupt Handler,如果 NIC 每收到一个 Packet 都触发一个 IRQ 会导致 CPU 花费大量的时间执行 Interrupt Handler,而每次执行只能从 Ring Buffer 中拿出一个 Packet,虽然 Interrupt Handler 执行时间很短,但这么做非常低效,并会给 CPU 带来很多负担。所以目前都是采用一个叫做 New API(NAPI)(https://wiki.linuxfoundation.org/networking/napi) 的机制,去对 IRQ 做合并以减少 IRQ 次数,目前大部分网卡 Driver 都支持 NAPI 机制。NAPI 机制是如何合并和减少 IRQ 次数的 , 可以简单理解为: 中断 + 轮询 。在数据量大时,一次中断后通过轮询接收一定数量数据包再返回,避免产生多次中断 , 具体细节大家可以参考这篇文章 (https://ylgrgyq.github.io/2017/07/23/linux-receive-packet-1/).

    概括一下网卡层面整个数据包的接收过程:

    Linux网络数据包的揭秘以及常见的调优方式总结

    1. 驱动程序事先在内存中分配一片缓冲区来接收数据包 , 叫做 sk_buffers.

    2. 将上述缓冲区的地址和大小(即数据包描述符),加入到 rx ring buffer。描述符中的缓冲区地址是 DMA 使用的物理地址 ;

    3. 驱动程序通知网卡有新的描述符 (或者说有空闲可用的描述符 )

    4. 网卡从 rx ring buffer 中取出描述符 , 从而获取缓冲区的地址和大小 .

    5. 当一个新的数据包到达,网卡 (NIC) 调用 DMA engine,把数据包放入 sk_buffer.

    如果整个过程正常 , 网卡会发起中断,通知内核的中断程序将数据包传递给 IP 层,进入 TCP/IP 协议栈处理。

    每个数据包经过 TCP 层一系列复杂的步骤,更新 TCP 状态机,最终到达 socket 的 recv Buffer,等待被应用程序接收处理。

    然后 , 内核应该会把刚占用掉的描述符重新放入 ring buffer,这样网卡就可以继续使用描述符了。

    我们可以使用 ethtool 命令,进行 Ring Buffer 的查看和设置 .

    1 查看网卡当前的设置(包括Ring  Buffer): ethtool -g eth1
    2 改变Ring Buffer大小: ethtool -G eth1 rx 4096 tx 4096
    

    四 中断处理程序如何把数据包传递给网络协议层

    我们通过一张图来说明下 ,

    Linux网络数据包的揭秘以及常见的调优方式总结

    上图中涉及到非常多的技术细节,限于篇幅我们只做总体的说明 :

    1. NIC 发起的硬件中断(也称为中断处理的上半部),被内核执行之后,开启了软中断(中断处理的下半部),并马上退出硬件中断处理程序 , 以便其他硬件可以继续发起硬件中断 .

    2. 软中断处理程序中,通过 poll 循环把数据从 Ring Buffer 取走,传给网络协议层处理,然后重新开启之前已经禁用的网卡硬件中断 .

    3. 当有新的数据包到达网卡时 , 回到第 1 步 .

    这里有几点需要额外说明 :

    什么是中断处理的上半部和下半部

    我们知道中断随时可能发生,因此中断处理程序也就随时可能执行。所以必须保证中断处理程序能够快速执行,这样才能尽快恢复被中断的代码。因此尽管对硬件而言,操作系统能迅速对其中断进行服务非常重要,而对于系统其他部分而言,让中断处理程序尽可能在短时间内完成运行也同样重要。所以我们一般把中断处理切为 2 个部分,上半部在接收到一个中断时立刻开始执行,但他只做必要的工作,例如对接收的中断进行应答或复位硬件,这些工作都是在所有中断被禁止的情况下完成的。而那些允许被稍后执行的工作,都会推到下半部去,下半部并不会马上执行,而是会在稍后适当的时机执行。

    网卡的软中断处理

    现在的网卡基本都支持 RSS(Receive Side Scaling)(https://en.wikipedia.org/wiki/Network_interface_controller#RSS),也就是多对列技术。一张网卡有多个队列,每个队列都有各自的 IRQ 号和 Ring Buffer,但是默认情况下网卡的软中断都是在 CPU0 上处理,在流量大的时候,会造成 CPU0 负载打满,引起丢包. 我们可以通过绑定中断和 CPU 的亲和性,把中断处理均衡到多核心上 (https://www.vpsee.com/2010/07/load-balancing-with-irq-smp-affinity/),提升系统整体性能 .

    什么是 RPS

    RPS 全称是 Receive Packet Steering, 采用软件模拟的方式,实现了多队列网卡所提供的功能,分散了在多 CPU 系统上数据接收时的软中断负载, 把软中断分到各个 CPU 处理,而不需要硬件支持,在多核 CPU 和单队列网卡的情况下,开启 RPS 可以大大提升网络性能 .

    如果系统开了 RPS, 数据包会被缓冲在 TCP 层之前的队列中 , 我们可以通过 net.core.netdev_max_backlog 适当加大这个队列的长度,以保证上层的处理时间 .

    TCP/IP 协议栈层面

    此时数据包已经接入内核处理区域,由内核的 TCP/IP 协议栈处理

    (一) 连接建立

    大家知道,两个基于 tcp 协议的 socket 要通信,首先要进行连接建立的过程,然后才是数据传输的过程。

    我们先简单看下连接的建立过程,客户端向 server 发送 SYN 包,server 回复 SYN+ACK,同时将这个处于 SYN_RECV 状态的连接保存到半连接队列。客户端返回 ACK 包完成三次握手,server 将 ESTABLISHED 状态的连接移入 accept 队列,等待应用调用 accept()。

    可以看到建立连接涉及两个队列:

    • 半连接队列 (SYN Queue): 保存 SYN_RECV 状态的连接。队列长度由 net.ipv4.tcp_max_syn_backlog 设置

    • 完整连接队列 (ACCEPT Queue): 保存 ESTABLISHED 状态的连接。队列长度为 min(net.core.somaxconn, backlog)。其中 backlog 是我们创建 ServerSocket(int port,int backlog) 时指定的参数,最终会传递给 listen 方法:

    #include
    int listen(int sockfd, int backlog);
    

    如果我们设置的 backlog 大于 net.core.somaxconn,完整连接队列的长度将被设置为 net.core.somaxconn。

    Linux网络数据包的揭秘以及常见的调优方式总结

    注意:不同的编程语言都有相应的 socket 申请方法 , 比如 Python 是 socket 模块.在服务端监听一个端口,底层都要经过 3 个步骤:

    申请 socket、bind 相应的 IP 和 port、调用 listen 方法进行监听。这个 listen 方法 python 会进行封装,别的编程语言也会进行封装,但最终都是调用系统的 listen() 调用

    我们对这两个队列做一下总结 :

    Linux网络数据包的揭秘以及常见的调优方式总结

    (二) 数据传输

    连接建立后 , 就到了 socket 数据传输的层面。此时 kernel 能够为应用程序做的,就是通过 socket Recv Buffer 缓存数据 , 尽量保证上层处理时间 .

    1  Recv Buffer 自动调节机制

    kernel 可以根据实际情况,自动调节 Recv Buffer 的大小 , 以期找到性能和资源的平衡点 .

    当 net.ipv4.tcp_moderate_rcvbuf 设置为 1 时,自动调节机制生效,每个 TCP 连接的 recv Buffer 由下面的 3 元数组指定 (min, default, max):

    net.ipv4.tcp_rmem = 4096    87380   16777216
    

    最初 Recv Buffer 被设置为 87380,同时这个缺省值会覆盖 net.core.rmem_default 的设置 , 随后 recv buffer 根据实际情况在最大值和最小值之间动态调节。

    当 net.ipv4.tcp_moderate_rcvbuf 被设置为 0,或者设置了 socket 选项 SO_RCVBUF,缓冲的动态调节机制被关闭。

    如果缓冲的动态调节机制被关闭 , 同时 socket 自己也没有设置 SO_RCVBUF 选项,那么一个 socket 的默认 Buffer 大小将由 net.core.rmem_default 决定,但是应用程序仍然可以通过 setsockopt() 系统调用,加大自己的 Recv Buffer, 最大不能超过 net.core.rmem_max 的设定 .

    因此,我们可以得出如下总结 :

    • 没有特殊情况 , 建议打开 net.ipv4.tcp_moderate_rcvbuf=1, 这样 kernel 会自动调整每个 socket 的 Recv Buffer

    • 我们应该把 net.ipv4.tcp_rmem 中 max 值和 net.core.rmem_max 值设置成一致,这样假设应用程序没有关注到这个点,仍然可以由 kernel 把它自动调节成系统最大的 Recv Buffer.

    • Recv Buffer 的默认值可以适当进行提高 , 包括 net.core.rmem_default 和 net.ipv4.tcp_rmem 中的 default 设置 , 以更加激进的方式传输数据 .

    关于 Linux 接收数据包链路优化的整体总结

    Linux网络数据包的揭秘以及常见的调优方式总结

    参考文章

    1. linux 网络之数据包的接受过程

      https://www.jianshu.com/p/e6162bc984c8

    2. Linux 网络协议栈收消息过程 -Ring Buffer

      https://ylgrgyq.github.io/2017/07/23/linux-receive-packet-1/

    3. Linux 网络协议栈收消息过程 -Per CPU Backlog

      https://ylgrgyq.github.io/2017/07/24/linux-receive-packet-2/

    4. 网卡收发包总结

      https://www.zybuluo.com/myecho/note/1068383

    5. /proc/sys/net 文档说明

      https://www.kernel.org/doc/Documentation/sysctl/net.txt

    6. NAPI

      https://wiki.linuxfoundation.org/networking/napi

    7. Linux 技巧 : 多核下绑定网卡中断到不同 CPU(core)总结

      https://blog.csdn.net/benpaobagzb/article/details/51044420

    8. Network interface controller

      https://en.wikipedia.org/wiki/Network_interface_controller#RSS

    Linux网络数据包的揭秘以及常见的调优方式总结


    以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

    查看所有标签

    猜你喜欢:

    本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

    Tales from Facebook

    Tales from Facebook

    Daniel Miller / Polity Press / 2011-4-1 / GBP 55.00

    Facebook is now used by nearly 500 million people throughout the world, many of whom spend several hours a day on this site. Once the preserve of youth, the largest increase in usage today is amongst ......一起来看看 《Tales from Facebook》 这本书的介绍吧!

    RGB转16进制工具
    RGB转16进制工具

    RGB HEX 互转工具

    随机密码生成器
    随机密码生成器

    多种字符组合密码

    HEX HSV 转换工具
    HEX HSV 转换工具

    HEX HSV 互换工具