2万字综述:Linux网络性能终极指南

原文:Linux Network Performance Ultimate Guide[1]

作者:Kien Nguyen-Tuan

参考资料

1、Linux 网络栈

资料来源

  • 这是入门指南。在进行任何调整之前,请确保我们了解运行 Linux 的计算机如何接收数据包。

  • Linux 队列: 注意:以下部分将大量使用 sysctl。如果您不熟悉此命令,请查看HOWTO#sysctl 部分[19]。

1.1 Linux 网络数据包接收

  • 详细版本您可以查看PackageCloud 的文章[20]。 - 在网络设备中,NIC 通常会发出 IRQ 来表示数据包已到达并准备好进行处理。 - IRQ(中断请求)是发送给处理器的硬件信号,指示处理器暂停当前活动并处理某些外部事件,例如键盘输入或鼠标移动。 - 在 Linux 中,IRQ 映射存储在 /proc/interrupts 中。 - 当 Linux 内核执行 IRQ 处理程序时,它会以非常高的优先级运行,并且通常会阻止生成其他 IRQ。 因此,设备驱动程序中的 IRQ 处理程序必须尽快执行,并将所有长时间运行的工作推迟到此上下文之外执行。 这就是 softIRQ 系统存在的原因。 - softIRQ 系统是内核用来处理设备驱动程序 IRQ 上下文之外的工作的系统。 对于网络设备,softIRQQ 系统负责处理传入的数据包

  • 初始设置(从步骤 1-4):

注意:有些 NIC 是“多队列”NIC。上图仅显示了单个环形缓冲区以方便理解,但根据您使用的 NIC 和硬件设置,系统中可能会有多个队列。请查看在 CPU 之间共享数据包处理负载部分以了解详细信息。

  1. 数据包到达网卡

  2. NIC 进行验证 MAC(如果不是混杂模式[21])并 FCS 决定放弃或继续

  3. NIC 将DMA(直接内存访问)[22] sk_buff 数据包放入 RAM - 在称为或 skb(套接字内核缓冲区 - SKB[23] )的内核数据结构中。

  4. NIC 将数据包 的引用 放入接收环形缓冲区队列, rx 直到 rx-usecs 超时或 rx-frames。让我们来谈谈 RX 环形缓冲区:

    • 它是一个循环缓冲区[24],_溢出时会简单地覆盖现有数据_。
    • 不包含数据包数据 skbs。而是由指向 DMA 到 RAM 的描述符组成(步骤 2)。
    • 固定大小、FIFO 且位于 RAM(当然)。
  5. NIC 引发 HardIRQ“硬中断”。

    egrep “CPU0|eth3” /proc/interrupts
        CPU0 CPU1 CPU2 CPU3 CPU4 CPU5
    110:    0    0    0    0    0    0   IR-PCI-MSI-edge   eth3-rx-0
    111:    0    0    0    0    0    0   IR-PCI-MSI-edge   eth3-rx-1
    112:    0    0    0    0    0    0   IR-PCI-MSI-edge   eth3-rx-2
    113:    2    0    0    0    0    0   IR-PCI-MSI-edge   eth3-rx-3
    114:    0    0    0    0    0    0   IR-PCI-MSI-edge   eth3-tx
    
    
    
    • HardIRQ:来自硬件的中断,称为“上半部”中断。
    • 当 NIC 接收到传入数据时,它会使用 DMA 将数据复制到内核缓冲区中。NIC 通过发出 HardIRQ 通知内核此数据。这些中断由中断处理程序处理,这些中断处理程序只做很少的工作,因为它们已经中断了另一个任务,并且无法自行中断。
    • HardIRQ 在 CPU 使用率方面可能会很昂贵,尤其是在持有内核锁的情况下。如果它们执行时间过长,将导致 CPU 无法响应其他 HardIRQ,因此内核引入了 SoftIRQs(Soft Interrupts),这样可以将 HardIRQ 处理程序中耗时的部分移到 SoftIRQ 处理程序中慢慢处理。我们在接下来的步骤中会讨论 SoftIRQ。
    • 可以看到 HardIRQ,其中 /proc/interrupts 每个队列在第一列中都分配有一个中断向量。这些中断向量在系统启动或加载 NIC 设备驱动程序模块时初始化。每个 RX 和 TX 队列都分配有一个唯一的向量,该向量会通知中断处理程序中断来自哪个 NIC/队列。这些列以计数器值的形式表示传入中断的数量:
  6. CPU 运行 IRQ handler 运行驱动程序代码的。

  7. 驱动程序将调度一个NAPI[25],清除 NIC 上的 HardIRQ,以便它可以为新数据包到达生成 IRQ。

  8. 驱动触发 SoftIRQ (NET_RX_SOFTIRQ)

    ps aux | grep ksoftirq
                                                                      # ksotirqd/<cpu-number>
    root          13  0.0  0.0      0     0 ?        S    Dec13   0:00 [ksoftirqd/0]
    root          22  0.0  0.0      0     0 ?        S    Dec13   0:00 [ksoftirqd/1]
    root          28  0.0  0.0      0     0 ?        S    Dec13   0:00 [ksoftirqd/2]
    root          34  0.0  0.0      0     0 ?        S    Dec13   0:00 [ksoftirqd/3]
    root          40  0.0  0.0      0     0 ?        S    Dec13   0:00 [ksoftirqd/4]
    root          46  0.0  0.0      0     0 ?        S    Dec13   0:00 [ksoftirqd/5]
    
    
    
    watch -n1 grep RX /proc/softirqs
    watch -n1 grep TX /proc/softirqs
    
    
    
    • 监控命令:

    • 让我们来谈谈 SoftIRQ,也称为“下半部”中断。它是一个内核例程,计划在其他任务不会被中断时运行。

    • 目的:耗尽网络适配器接收 Rx 环形缓冲区。

    • 这些例程以进程的形式运行 ksoftirqd/cpu-number,并调用驱动程序特定的代码函数。

    • 检查命令:

  9. NAPI 从 rx 环形缓冲区轮询数据。

    • 如果 SoftIRQ 运行时间不够长,传入数据的速率可能会超过内核耗尽缓冲区的能力。结果,NIC 缓冲区将溢出,流量将丢失。有时,需要增加 SoftIRQ 在 CPU 上运行的时间。这称为 netdev_budget
      sysctl net.core.netdev_budget
      net.core.netdev_budget = 300
    
    
    
    • netdev_budget_usecs:1 个 NAPI 轮询周期的最大微秒数。当 netdev_budget_usecs 轮询周期已过或处理的数据包数达到时,轮询将退出 netdev_budget
      sysctl net.core.netdev_budget_usecs
    
    
      net.core.netdev_budget_usecs = 8000
    
    
    
    • dev_weight:内核在 NAPI 中断上可以处理的最大数据包数量,这是一个 PER-CPU 变量。对于支持 LRO 或 GRO_HW 的驱动程序,硬件聚合数据包在此计为一个数据包。

    • 检查命令,默认值为 300,这意味着 SoftIRQ 进程在离开 CPU 之前要从 NIC 中消耗 300 条消息。

    • 检查命令:

    • NAPI 的编写是为了提高处理传入卡的数据包的效率。我们都知道,HardIRQ 的代价很高,因为它们不能被中断。即使使用 中断合并(稍后会详细介绍),中断处理程序也会完全独占一个 CPU 核心。NAPI 的设计允许驱动程序进入轮询模式,而不是每次接收所需的数据包时都进入 HardIRQ 模式。

    • 步骤 1->9 简要:

    • netdev_budget_usecs 轮询例程具有预算,它通过使用超时或 netdev_budget 数据包来确定允许代码使用的 CPU 时间 dev_weight。这是为了防止 SoftIRQ 独占 CPU。完成后,内核将退出轮询例程并重新准备,然后整个过程将重复进行。

    • 让我们讨论一下 netdev_budget_usecs 超时或 netdev_budget 数据 dev_weight 包:

      sysctl net.core.dev_weight
    
    
      net.core.dev_weight = 64
    
    
    
  10. Linux 还为 分配内存 sk_buff

  11. Linux 填充元数据:协议、接口、setmatchheader、删除以太网

  12. Linux 将 skb 传递给内核栈( netif_receive_skb

  13. 它设置网络头,克隆 skb 到 taps(即 tcpdump)并将其传递给 tc ingress

  14. netdev_max_backlog 数据包按照以下算法处理到指定大小的 qdisc default_qdisc

    sysctl net.core.netdev_max_backlog
    
    
    net.core.netdev_max_backlog = 1000
    
    
    
    • 默认情况下,值为 1000(取决于网络接口驱动程序): ifconfig <interface> | grep rxqueuelen

    • rxqueuelen:接收队列长度,是一个 TCP/IP 堆栈网络接口值,用于设置网络接口设备每个内核接收队列允许的数据包数量。

    • default_qdisc:用于网络设备的默认排队规则。这允许使用替代方法覆盖 pfifo_fast 的默认值。由于默认排队规则是在没有附加参数的情况下创建的,因此最适合无需配置即可正常工作的排队规则,如随机公平队列 (sfq)、CoDel (codel) 或公平队列 CoDel (fq_codel)。有关每个 QDisc 的完整详细信息,请参见 man tc <qdisc-name>(例如 man tc fq_codel)。

    • netdev_max_backlog:Linux 内核中的一个队列,在从 NIC 接收流量后,但在协议栈(IP、TCP 等)处理之前,流量会存储在该队列中。每个 CPU 核心都有一个积压队列。给定核心的队列可以自动增长,包含的数据包数量最多为设置指定的最大值 netdev_max_backlog

    • 换句话说,当接口接收数据包的速度比内核处理数据包的速度快时,这是在 INPUT 端(接收 dsic)排队的数据包的最大数量。

    • 检查命令,默认值为 1000。

  15. 它调用 ip_rcv 并处理 IP 数据包

  16. 它调用 netfilter( PREROUTING)

  17. 它查看路由表,看看是转发还是本地

  18. 如果是本地的,则调用 netfilter ( LOCAL_IN)

  19. 它调用 L4 协议(例如 tcp_v4_rcv

  20. 它找到了正确的插座

  21. 它进入 tcp 有限状态机

  22. 将数据包排入接收缓冲区并按 tcp_rmem 规则调整大小

    • net.core.rmem_maxnet.ipv4.tcp-rmem‘最大值’之间,较大值优先[26]。

    • 增加此缓冲区以支持扩展到更大的窗口大小。更大的窗口会增加在需要确认 (ACK) 之前要传输的数据量。这可以减少总体延迟并提高吞吐量。

    • 此设置通常设置为非常保守的值 262,144 字节。建议将此值设置为内核允许的最大值。4.x 内核接受超过 16 MB 的值。

    • min:TCP 套接字使用的接收缓冲区的最小大小。即使在中等内存压力下,每个 TCP 套接字都可以保证这一点。默认值:4 KB。

    • 默认值:TCP 套接字使用的接收缓冲区的初始大小。此值覆盖 net.core.rmem_default 其他协议使用的值。默认值:131072 字节。此值导致初始窗口为 65535。

    • max:为 TCP 套接字自动选择的接收缓冲区允许的最大接收缓冲区大小。此值不会覆盖 net.core.rmem_max。使用 调用 setsockopt()SO_RCVBUF 禁用该套接字的接收缓冲区大小的自动调整,在这种情况下将忽略此值。 SO_RECVBUF 设置 TCP 接收缓冲区的固定大小,它将覆盖 tcp_rmem,并且内核将不再动态调整缓冲区。设置的最大值 SO_RECVBUF 不能超过 net.core.rmem_max。通常,我们不会使用它。默认值:介于 131072 和 6MB 之间,取决于 RAM 大小。

    • 如果启用了 tcp_moderate_rcvbuf,内核将自动调整接收缓冲区

    • tcp_rmem:包含 3 个值,分别代表 TCP 套接字接收缓冲区的最小大小、默认大小和最大大小。

    • net.core.rmem_max:TCP 接收缓冲区大小的上限。

  23. 内核将发出信号,表示有数据可供应用程序使用(epoll 或任何轮询系统)

  24. 应用程序唤醒并读取数据

1.2 Linux 内核网络传输

虽然比接收逻辑逻辑简单,但发送逻辑仍然值得研究。

  1. 应用程序发送消息( sendmsg 或其他)

  2. TCP 发送报文分配 skb_buff

  3. 它将 skb 放入大小 tcp_wmem

    sysctl net.ipv4.tcp_wmem
    net.ipv4.tcp_wmem = 4096        16384   262144
    
    
    
    • TCP 发送缓冲区的大小将由内核在 min 和 max 之间动态调整。初始大小为默认大小。

    • net.core.wmem_max:TCP 发送缓冲区大小的上限。与 类似 net.core.rmem_max(但用于传输)。

    • min:为 TCP 套接字保留的发送缓冲区内存量。每个 TCP 套接字自诞生之日起就有权使用它。默认值:4K

    • 默认值:TCP 套接字使用的发送缓冲区的初始大小。此值覆盖其他协议使用的 net.core.wmem_default。它通常低于 net.core.wmem_default。默认值:16K

    • max:允许用于自动调整 TCP 套接字发送缓冲区的最大内存量。此值不会覆盖 net.core.wmem_max。使用 调用 setsockopt()SO_SNDBUF 禁用该套接字发送缓冲区大小的自动调整,在这种情况下将忽略此值。 SO_SNDBUF 设置发送缓冲区的固定大小,它将覆盖 tcp_wmem,并且内核将不再动态调整缓冲区。SO_SNDBUF 设置的最大值不能超过 net.core.wmem_max。通常,我们不会使用它。默认值:介于 64K 和 4MB 之间,取决于 RAM 大小。

    • tcp_wmem:包含 3 个值,分别代表 TCP 套接字发送缓冲区的最小大小、默认大小和最大大小。

    • 检查命令:

  4. 构建 TCP 标头(源端口和目标端口、校验和)

  5. 调用 L3 处理程序(在本例中 ipv4tcp_write_xmittcp_transmit_skb

  6. L3( ip_queue_xmit) 完成其工作:构建 IP 报头并调用 netfilter( LOCAL_OUT)

  7. 调用输出路由操作

  8. 调用 netfilter( POST_ROUTING)

  9. 对数据包进行分片 ( ip_output)

  10. 调用 L2 发送函数 ( dev_queue_xmit)

  11. txqueuelen 使用其算法将长度为输出(QDisc)的队列输入 default_qdisc

    • 默认情况下,值为 1000(取决于网络接口驱动程序): ifconfig <interface> | grep txqueuelen

    • txqueuelen:传输队列长度,是 TCP/IP 堆栈网络接口值,用于设置网络接口设备每个内核传输队列允许的数据包数量。

    • default_qdisc:用于网络设备的默认排队规则。这允许使用替代方法覆盖 pfifo_fast 的默认值。由于默认排队规则是在没有附加参数的情况下创建的,因此最适合无需配置即可正常工作的排队规则,如随机公平队列 (sfq)、CoDel (codel) 或公平队列 CoDel (fq_codel)。有关每个 QDisc 的完整详细信息,请参见 man tc <qdisc-name>(例如 man tc fq_codel)。

  12. 驱动程序代码将数据包排入 ring buffer tx

  13. soft IRQ (NET_TX_SOFTIRQ) 驱动程序将在超时后执行 tx-usecstx-frames

  14. 重新启用 NIC 的硬 IRQ

  15. 驱动程序会将所有要发送的数据包映射到某个 DMA 区域

  16. NIC 通过 DMA 从 RAM 获取数据包并进行传输

  17. 传输完成后,NIC 将发出一个 hard IRQ 信号来表示传输完成

  18. 驱动程序将处理此 IRQ(将其关闭)

  19. 并安排( soft IRQ)NAPI 投票系统

  20. NAPI 将处理接收数据包信号并释放 RAM

2、网络性能调优

调整 NIC 以获得最佳吞吐量和延迟是一个复杂的过程,需要考虑许多因素。没有可以广泛适用于每个系统的通用配置。

网络性能调整需要考虑一些因素。请注意,您的设备中的接口卡名称可能不同,请更改相应的值。

接下来,让我们跟踪数据包的接收(和传输)并进行一些调整。

2.1 快速指南

2.1.1 /proc/net/softnet_stat& /proc/net/sockstat

在我们继续之前,让我们先讨论一下 /proc/net/softnet_stat& /proc/net/sockstat 因为这些文件将会被大量使用。

cat /proc/net/softnet_stat

0000272d 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
000034d9 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
00002c83 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000002
0000313d 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000003
00003015 00000000 00000001 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000004
000362d2 00000000 000000d2 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000005

  • softnet_stat 文件的每一行代表从 CPU0 开始的一个 CPU 核心。
  • 每列的统计数据以十六进制提供
  • 第一列是中断处理程序接收的帧数。
  • netdev_max_backlog 第二列是由于超出而丢失的帧数。
  • netdev_budget 第三列是当仍有工作要做时 ksoftirqd 耗尽 CPU 时间的次数。
  • 其他列可能因 Linux 版本的不同而有所不同。
cat /proc/net/sockstat

sockets: used 937
TCP: inuse 21 orphan 0 tw 0 alloc 22 mem 5
UDP: inuse 9 mem 5
UDPLITE: inuse 0
RAW: inuse 0
FRAG: inuse 0 memory 0

  • 检查 mem 字段。它是通过 sk_buff->truesize 对所有套接字求和来计算的。
  • 更多详细信息请点击此处[27]

2.1.2 ss

  • ss 是另一个用于调查套接字的实用程序。它用于转储套接字统计信息。它允许显示类似于的信息 netstat。它可以显示比其他工具更多的 TCP 和状态信息。
  • 更多信息请参见手册页: man ss
  • 例如,检查套接字内存使用情况:
ss -tm

#  -m, --memory
#         Show socket memory usage. The output format is:

#         skmem:(r<rmem_alloc>,rb<rcv_buf>,t<wmem_alloc>,tb<snd_buf>,
#                       f<fwd_alloc>,w<wmem_queued>,o<opt_mem>,
#                       bl<back_log>,d<sock_drop>)

#         <rmem_alloc>
#                the memory allocated for receiving packet

#         <rcv_buf>
#                the total memory can be allocated for receiving packet

#         <wmem_alloc>
#                the memory used for sending packet (which has been sent to layer 3)

#         <snd_buf>
#                the total memory can be allocated for sending packet

#         <fwd_alloc>
#                the  memory  allocated  by  the  socket as cache, but not used for receiving/sending packet yet. If need memory to send/receive packet, the memory in this
#                cache will be used before allocate additional memory.

#         <wmem_queued>
#                The memory allocated for sending packet (which has not been sent to layer 3)

#         <ropt_mem>
#                The memory used for storing socket option, e.g., the key for TCP MD5 signature

#         <back_log>
#                The memory used for the sk backlog queue. On a process context, if the process is receiving packet, and a new packet is received, it will be put into  the
#                sk backlog queue, so it can be received by the process immediately

#         <sock_drop>
#                the number of packets dropped before they are de-multiplexed into the socket

#  -t, --tcp
#         Display TCP sockets.

State       Recv-Q Send-Q        Local Address:Port        Peer Address:Port
ESTAB       0      0             192.168.56.102:ssh        192.168.56.1:56328
skmem:(r0,rb369280,t0,tb87040,f0,w0,o0,bl0,d0)

# rcv_buf: 369280 bytes
# snd_buf: 87040 bytes

2.1.3 netstat

  • 命令行实用程序,可打印有关开放网络连接和协议栈统计信息的信息。它从 /proc/net/ 文件系统检索有关网络子系统的信息。这些文件包括:

    • /proc/net/dev(设备信息)
    • /proc/net/tcp(TCP 套接字信息)
    • /proc/net/unix(Unix 域套接字信息)
  • 有关 netstat 及其引用文件的更多信息 /proc/net/,请参阅 netstat 手册页: man netstat

2.1.4 sysctl

  • 而不是直接通过文件系统 echo 中的值来修改系统变量 /proc
echo "value" > /proc/sys/location/variable

  • sysctl 命令可用于更改系统/网络设置。它提供了临时覆盖默认设置值的方法,用于评估目的,以及永久更改系统重启后保持不变的值。
# To display a list of available sysctl variables
sysctl -a | less
# To only list specific variables use
sysctl variable1 [variable2] [...]
# To change a value temporarily use the sysctl command with the -w option:
sysctl -w variable=value
# To override the value persistently, the /etc/sysctl.conf file must be changed. This is the recommend method. Edit the /etc/sysctl.conf file.
vi /etc/sysctl.conf
# Then add/change the value of the variable
variable = value
# Save the changes and close the file. Then use the -p option of the sysctl command to load the updated sysctl.conf settings:
sysctl -p or sysctl -p /etc/sysctl.conf
# The updated sysctl.conf values will now be applied when the system restarts.

2.2 NIC 环形缓冲区

  • 首先,检查步骤 (4) - NIC 环形缓冲区。它是一个循环缓冲区,大小固定,FIFO,位于 RAM 中。缓冲区可以顺利接受大量连接而不丢弃它们,当您看到丢弃或超限时,您可能需要增加这些队列,也就是说,传入的数据包数量超过了内核能够处理的数据包数量,副作用可能是延迟增加。
  • 环形缓冲区的大小通常设置为小于最大值。通常,增加接收缓冲区大小就足以防止丢包,因为它可以让内核有更多时间耗尽缓冲区。
  ethtool -g eth3
  Ring parameters for eth3:
  Pre-set maximums:
  RX: 8192
  RX Mini: 0
  RX Jumbo: 0
  TX: 8192
  Current hardware settings:
  RX: 1024
  RX Mini: 0
  RX Jumbo: 0
  TX: 512
  # eth3's inteface has the space for 8KB but only using 1KB

  # Increase both the Rx and Tx buffers to the maximum
  ethtool -G eth3 rx 8192 tx 8192

  ethtool -S eth3 | grep -e "err" -e "drop" -e "over" -e "miss" -e "timeout" -e "reset" -e "restar" -e "collis" -e "over" | grep -v "\: 0"

  • RHEL/CentOS:使用 /sbin/ifup-local,详情请参见此处。[28]

  • Ubuntu:关注这里[29]

  • 持久化更改:

  • 如何监控:

  • 更改命令:

  • 检查命令:

2.3 中断合并 (IC) - rx-usecs、tx-usecs、rx-frames、tx-frames(硬件 IRQ)

  • 继续执行步骤 (5),硬中断 - HardIRQ。NIC 排队引用接收环形缓冲区队列 rx 中的数据包,直到 rx-usecs 超时或 rx-frames,然后引发 HardIRQ。这称为 中断合并

    • 中断过早:系统性能不佳(内核停止正在运行的任务来处理硬中断)

    • 中断太晚:流量没有及时从 NIC 上分离出来 -> 更多流量 -> 覆盖 -> 流量丢失!

    • 网络将接收/传输的流量(帧数) rx/tx-frames,或接收/传输流量后经过的时间(超时) rx/tx-usecs

  • 更新 中断合并 可以减少 CPU 使用率、hardIRQ,可能会以延迟为代价增加吞吐量

  • 调优:

  ethtool -c eth3

  Coalesce parameters for eth3:
  Adaptive RX: on TX: off # Adaptive mdoe
  stats-block-usecs: 0
  sample-interval: 0
  pkt-rate-low: 400000
  pkt-rate-high: 450000
  rx-usecs: 16
  rx-frames: 44
  rx-usecs-irq: 0
  rx-frames-irq: 0

  # Turn adaptive mode off
  # Interrupt the kernel immediately upon reception of any traffic
  ethtool -C eth3 adaptive-rx off rx-usecs 0 rx-frames 0

  • 如何监控:缺。

  • 在中断内核之前,至少允许一些数据包在 NIC 中缓冲,并至少允许一些时间通过。这些值取决于系统功能和收到的流量。

  • 更改命令:

  • 自适应模式使卡能够自动调节 IC。驱动程序将检查流量模式和内核接收模式,并实时估计合并设置,旨在防止数据包丢失 -> 如果收到许多小数据包,则很有用。

  • 更高的中断合并有利于带宽而不是延迟:VOIP 应用程序(延迟敏感)可能比文件传输(吞吐量敏感)需要更少的合并

  • 检查命令:

2.4 IRQ 亲和性

  • IRQ 具有关联的“亲和属性”, smp_affinity 它定义了允许执行该 IRQ 的中断服务例程 (ISR) 的 CPU 核心。此属性可用于通过将中断关联和应用程序的线程关联分配给一个或多个特定 CPU 核心来提高应用程序性能。这允许在指定的中断和应用程序线程之间共享缓存行。
  • 默认情况下,它由守护进程控制`irqbalancer`[30]。
systemctl status irqbalance.service

  • 但如果需要,也可以手动平衡,以确定是否 irqbalance 未以最佳方式平衡 IRQ,从而导致数据包丢失。在某些非常特殊的情况下,永久手动平衡中断可能会有所帮助。在进行此类调整之前,请确保停止 irqbalance
systemctl stop irqbalance.service

  • 特定 IRQ 号的中断亲和性值存储在关联 /proc/irq/<IRQ_NUMBER>/smp_affinity 文件中,root 用户可以查看和修改该文件。存储在此文件中的值是一个十六进制位掩码,代表系统中的所有 CPU 核心。
  • 要在具有 4 个核心的服务器上设置以太网驱动程序的中断亲和性(例如):
# Determine the IRQ number associated with the Ethernet driver
grep eth0 /proc/interrupts

32:   0     140      45       850264      PCI-MSI-edge      eth0

# IRQ 32
# Check the current value
# The default value is 'f', meaning that the IRQ can be serviced
# on any of the CPUs
cat /proc/irq/32/smp_affinity

f

# CPU0 is the only CPU used
echo 1 > /proc/irq/32/smp_affinity
cat /proc/irq/32/smp_affinity

1

# Commas can be used to delimit smp_affinity values for discrete 32-bit groups
# This is required on systems with more than 32 cores
# For example, IRQ  40 is serviced on all cores of a 64-core system
cat /proc/irq/40/smp_affinity

ffffffff,ffffffff

# To service IRQ 40 on only the upper 32 cores
echo 0xffffffff,00000000 > /proc/irq/40/smp_affinity
cat /proc/irq/40/smp_affinity

ffffffff,00000000

  • 用于在 Intel NIC 上设置 IRQ 亲和性的脚本[31],可处理具有 > 32 个核心的系统。
  • 正如我所说,IRQ 亲和性可以提高性能,但仅限于具有预定义工作负载的非常特定的配置。这是一把双刃剑[32]。

2.5 在 CPU 之间共享数据包处理负载

资料来源:

以前,一切都很简单。网卡很慢,只有一个队列。当数据包到达时,网卡通过 DMA 复制数据包并发出中断,Linux 内核收获这些数据包并完成中断处理。随着网卡速度越来越快,基于中断的模型可能会因大量传入数据包而导致 IRQ 风暴。这将消耗大部分 CPU 功率并冻结系统。为了解决这个问题,提出了NAPI[36](中断和轮询)。当内核从网卡接收到中断时,它开始轮询设备并尽快收获队列中的数据包。NAPI 与当今常见的 1Gbps 网卡配合良好。但是,对于 10Gbps、20Gbps 甚至 40Gbps 网卡,NAPI 可能不够用。如果我们仍然使用一个 CPU 和一个队列来接收数据包,这些卡将需要更快的 CPU。幸运的是,多核 CPU 现在很流行,那么为什么不并行处理数据包呢?

2.5.1 接收端扩展 (RSS)

  • 当数据包到达 NIC 时,它们会被添加到接收队列。在设备驱动器初始化期间,接收队列会被分配一个 IRQ 号,并且一个可用的 CPU 处理器会被分配给该接收队列。该处理器负责为 IRQ 提供中断服务路由 (ISR)。通常,数据处理也由执行 ISR 的同一处理器完成。

    • eth1 接口有 4 个接收队列和 4 个发送队列,56-59 IRQ 被分配给这些队列。现在数据包处理负载被分配到 4 个 CPU 上,从而实现更高的吞吐量和更低的延迟。

    • 这些图片来自balodeamit 博客[37]

    • IRQ 53 用于“eth1-TxRx-0”单声道队列。

    • 检查 smp_affinity->队列已配置为将中断发送到 CPU8。

    • 如果有大量网络流量 -> 则只有单核负责处理数据。ISR 例程很小,因此如果在单核上执行,性能不会有很大差异,但数据处理和在 TCP/IP 堆栈中向上移动数据需要时间(其他核处于空闲状态)。
    • RSS 来救场了!RSS 允许将网卡配置为分布在多个发送和接收队列(环形缓冲区)中。这些队列分别映射到每个 CPU 处理器。当每个队列生成中断时,它们将被发送到映射的处理器 -> 网络流量由多个处理器处理。

  • RSS 提供了在多处理环境中并行接收处理的好处。
  • 这是 NIC 技术。它支持多个队列,并在 NIC 中集成了哈希函数(按源和目标 IP 以及(如果适用)按 TCP/UDP 源和目标端口将数据包分发到不同的队列)。NIC 为每个传入数据包计算一个哈希值。根据哈希值,NIC 将相同数据流的数据包分配给单个队列,并在队列之间均匀分配流量。
  • 用命令检查 ethool -L
  • 根据Linux 内核文档[38], RSS should be enabled when latency is a concern or whenever receive interrupt processing froms a bottleneck... For low latency networking, the optimal setting is to allocate as many queues as there are CPUs in the system (or the NIC maximum, if lower)

  • // WIP——命令!

2.5.2 接收数据包控制 (RPS)

  • 从逻辑上讲,RPS 是 RSS 的软件实现。由于是软件实现,因此它稍后会在数据路径中被调用。RSS 选择队列,从而选择运行硬件中断处理程序的 CPU,而 RPS 选择 CPU 在中断处理程序之上执行协议处理。
  • 当驱动程序接收到一个数据包时,它会将数据包包装在一个套接字缓冲区中 sk_buff,该缓冲区包含 u32 该数据包的哈希值(基于源 IP、源端口、目标 IP 和目标端口)。由于同一 TCP/UDP 连接(流)的每个数据包共享相同的哈希值,因此使用同一个 CPU 来处理它们是合理的。之后,它将到达 netif_rx_internal()netif_receive_skb_internal(),然后 get_rps_cpu() 将被调用将哈希映射到 中的条目 rps_map,即 CPU id。获取 CPU id 后, enqueue_to_backlog() 将 sk_buff 放入特定的 CPU 队列以 进行进一步处理。每个 CPU 的队列都分配在每个 CPU 变量中`softnet_data`[39]。

  • 使用 RPS 的好处与 RSS 相同:在 CPU 之间共享数据包处理的负载。

    • 如果有 RSS 则可能不需要。
    • 如果 CPU 数量多于队列数量,RPS 仍然有用。
  • RPS 需要使用 kconfig 符号编译的内核 CONFIG_RPS(默认情况下,SMP 启用该符号)。即使编译后,RPS 仍处于禁用状态,直到明确配置为止。可以使用 sysfs 文件条目为每个接收队列配置 RPS 可以转发流量的 CPU 列表:

/sys/class/net/<dev>/queues/rx-<n>/rps_cpus

# This file implements a bitmap of CPUs
# 0 (default): disabled

  • 建议配置:

    • 单队列设备: rps_cpus- 与中断 CPU 位于同一内存域中的 CPU。如果 NUMA 局部性不是问题, rps_cpus- 系统中的所有 CPU。在高中断率下,将中断 CPU 从映射中排除可能是明智之举,因为它已经执行了大量工作。
    • 多队列系统:如果配置了 RSS -> RPS 是多余的且不必要的。如果硬件队列比 CPU 少,那么如果 rps_cpus 每个队列都与该队列的中断 CPU 共享相同的内存域,则 RPS 可能会有益。

2.5.3 接收流控制 (RFS)

  • 虽然 RPS 根据流量分发数据包,但它并没有考虑用户空间应用程序。

    • 应用程序可能在 CPU A 上运行,内核将数据包放在 CPU B 的队列中。
    • CPU A 只能使用自己的缓存,CPU B 中缓存的数据包变得毫无用处。
  • RFS 为应用程序进一步扩展了 RPS。

  • CONFIG_RPS 仅当启用 kconfig 符号时,RFS 才可用。

  • RFS 并不维护每个队列的哈希到 CPU 映射,而是维护一个全局的流到 CPU 表。 rps_sock_flow_table 该表的大小可以调整:

sysctl -w net.core.rps_sock_flow_entries 32768

  • 虽然套接字流表改善了应用程序的局部性,但它也带来了一个问题。当调度程序将应用程序迁移到新 CPU 时,旧 CPU 队列中剩余的数据包将变为未完成的,应用程序可能会收到无序的数据包。为了解决这个问题,RFS 使用每个队列 rps_dev_flow_table 来跟踪未完成的数据包。

    • 每个队列流表的大小 rps_dev_flow_table 可以通过 sysfs 接口配置: /sys/class/net/<dev>/queues/rx-<n>/rps_flow_cnt.
  • 接下来的步骤太复杂了,如果你想知道,请查看这个[40]。

  • 建议配置:

    • 建议的流量数取决于任何给定时间的预期活动连接数,该数可能明显少于连接数- 32768 > rps_sock_flow_entries
    • 单队列设备: rps_flow_cnt = rps_sock_flow_entries
    • 多队列设备:( rps_flow_cnt 每个队列)= rps_sock_flow_entries / N(N 为队列数)。

2.5.4 加速接收流控制 (aRFS)

  • 加速 RFS 之于 RFS 就像 RSS 之于 RPS:一种硬件加速的负载平衡机制,它使用软状态根据使用每个流的数据包的应用程序的运行位置来引导流。

  • aRFS 的性能应该比 RFS 更好,因为数据包被直接发送到使用数据的线程本地的 CPU。

  • 仅当满足以下条件时,aRFS 才可用:

    • aRFS 必须由网络接口 卡支持(导出 ndo_rx_flow_steer netdevice 函数)
    • ntuple 必须启用过滤。
    • 内核是用 编译的 CONFIG_RFS_ACCEL
  • CPU 到队列的映射是根据驱动程序为每个接收队列配置的 IRQ 亲和性自动推断出来的,因此不需要额外的配置。

  • 建议配置:

    • 只要想要使用 RFS 并且 NIC 支持硬件加速,即可启用。

2.6 中断合并(软 IRQ)

  • net.core.netdev_budget_usecs

    • 更改命令:

    • 保留该值,检查此项[41]

    • 调优:

    sysctl -w net.core.netdev_budget_usecs <value>
    
    
    
  • net.core.netdev_budget

    • 更改命令:

    • 保留该值,检查此项[42]

    • 如何监控:

    • 如果第一列以外的任何一列都在增加,则需要更改预算。小幅增加是正常的,不需要调整。

    • 调优:

    sysctl -w net.core.netdev_budget <value>
    
    
    
    cat /proc/net/softnet_stat
    
    
    
  • net.core.dev_weight

    • 更改命令:

    • 保留该值,检查此项[43]

    • 如何监控:

    • 调优:

    sysctl -w net.core.dev_weight <value>
    
    
    
    cat /proc/net/softnet_stat
    
    
    

2.7 接收 QDisc

  • 在步骤 (14) 中,我提到了 netdev_max_backlog,这是关于每个 CPU 的积压队列。 netif_receive_skb() 内核函数(步骤 (12))将找到数据包对应的 CPU,并将数据包排入该 CPU 的队列。如果该处理器的队列已满且已达到最大大小,则数据包将被丢弃。队列的默认大小 - netdev_max_backlog 值为 1000,这可能不足以用于以 1Gbps 运行的多个接口,甚至以 10Gbps 运行的单个接口。
  • 调优:
  cat /proc/net/softnet_stat

  • 第二列是当 netdev 积压队列溢出时递增的计数器。

  • 将值加倍 -> 检查 /proc/net/softnet_stat

  • 如果利率降低 -> 价值加倍

  • 重复,直到确定最佳尺寸并且液滴不再增加

  • 更改命令:

    sysctl -w net.core.netdev_max_backlog <value>
    
    
    
  • 保留该值,检查此项[44]

  • 如何监控:确定积压是否需要增加。

2.8 发送 QDisc - txqueuelen 和 default_qdisc

  • 在步骤(11)(传输)中,有 txqueuelen 一个队列/缓冲区来面对连接缓冲区并应用流量控制(tc)[45]。

  • 调优:

  ifconfig <interface> txqueuelen value

  ip -s link
  # Check RX/TX dropped?

  • 如何监控:

  • 更改命令:

  • default_qdisc 也可以进行更改,因为每个应用程序都有不同的负载并且需要流量控制,并且它也用于对抗缓冲区浮动[46]。可以查看本文 - 队列学科部分[47]。

  • 调优:

  sysctl -w net.core.default_qdisc <value>
  ``
- 保留该值,检查[此项](https://access.redhat.com/discussions/2944681 "此项")
- 如何监控:

```shell
tc -s qdisc ls dev <interface>
# Example
qdisc fq_codel 0: root refcnt 2 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
  Sent 33867757 bytes 231578 pkt (dropped 0, overlimits 0 requeues 6) # Dropped, overlimits, requeues!!!
  backlog 0b 0p requeues 6
    maxpacket 5411 drop_overlimit 0 new_flow_count 1491 ecn_mark 0
    new_flows_len 0 old_flows_len 0

  • 更改命令:

### 2.9 TCP 读写缓冲区/队列

- 定义在`tcp_mem`和 `tcp_moderate_rcvbuf`时指定的[内存压力](https://wwwx.cs.unc.edu/~sparkst/howto/network_tuning.php "内存压力")是多少。

- 我们可以调整缓冲区的 mix-max 大小来提高性能:
    - 更改命令:
    ```shell
    sysctl -w net.ipv4.tcp_rmem="min default max"
    sysctl -w net.ipv4.tcp_wmem="min default max"
    ```
    - 保留该值,检查[此项](https://access.redhat.com/discussions/2944681 "此项")
    - 如何监控:检查`/proc/net/sockstat`。

### 2.10 TCP FSM 和拥塞算法

> Accept 和 SYN 队列由 net.core.somaxconn 和 net.ipv4.tcp_max_syn_backlog 控制。[现在 net.core.somaxconn 限制了这两个队列的大小](https://blog.cloudflare.com/syn-packet-handling-in-the-wild/#queuesizelimits "现在 net.core.somaxconn 限制了这两个队列的大小")。

- `net.core.somaxconn`:为传递给 的 backlog 参数的值提供上限`listen() function`,在用户空间中称为`SOMAXCONN`。如果您更改此值,您还应将应用程序更改为兼容的值。您可以查看[Envoy 的性能调优说明](https://ntk148v.github.io/posts/til/envoy/performance.md "Envoy 的性能调优说明")。
- `net.ipv4.tcp_fin_timeout`:指定在强制关闭套接字之前等待最终 FIN 数据包的秒数。
- `net.ipv4.tcp_available_congestion_control`:显示已注册的可用拥塞控制选项。
- `net.ipv4.tcp_congestion_control`:设置用于新连接的拥塞控制算法。
- `net.ipv4.tcp_max_syn_backlog`:设置尚未收到连接客户端确认的排队连接请求的最大数量;如果超过此数字,内核将开始丢弃请求。
- `net.ipv4.tcp_syncookies`:启用/禁用 syn cookie,用于防止 syn flood 攻击。
- `net.ipv4.tcp_slow_start_after_idle`:启用/禁用 tcp 慢启动。

![](https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Tcp_state_diagram_fixed_new.svg/796px-Tcp_state_diagram_fixed_new.svg.png?20140126065545)
- 您可能想要检查[宽带调整说明](https://ntk148v.github.io/posts/linux-network-performance-ultimate-guide/broadband-tweaks.md "宽带调整说明")。

### 2.11 NUMA

- 这个术语超出了网络性能的范畴。
- 非均匀内存访问 (NUMA) 是一种内存架构,它允许处理器比其他传统技术更快地访问内存内容。换句话说,处理器访问本地内存的速度比访问非本地内存快得多。这是因为在 NUMA 设置中,每个处理器都被分配了一个专门供自己使用的特定本地内存。这消除了非本地内存的共享,减少了当多个请求访问同一内存位置时的延迟(更少的内存锁)-> 提高网络性能(因为 CPU 必须访问环形缓冲区(内存)来处理数据包)
![](15-Linux网络性能终极指南.png)

- NUMA 架构将 CPU、内存和设备的子集拆分为不同的“节点”,实际上创建了具有快速互连和通用操作系统的多台小型计算机。NUMA 系统需要与非 NUMA 系统进行不同的调整。对于 NUMA,其目标是将单个节点中设备的所有中断分组到属于该节点的 CPU 核心上。
- 尽管这看起来好像有助于减少延迟,但众所周知,NUMA 系统与实时应用程序的交互很差,因为它们可能导致意外事件延迟。
- 确定 NUMA 节点:
```shell
ls -ld /sys/devices/system/node/node*

drwxr-xr-x. 3 root root 0 Aug 15 19:44 /sys/devices/system/node/node0
drwxr-xr-x. 3 root root 0 Aug 15 19:44 /sys/devices/system/node/node1

  • 确定 NUMA 位置:
cat /sys/devices/system/node/node0/cpulist

0-5

cat /sys/devices/system/node/node1/cpulist
# empty

  • 调整所有 CPU 的 IRQ 亲和性是有意义的,请确保您使用 sudo systop irqbalance 服务并手动设置 CPU 亲和性:
systemctl stop irqbalance

  • 确定设备位置:
  # cat /sys/class/net/<interface>/device/numa_node
  cat /sys/class/net/eth3/device/numa_node

  1
  # -1 - the hardware platform is not actually NUMA and the kernel is just emulating
  # or 'faking' NUMA, or a device is on a bus which does not have any NUMA locality,
  # such as a PCI package

  • 检查 PCIe 网络接口是否属于特定 NUMA 节点。该命令将显示 NUMA 节点号,设备的中断应定向到 PCIe 设备所属的 NUMA 节点

  • Linux 内核从 2.5 版本开始支持 NUMA - RedHat、基于 Debian 的内核通过两个软件包 numactl 和提供 NUMA 支持以实现进程优化 numad

  systemctl enable numad
  systemctl start numad

  • numadctl:控制进程或共享内存的 NUMA 策略。

  • numad 是一个守护进程,可帮助在具有 NUMA 架构的系统上进行进程和内存管理。Numad 通过监视系统拓扑和资源使用情况来实现此目的,然后尝试定位进程以实现高效的 NUMA 局部性和效率,其中进程哈希值足够大内存大小和 CPU 负载。

2.12 更多内容——数据包处理

本节为高级部分,介绍了一些实现高性能的高级模块/框架。

2.12.1 AF_PACKET v4

资料来源:

  • https://developer.ibm.com/articles/j-zerocopy/[48]

  • https://lwn.net/Articles/737947/d[49]

  • Linux 中的新快速数据包接口:

    • 为了更好地理解问题的解决方案,我们首先需要了解问题本身。

    • 此示例取自IBM 文章[51]。

    • 场景:从文件读取并通过网络将数据传输到另一个程序。

    • 复制操作需要在用户态和内核态进行 4 次上下文切换,数据被复制了 4 次才算操作完成。

    零拷贝通过消除这些冗余的数据拷贝来提高性能。

    • 您会注意到,实际上不需要第 2 和第 3 次复制(应用程序除了缓存数据并将其传输回套接字缓冲区外不执行任何其他操作)-> 数据可以直接从读取缓冲区传输到套接字缓冲区-> 使用方法 transferTo(),假设此方法将数据从文件通道传输到给定的可写字节通道。在内部,它取决于操作系统对零复制的支持(在 Linux、UNIX 中,这是 sis sendfile() 系统调用)。

    • 硬件描述符仅映射到内核

    • AF_PACKET v4

    • 数据路径中没有系统调用

    • 默认复制模式

    • 真正的零拷贝[50]模式 PACKET_ZEROCOPY,DMA 数据包缓冲区映射到用户空间。

    File.read(fileDesc, buf, len);
    Socket.send(socket, buf, len);
    
    
    
    #include <sys/socket.h>
    ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
    
    
    
    public void transferTo(long position, long count, WritableByteChannel target);
    
    
    // Copy data from a disk file to a socket
    transferTo(position, count, writableChannel);
    
    
    

    • 收集操作:在 Linux 内核 2.4 及更高版本中,套接字缓冲区描述符已修改以满足此要求。这种方法不仅减少了多次上下文切换,还消除了需要 CPU 参与的重复数据复制。
    • 不会将任何数据复制到套接字缓冲区中。相反,只有包含数据位置和长度信息的描述符才会附加到套接字缓冲区中。DMA 引擎将数据直接从内核缓冲区传递到协议引擎,从而消除了剩余的最终 CPU 复制。
  • 为了提高 Rx 和 Tx 性能,此实现使用了 PACKET_MMAP

5.12.2 PACKET_MMAP

资料来源:

  • https://docs.kernel.org/networking/packet_mmap.html[52]
  • PACKET_MMAP 是一个用于快速数据包嗅探的 Linux API。
  • 它提供了一个映射环形缓冲区,由用户空间和内核共享,用于发送和接收数据包。这有助于减少系统调用以及用户空间和内核之间所需的复制。

5.12.3 绕过内核:数据平面开发套件(DPDK)

资料来源:

  • https://blog.cloudflare.com/kernel-bypass/[53]

  • https://www.cse.iitb.ac.in/~mythili/os/anno_slides/network_stack_kernel_bypass_slides.pdf[54]

  • https://selectel.ru/blog/en/2016/11/24/introduction-dpdk-architecture-principles/[55]

  • https://www.slideshare.net/garyachy/dpdk-44585840[56]

  • 内核不足:

    • 用户空间数据包处理:

    • 用户空间网络栈

    • 数据平面开发套件(DPDK)

    • etmap

    • mTCP

    • 内核和用户空间之间的上下文切换

    • 内核和用户空间之间的数据包复制

    • 动态分配 sk_buff

    • 每包中断

    • 共享数据结构

    • 要了解该问题,请查看此幻灯片[57]。

    • 内核堆栈中的性能开销:

    • 解决方案:为什么只是绕过 内核?

    • 内核旁路技术有很多种:

    • 但我只谈论 DPDK,因为它最受欢迎。

  • DPDK(数据平面开发套件):

    • 严重依赖硬件。

    • 必须专用一个 CPU 核心并将其分配给运行 PMD。100% CPU。

    • 核心组件:

    • 轮询模式驱动程序:当收到帧时,NIC 不会向 CPU 发出中断,而是 CPU 运行轮询模式驱动程序 (PMD) 来不断轮询 NIC 是否有新数据包。但是,这意味着必须有一个专用的 CPU 核心分配给正在运行的 PMD。但是,这意味着必须有一个专用的 CPU 核心分配给正在运行的 PMD。DPDK 包括用于 1 GbE、10 GbE 和 40GbE 的轮询模式驱动程序 (PMD),以及半虚拟化 virtio 以太网控制器,这些控制器设计为无需异步、基于中断的信号机制即可工作。

    • 数据包转发算法支持:DPDK 包含 Hash( librte_hash)和最长前缀匹配(LPM, librte_lpm)库来支持相应的数据包转发算法。

    • librte_net:IP 协议定义和便利宏的集合。它基于 FreeBSD* IP 堆栈的代码,包含协议编号(用于 IP 标头)、IP 相关宏、IPv4/IPv6 标头结构以及 TCP、UDP 和 SCTP 标头结构。

    • mbuf 库提供了创建和销毁缓冲区的功能,DPDK 应用程序可以使用它来存储消息缓冲区(在启动时创建并存储在内存池中)

    • 提供 API 来分配/释放 mbufs,操作用于承载网络数据包的数据包缓冲区。

    • 池通过名称来标识,并使用环来存储空闲对象。

    • 提供一些可选服务,例如每个核心的对象缓存和对齐助手,以确保对象被填充以在所有 RAM 通道上均匀分布。

    • 环境抽象层 (EAL):提供一个通用接口,向应用程序和库隐藏环境细节。

    • 环管理器( librte_ring):环结构在有限大小的表中提供无锁的多生产者、多消费者 FIFO API。

    • 内存池管理器( librte_mempool):负责在内存中分配对象池。

    • 网络数据包缓冲区管理( librte_mbuf):

    • 计时器管理器( librte_timer):为 DPDK 执行单元提供计时器服务,提供异步执行函数的能力。

    • 内核根本不介入:与网卡的交互是通过特殊的驱动程序和库进行的

    • 需要将网卡上接收传入流量的端口与 Linux(内核驱动程序)解除绑定。这是使用 dpdk_nic_bind(或 dpkg-devbind)命令完成的, ./dpdk_nic_bind.py 在早期版本中为 。

    • 那么 DPDK 如何管理端口呢?Linux 中的每个驱动程序都有绑定和取消绑定文件:

    • 要将设备与驱动程序解除绑定,需要将设备的总线号写入解除绑定文件。同样,要将设备绑定到另一个驱动程序,需要将总线号写入其绑定文件。有关此内容的更多详细信息,请参见此处[58]。

    • DPDK 安装说明告诉我们端口[59]需要由 vfio_pci、igb_uio 或 uio_pci_generic 驱动程序管理。

    • 这些驱动程序使得与用户空间中的设备交互成为可能。当然它们包含一个内核模块,但那只是初始化设备和分配 PCI 接口。

    • 应用程序和网卡之间的所有进一步通信均由 DPDK PMD 组织。

    • DPDK 还需要配置 大页面。这是分配大块内存并向其中写入数据所必需的(与 DPDK 在传统数据包处理中所做的工作相同)

    • 主阶段:

    • 传入的数据包进入环形缓冲区。应用程序定期检查此缓冲区是否有新数据包

    • 如果缓冲区包含新的数据包描述符,应用程序将使用数据包描述符中的指针引用专门分配的内存池中的 DPDK 数据包缓冲区。

    • 如果环形缓冲区不包含任何数据包,应用程序将把网络设备排队到 DPDK 下,然后再次引用环。

    • 当 NIC 由 DPDK 驱动程序控制时,它对内核是不可见的

    • 10 或 40Gb NIC

    • 速度是最重要的标准

    • 仅转发数据包——而不是网络堆栈

    • 由各种用户空间库和驱动程序组成的框架快速数据包处理。

    • 目标:以本机速度(快速数据包处理)将网络数据包转发到/从网络接口卡(NIC)到用户应用程序。

    • 所有流量都会绕过内核:

    • 开源(大部分为 BSD-3,Linux 内核相关部分为 GPL)

    • 工作原理:

    ls /sys/bus/pci/drivers/ixgbe
    bind  module  new_id  remove_id  uevent  unbind
    
    
    
    • 组件:
    • 限制:

2.12.4 PF_RING

资料来源:

  • PF_RING 具有模块化架构,可以使用除标准 PF_RING 模块之外的附加组件。 - ZC 模块(零拷贝): - 基于 FPGA 的卡模块:增加对多家供应商的支持 - Stack 模块:可用于向 Linux 网络堆栈注入数据包 - 时间线模块:可用于无缝提取 n2disk 转储集中的流量 PF_RING API - Sysdig 模块:使用 sysdig 内核模块捕获系统事件

  • 好处:

    • 它为传入的数据包创建一条直线路径,以使它们成为一等公民
    • 无需使用自定义网卡:任何卡都受支持
    • 对应用程序透明:旧版应用程序需要重新编译才能使用
    • 无需内核或低级编程
    • 熟悉网络应用程序的开发人员可以立即利用它,而无需学习新的 API
  • PF_RING 降低了数据包捕获和转发到用户空间的成本。但是,它有一些设计限制,因为它需要两个参与者来捕获数据包,从而导致性能不佳:

    • 内核:将数据包从 NIC 复制到环
    • 用户空间:从环中读取数据包并处理它
  • PF_RING 自 7.5 版本包含对[64] AF_XDP 适配器的支持以来,从源代码编译时默认启用此功能。

2.12.5 可编程数据包处理:eXpress 数据路径 (XDP)

资料来源:

  • https://www.iovisor.org/technology/xdp[65]

  • https://blogs.igalia.com/dpino/2019/01/10/the-express-data-path/[66]

  • https://pantheon.tech/what-is-af_xdp/[67]

  • https://github.com/iovisor/bpf-docs/blob/master/Express_Data_Path.pdf[68]

  • https://github.com/xdp-project/xdp-paper/blob/master/xdp-the-express-data-path.pdf[69]

  • http://vger.kernel.org/lpc_net2018_talks/lpc18_paper_af_xdp_perf-v2.pdf[70]

  • https://arthurchiao.art/blog/firewalling-with-bpf-xdp/[71]

  • https://archive.fosdem.org/2018/schedule/event/xdp/attachments/slides/2220/export/events/attachments/xdp/slides/2220/fosdem18_SdN_NFV_qmonnet_XDPoffload.pdf[72]

  • https://people.netfilter.org/hawk/presentations/KernelRecipes2018/XDP_Kernel_Recipes_2018.pdf[73]

  • https://legacy.netdevconf.info/2.1/session.html?gospodarek[74]

  • XDP(eXpress 数据路径):

    • XDP 专为高性能而设计

    • …和可编程性:无需修改内核即可即时实现新功能

    • XDP 不是内核旁路:

    • XDP 不会取代 TCP/IP 堆栈。

    • XDP 不需要任何专门的硬件,但有一些硬件要求:

    • 它是内核堆栈中的集成快速路径。

    • 如果说传统的内核网络堆栈是一条高速公路,那么内核旁路就是建设高速列车基础设施的提议,而 XDP 就是在高速公路上增加拼车车道的提议——Tom Herbert 和 Alexei Starovoitov。

    • TX/RX 校验和卸载

    • 接收方缩放 (RSS)

    • 传输分段卸载 (TSO)

    • 多队列 NIC

    • 常见协议通用卸载:

    • LRO、aRFS、设备流哈希是“不错的选择”

    • 通过 filter 等 pre-stack 处理来缓解 DOS 攻击

    • 转发和负载平衡

    • 批处理技术,例如通用接收卸载 (GRO)

    • 流量采样、监控

    • ULP 处理

    • 分配 SKB 之前

    • 设备驱动程序内部 RX 函数

    • 直接在 RX 数据包页面上操作

    • eBPF 是内核执行的用户定义、沙盒字节码。更多信息请查看[75]。

    • 从以前的 BPF 版本 (cBPF,由 tcpdump 使用) 演变而来

    • 11 个寄存器(64 位)、512 字节堆栈

    • 对上下文的读写访问(用于网络:数据包)

    • LLVM 后端从 c 编译为 eBPF(或从 Lua、go、P4、Rust 等)

    • 内核验证器确保安全

    • 适用于主要架构的 JIT(即时)编译器

    • 特性:

    • 映射:eBPF 程序之间或与用户空间共享的键值条目(哈希、数组等)

    • 尾调用:从一个程序“长跳转”到另一个程序,上下文被保留

    • 编程助手函数;从 eBPF 程序调用的内核函数白名单:获取当前时间、打印调试信息、查找或更新地图、缩小或增加数据包……

    • 用于早期数据包拦截的 eBPF 实现。它是 Linux 网络数据路径中的可编程、高性能、专用应用程序数据包处理器。

    • 在 SW 网络栈的最低点进行裸数据包处理。

    • 使用案例:

    • 特性:

  • 与 DPDK 比较:

    • 允许选择忙轮询或中断驱动网络

    • 无需分配大页面

    • 无特殊硬件要求

    • 不需要专用 CPU,用户可以选择如何在 CPU 之间构建工作

    • 无需从第三方用户空间应用程序向内核注入数据包

    • 无需为访问网络硬件定义新的安全模型

    • 无需第三方代码/许可。

    • XDP 是一个年轻的项目,但非常有前景。

    • XDP 相对于 DPDK 的优势:

  • XDP 数据包处理器:

    • Forward:

    • Drop:

    • Normal receive:

    • GRO:

    • 可能是数据包修改后

    • TX 队列专属于同一 CPU,因此无需锁定

    • 只需从函数返回错误

    • 驱动程序回收页面

    • 分配 skbuff 并接收到堆栈中

    • 将数据包发送到另一个 CPU 进行处理

    • 允许用户空间使用“原始”接口 AF_PACKET,如 netmap

    • 合并同一连接的数据包

    • 执行大数据包的接收

    • 解析数据包

    • 执行表查找,创建/管理状态过滤器

    • 处理数据包

    • 返回操作:

    • 无锁定 RX 队列

    • CPU 可以专用于忙轮询以使用中断模型

    • 功能接口

    • 没有提前分配 skbuff,没有 SW 队列

    • 在内核中

    • 处理 RX 数据包的组件

    • 直接从驱动程序处理 RX“数据包页面”

    • 为每个 RX 队列分配一个 CPU

    • BPF 程序执行处理

    • 基本动作:

  • AF_XDP

    • 升级版 AF_PACKET:使用 XDP 程序触发选定队列的 Rx 路径

    • XDP 程序可以通过 eBPF 将帧重定向到用户空间中的内存缓冲区 -> 不绕过内核而是创建内核快速路径。

    • DMA 传输使用用户空间内存(零拷贝)

    • 好处:

    • DPDK 应用程序无需更改,内核驱动程序负责处理硬件

    • 为用户提供新的选择

    • 处理数据包的 eBPF 程序可以以非常有效的方式转发给应用程序

    • 用户空间与内核空间之间的零拷贝

    • 相比之下,实现了 3-20 倍的改进 AF_PACKET

    • 性能改进:

    • 将 XDP 直通直接连接到用户空间:

    • 对于DPDK[77]:

    • Linux 4.18[76]中引入了一种新型套接字,它并不完全绕过内核,而是利用其功能并能够创建类似于 DPDK 或的东西 AF_PACKET

  • 限制:

    • 相当年轻的项目
    • 需要新的内核版本(>=5.4)才能完全支持

本文在 AI 的帮助下进行了翻译,如果有些术语没有纠正,欢迎在评论区指正

参考资料[1]

Linux Network Performance Ultimate Guide: https://ntk148v.github.io/posts/linux-network-performance-ultimate-guide/ [2] https://github.com/leandromoreira/linux-network-performance-parameters/: https://github.com/leandromoreira/linux-network-performance-parameters/ [3] https://access.redhat.com/sites/default/files/attachments/20150325_network_performance_tuning.pdf: https://access.redhat.com/sites/default/files/attachments/20150325_network_performance_tuning.pdf [4] https://www.coverfire.com/articles/queueing-in-the-linux-network-stack/: https://www.coverfire.com/articles/queueing-in-the-linux-network-stack/ [5] https://blog.cloudflare.com/how-to-achieve-low-latency/: https://blog.cloudflare.com/how-to-achieve-low-latency/ [6] https://blog.cloudflare.com/how-to-receive-a-million-packets/: https://blog.cloudflare.com/how-to-receive-a-million-packets/ [7] https://beej.us/guide/bgnet/html/: https://beej.us/guide/bgnet/html/ [8] https://blog.csdn.net/armlinuxww/article/details/111930788: https://blog.csdn.net/armlinuxww/article/details/111930788 [9] https://www.ibm.com/docs/en/linux-on-systems?topic=recommendations-network-performance-tuning: https://www.ibm.com/docs/en/linux-on-systems?topic=recommendations-network-performance-tuning [10] https://blog.packagecloud.io/illusterated-guide-monitoring-tuning-linux-networking-stack-receiving-data/: https://blog.packagecloud.io/illustrated-guide-monitoring-tuning-linux-networking-stack-receiving-data/ [11] https://blog.packagecloud.io/monitoring-tuning-linux-networking-stack-receiving-data/: https://blog.packagecloud.io/monitoring-tuning-linux-networking-stack-receiving-data/ [12] https://blog.packagecloud.io/monitoring-tuning-linux-networking-stack-sending-data/: https://blog.packagecloud.io/monitoring-tuning-linux-networking-stack-sending-data/ [13] https://www.sobyte.net/post/2022-10/linux-net-snd-rcv/: https://www.sobyte.net/post/2022-10/linux-net-snd-rcv/ [14] https://juejin.cn/post/7106345054368694280: https://juejin.cn/post/7106345054368694280 [15] https://openwrt.org/docs/guide-developer/networking/praxis: https://openwrt.org/docs/guide-developer/networking/praxis [16] https://blog.51cto.com/u_151691722710604: https://blog.51cto.com/u_151691722710604 [17] https://sn0rt.github.io/media/paper/TCPlinux.pdf: https://sn0rt.github.io/media/paper/TCPlinux.pdf [18] https://medium.com/coccoc-engineering-blog/linux-network-ring-buffers-cea7ead0b8e8: https://medium.com/coccoc-engineering-blog/linux-network-ring-buffers-cea7ead0b8e8 [19] HOWTO#sysctl 部分: https://ntk148v.github.io/posts/linux-network-performance-ultimate-guide/#204-sysctl [20] PackageCloud 的文章: https://blog.packagecloud.io/illustrated-guide-monitoring-tuning-linux-networking-stack-receiving-data [21] 混杂模式: https://unix.stackexchange.com/questions/14056/what-is-kernel-ip-forwarding [22] DMA(直接内存访问): https://en.wikipedia.org/wiki/Direct_memory_access [23] SKB: http://vger.kernel.org/~davem/skb.html [24] 循环缓冲区: https://en.wikipedia.org/wiki/Circular_buffer [25] NAPI: https://en.wikipedia.org/wiki/New_API [26] 优先: https://github.com/torvalds/linux/blob/master/net/ipv4/tcp_output.c#L241 [27] 请点击此处: https://unix.stackexchange.com/questions/419518/how-to-tell-how-much-memory-tcp-buffers-are-actually-using [28] 此处。: https://access.redhat.com/solutions/8694 [29] 这里: https://unix.stackexchange.com/questions/542546/what-is-the-systemd-native-way-to-manage-nic-ring-buffer-sizes-before-bonded-int [30] irqbalancer: https://github.com/Irqbalance/irqbalance [31] 用于在 Intel NIC 上设置 IRQ 亲和性的脚本: https://gist.github.com/xdel/9c50ccedea9e0c9d0000d550b07ee242 [32] 一把双刃剑: https://stackoverflow.com/questions/48659720/is-it-a-good-practice-to-set-interrupt-affinity-and-io-handling-thread-affinity [33] http://balodeamit.blogspot.com/2013/10/receive-side-scaling-and-receive-packet.html: http://balodeamit.blogspot.com/2013/10/receive-side-scaling-and-receive-packet.html [34] https://garycplin.blogspot.com/2017/06/linux-network-scaling-receives-packets.html: https://garycplin.blogspot.com/2017/06/linux-network-scaling-receives-packets.html [35] https://github.com/torvalds/linux/blob/master/Documentation/networking/scaling.rst: https://github.com/torvalds/linux/blob/master/Documentation/networking/scaling.rst [36] NAPI: https://wiki.linuxfoundation.org/networking/napi [37] balodeamit 博客: http://balodeamit.blogspot.com/2013/10/receive-side-scaling-and-receive-packet.html [38] Linux 内核文档: https://github.com/torvalds/linux/blob/v4.11/Documentation/networking/scaling.txt#L80 [39] softnet_data: https://github.com/torvalds/linux/blob/v4.11/include/linux/netdevice.h#L2788 [40] 这个: https://garycplin.blogspot.com/2017/06/linux-network-scaling-receives-packets.html [41] 此项: https://access.redhat.com/discussions/2944681 [42] 此项: https://access.redhat.com/discussions/2944681 [43] 此项: https://access.redhat.com/discussions/2944681 [44] 此项: https://access.redhat.com/discussions/2944681 [45] 流量控制(tc): http://tldp.org/HOWTO/Traffic-Control-HOWTO/intro.html [46] 缓冲区浮动: https://www.bufferbloat.net/projects/codel/wiki/ [47] 本文 - 队列学科部分: https://www.coverfire.com/articles/queueing-in-the-linux-network-stack/ [48] https://developer.ibm.com/articles/j-zerocopy/: https://developer.ibm.com/articles/j-zerocopy/ [49] https://lwn.net/Articles/737947/d: https://lwn.net/Articles/737947/d [50] 零拷贝: https://en.wikipedia.org/wiki/Zero-copy [51] IBM 文章: https://developer.ibm.com/articles/j-zerocopy/ [52] https://docs.kernel.org/networking/packet_mmap.html: https://docs.kernel.org/networking/packet_mmap.html [53] https://blog.cloudflare.com/kernel-bypass/: https://blog.cloudflare.com/kernel-bypass/ [54] https://www.cse.iitb.ac.in/~mythili/os/anno_slides/network_stack_kernel_bypass_slides.pdf: https://www.cse.iitb.ac.in/~mythili/os/anno_slides/network_stack_kernel_bypass_slides.pdf [55] https://selectel.ru/blog/en/2016/11/24/introduction-dpdk-architecture-principles/: https://selectel.ru/blog/en/2016/11/24/introduction-dpdk-architecture-principles/ [56] https://www.slideshare.net/garyachy/dpdk-44585840: https://www.slideshare.net/garyachy/dpdk-44585840 [57] 幻灯片: https://www.cse.iitb.ac.in/~mythili/os/anno_slides/network_stack_kernel_bypass_slides.pdf [58] 此处: https://lwn.net/Articles/143397/ [59] 告诉我们端口: http://dpdk.org/doc/guides-16.04/linux_gsg/build_dpdk.html#loading-modules-to-enable-userspace-io-for-dpdk [60] https://www.ntop.org/products/packet-capture/pf_ring/: https://www.ntop.org/products/packet-capture/pf_ring/ [61] https://repository.ellak.gr/ellak/bitstream/11087/1537/1/5-deri-high-speed-network-analysis.pdf: https://repository.ellak.gr/ellak/bitstream/11087/1537/1/5-deri-high-speed-network-analysis.pdf [62] https://www.synacktiv.com/en/publications/writing-namespace-isolation-with-pf_ring-before-700.html: https://www.synacktiv.com/en/publications/breaking-namespace-isolation-with-pf_ring-before-700.html [63] PF_RING: https://github.com/ntop/PF_RING [64] 自 7.5 版本包含对: https://www.ntop.org/guides/pf_ring/modules/af_xdp.html [65] https://www.iovisor.org/technology/xdp: https://www.iovisor.org/technology/xdp [66] https://blogs.igalia.com/dpino/2019/01/10/the-express-data-path/: https://blogs.igalia.com/dpino/2019/01/10/the-express-data-path/ [67] https://pantheon.tech/what-is-af_xdp/: https://pantheon.tech/what-is-af_xdp/ [68] https://github.com/iovisor/bpf-docs/blob/master/Express_Data_Path.pdf: https://github.com/iovisor/bpf-docs/blob/master/Express_Data_Path.pdf [69] https://github.com/xdp-project/xdp-paper/blob/master/xdp-the-express-data-path.pdf: https://github.com/xdp-project/xdp-paper/blob/master/xdp-the-express-data-path.pdf [70] http://vger.kernel.org/lpc_net2018_talks/lpc18_paper_af_xdp_perf-v2.pdf: http://vger.kernel.org/lpc_net2018_talks/lpc18_paper_af_xdp_perf-v2.pdf [71] https://arthurchiao.art/blog/firewalling-with-bpf-xdp/: https://arthurchiao.art/blog/firewalling-with-bpf-xdp/ [72] https://archive.fosdem.org/2018/schedule/event/xdp/attachments/slides/2220/export/events/attachments/xdp/slides/2220/fosdem18_SdN_NFV_qmonnet_XDPoffload.pdf: https://archive.fosdem.org/2018/schedule/event/xdp/attachments/slides/2220/export/events/attachments/xdp/slides/2220/fosdem18_SdN_NFV_qmonnet_XDPoffload.pdf [73] https://people.netfilter.org/hawk/presentations/KernelRecipes2018/XDP_Kernel_Recipes_2018.pdf: https://people.netfilter.org/hawk/presentations/KernelRecipes2018/XDP_Kernel_Recipes_2018.pdf [74] https://legacy.netdevconf.info/2.1/session.html?gospodarek: https://legacy.netdevconf.info/2.1/session.html?gospodarek [75] 查看: https://ntk148v.github.io/posts/linux-network-performance-ultimate-guide/ebpf/README.md [76] Linux 4.18: https://www.kernel.org/doc/html/v4.18/networking/af_xdp.html [77] DPDK: https://doc.dpdk.org/guides/nics/af_xdp.html

原文地址:https://mp.weixin.qq.com/s/Y4CAZywJbvySrI1ZHx8McQ