译|Monitoring and Tuning the Linux Networking Stack: Receiving Data(七)

简介: 译|Monitoring and Tuning the Linux Networking Stack: Receiving Data(七)

Receive Packet Steering (RPS)

回想一下我们之前讨论的网络设备驱动程序注册 NAPI poll 函数的过程。 每个 NAPI 轮询器实例在软中断的上下文中执行,每个 CPU 有一个软中断。 进一步回想一下,驱动程序的 IRQ 处理程序运行的 CPU 将唤醒其 softirq 处理循环来处理数据包。

换句话说:单个 CPU 处理硬件中断并轮询数据包以处理输入数据。

某些 NIC(如Intel I350)在硬件级别支持多个队列。 这意味着传入的数据包可以被 DMA 到每个队列的单独的内存区域,并且还具有单独的 NAPI 结构来管理轮询该区域。 因此,多个 CPU 将处理来自设备的中断,并且还处理数据包。

该特征通常被称为 Receive Side Scaling (RSS)。

Receive Packet Steering (RPS) 是 RSS 的软件实现。 由于它是在软件中实现的,这意味着它可以为任何 NIC 启用,即使是只有单个接收队列的 NIC。 然而,由于它是在软件中,这意味着 RPS 只能在已经从 DMA 存储器区域收取数据包之后进入流。

这意味着您不会注意到处理 IRQ 或 NAPI poll 循环所花费的 CPU 时间减少,但您可以在收集数据包后分布处理数据包的负载,并减少网络栈上的 CPU 时间。

RPS 的工作原理是为传入数据生成一个散列,以确定哪个 CPU 应该处理数据。 然后排队数据到每 CPU 接收网络积压中以进行处理。 处理器间中断(IPI)被传送到拥有积压的 CPU。 如果当前没有处理积压工作中的数据,这有助于启动积压工作处理。 /proc/net/softnet_stat 包含每个 softnet_data 结构体接收 IPI(received_rps字段)的次数计数。

因此,netif_receive_skb 将继续向网络栈发送网络数据,或者将其移交给 RPS 以在不同的 CPU 上进行处理。

调优:启用 RPS

要使 RPS 工作,必须在内核配置中启用它(Ubuntu 内核 3.13.0 是启用的),并使用位掩码描述哪些 CPU 应该处理给定接口和接收队列的数据包。

您可以在内核文档中找到有关这些位掩码的一些文档。

简而言之,要修改的位掩码位于:

/sys/class/net/DEVICE_NAME/queues/QUEUE/rps_cpus

因此,对于 eth0 和 接收队列 0,你将修改 /sys/class/net/eth0/queues/rx-0/rps_cpus 文件,其中十六进制数指示哪些 CPU 应处理来自 eth0 的接收队列 0 的数据包。 正如文档指出的,在某些配置中可能不需要 RPS。

注: 启用 RPS 将数据包处理分配给以前未处理数据包的 CPU,将导致该 CPU 的 NET_RX 软中断数增加,以及 CPU 使用率图中的 sisitime 增加。 您可以比较软中断和 CPU 使用率图表的前后对比,以确认 RPS 配置是否符合您的喜好。

Receive Flow Steering (RFS)

Receive flow steering (RFS) 与 RPS 配合使用。 RPS 尝试在多个 CPU 之间分配传入数据包负载,但不考虑任何数据局部性问题以最大化 CPU 缓存命中率。 您可以使用 RFS 定向同一个流的数据包到同一个 CPU 进行处理,从而帮助提高缓存命中率。

调优:启用 RFS

要使 RFS 工作,您必须启用并配置 RPS。

RFS 跟踪所有流的全局哈希表,并且可以设置 net.core.rps_sock_flow_entries sysctl 来调整该哈希表的大小。

设置 sysctl 增加 RFS 套接字流哈希的大小。

$ sudo sysctl -w net.core.rps_sock_flow_entries=32768

接下来,您还可以设置每个接收队列的流数,方法是写入此值每个接收队列的名为rps_flow_cnt 的 sysfs 文件。

示例:增加 eth0 上接收队列 0 的流数到 2048。

$ sudo bash -c 'echo 2048 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt'

硬件加速 Receive Flow Steering (aRFS)

RFS 可以使用硬件加速来加速;NIC 和内核可以一起工作以确定哪些流应该在哪些 CPU 上被处理。 要使用此功能,NIC 和驱动程序必须支持此功能。

请参阅您的网卡数据手册以确定是否支持此功能。 如果您的 NIC 驱动程序公开了一个名为 ndo_rx_flow_steer 的函数,则该驱动程序支持加速 RFS。

调优:启用加速 RFS(aRFS)

假设您的 NIC 和驱动程序支持它,您可以启用和配置一组内容来启用加速 RFS:

  1. 启用并配置 RPS。
  2. 启用并配置 RFS。
  3. x在编译内核时启用 CONFIG_RFS_ACCEL。 Ubuntu kernel 3.13.0 启用
  4. 如前所述,为设备启用 ntuple 支持。 您可以使用 ethtool 来验证是否为设备启用了ntuple 支持。
  5. 配置 IRQ 设置以确保每个接收队列由所需的网络处理 CPU 之一处理。

一旦配置了上述内容,加速 RFS 自动移动数据到与处理该流数据的CPU核心绑定的接收队列,并且您不需要为每个流手动指定 ntuple 过滤规则。

使用 netif_receive_skb 向上移动网络栈。

接着我们上次讲到的 netif_receive_skb,它从几个地方调用。最常见的两个(也是我们已经看过的两个):

  • 如果数据包不会合并到现有的 GRO 流中,则为 napi_skb_finish,或者
  • 如果协议层指示现在是刷新流的时候,则为 napi_gro_complete,或者

提醒:netif_receive_skb 及其后代在 softirq 处理循环的上下文中运行,使用像 top 这样的工具,您将看到这里花费的时间计入 sitimesi

netif_receive_skb 首先检查一个 sysctl 值,以确定用户是否在数据包进入积压队列之前或之后请求接收时间戳。如果启用了此设置,则在数据进入 RPS(和 CPU 的关联积压队列)之前对数据进行时间戳。如果禁用了此设置,则在进入队列后进行时间戳。如果启用了 RPS,则可以使用此功能在多个 CPU 之间分配时间戳的负载,但会因此引入一些延迟。

调优:接收数据包时间戳

您可以调整一个名为 net.core.netdev_tstamp_prequeue 的 sysctl 来调优接收到数据包后的时间戳:

调整 sysctl 禁用接收数据包的时间戳

$ sudo sysctl -w net.core.netdev_tstamp_prequeue=0

默认值为 1。 请参阅上一节的解释,以了解此设置的确切含义。

netif_receive_skb

处理完时间戳后,netif_receive_skb 的操作方式会因 RPS 是否启用而不同。 让我们先从更简单的路径开始:RPS 已禁用。

RPS 禁用(默认设置)

如果未启用 RPS,则调用 __netif_receive_skb,它执行一些簿记工作,然后调用 __netif_receive_skb_core 移动数据到协议栈附近。

我们将看到 __netif_receive_skb_core 的工作原理,但首先让我们看看启用 RPS 的代码路径如何工作,因为该代码也将调用 __netif_receive_skb_core

RPS 启用

如果启用了 RPS,在处理上述提到的时间戳选项之后,netif_receive_skb 将执行一些计算,以确定应使用哪个 CPU 的积压队列。这是使用 get_rps_cpu 函数完成的。来自 net/core/dev.c

cpu = get_rps_cpu(skb->dev, skb, &rflow);
if (cpu >= 0) {
  ret = enqueue_to_backlog(skb, cpu, &rflow->last_qtail);
  rcu_read_unlock();
  return ret;
}

get_rps_cpu 将考虑上述 RFS 和 aRFS 设置,以确保调用 enqueue_to_backlog 排队数据到所需的 CPU 的 backlog。

enqueue_to_backlog

此函数首先获取指向远程 CPU 的 softnet_data 结构指针,该结构包含指向input_pkt_queue 的指针。 接下来,检查远程 CPU 的 input_pkt_queue。 来自 net/core/dev.c

qlen = skb_queue_len(&sd->input_pkt_queue);
if (qlen <= netdev_max_backlog && !skb_flow_limit(skb, qlen)) {

首先比较 input_pkt_queue 的长度与 netdev_max_backlog 。如果队列长度大于此值,则丢弃数据。同样,检查流量限制,如果超过了流量限制,则丢弃数据。在这两种情况下,都会增加 softnet_data 结构的丢弃计数。请注意,这是数据将要排队到的 CPU 的 softnet_data 结构。阅读上面关于 /proc/net/softnet_stat 的部分,基于监控的目的了解如何获取丢弃计数。

enqueue_to_backlog 在很少地方调用。它用于已启用 RPS 的数据包处理,也从 netif_rx 调用。大多数驱动程序都 不应 使用 netif_rx,而应使用 netif_receive_skb。如果您没有使用 RPS 并且您的驱动程序没有使用 netif_rx,则增加积压不会对您的系统产生任何明显影响,因为它不会被使用。

注意:您需要检查正在使用的驱动程序。如果它调用了 netif_receive_skb 并且您 没有 使用 RPS,则增加 netdev_max_backlog 将不会产生任何性能改进,因为没有任何数据会进入 input_pkt_queue

假设 input_pkt_queue 足够小且未达到流量限制(接下来会详细介绍),则可以排队数据。这里的逻辑有点奇怪,但可以总结为:

  • 如果队列为空:检查远程 CPU 上是否已启动 NAPI。如果没有,则检查是否已排队发送 IPI。如果没有,则排队一个并调用 ____napi_schedule 启动 NAPI 处理循环。继续排队数据。
  • 如果队列不为空,或者前面描述的操作已完成,则将数据入队。

这段代码使用了 goto,所以要仔细阅读。 来自 net/core/dev.c

if (skb_queue_len(&sd->input_pkt_queue)) {
enqueue:
         __skb_queue_tail(&sd->input_pkt_queue, skb);
         input_queue_tail_incr_save(sd, qtail);
         rps_unlock(sd);
         local_irq_restore(flags);
         return NET_RX_SUCCESS;
 }
 /* Schedule NAPI for backlog device
  * We can use non atomic operation since we own the queue lock
  */
 if (!__test_and_set_bit(NAPI_STATE_SCHED, &sd->backlog.state)) {
         if (!rps_ipi_queued(sd))
                 ____napi_schedule(sd, &sd->backlog);
 }
 goto enqueue;
流量限制

RPS 可以在多个 CPU 之间分配数据包处理负载,但是单个大流量可能会垄断 CPU 处理时间并使较小的流量饥饿。流量限制是一种功能,限制每个流量排队到积压的数据包数量为一定数量。这有助于确保即使大流量推送数据包,也能处理较小的流量。

上面来自 net/core/dev.c 的 if 语句调用 skb_flow_limit 检查流量限制:

if (qlen <= netdev_max_backlog && !skb_flow_limit(skb, qlen)) {

这段代码检查队列中是否还有空间,以及是否尚未达到流量限制。 默认情况下,禁用流量限制。 要启用流量限制,必须指定位图(类似于 RPS 的位图)。

监控:监控 input_pkt_queue 已满或流量限制导致的丢弃

请参阅上面有关监视/proc/net/softnet_statdropped字段是一个计数器,每次数据被丢弃而不是排队到CPU的 input_pkt_queue 时,它都会递增。

调优
调优:调优 netdev_max_backlog 防止丢弃

在调整此调优值之前,请参阅上一节中的注释。

如果使用 RPS 或驱动程序调用 netif_rx,则可以增加 netdev_max_backlog 来帮助防止 enqueue_to_backlog 的丢弃。

示例:使用 sysctl 增加 backlog 到 3000。

$ sudo sysctl -w net.core.netdev_max_backlog=3000

默认值为 1000。

调优:调优 backlog 的 NAPI poll 权重

您可以设置 net.core.dev_weight sysctl 来调整积压的 NAPI 轮询器的权重。调整此值可以确定积压 poll 循环可以消耗的总预算的多少(请参阅上面调整 net.core.netdev_budget 的部分):

示例:使用 sysctl 增加 NAPI poll 积压处理循环。

$ sudo sysctl -w net.core.dev_weight=600

默认值为 64。

记住,backlog 处理运行在 softirq 上下文,类似于设备驱动程序注册的 poll 函数,并且将受到总 budget 和时间的限制,如前几节所述。

调优:启用流量限制并调优流量限制哈希表大小

使用 sysctl 设置流量限制表的大小。

$ sudo sysctl -w net.core.flow_limit_table_len=8192

默认值为 4096。

此更改仅影响新分配的流哈希表。 因此,如果您想增加表的大小,应该在启用流量限制之前进行。

要启用 流量限制,您应该在 /proc/sys/net/core/flow_limit_cpu_bitmap 中指定一个位掩码,该位掩码类似于 RPS 位掩码,指示哪些 CPU 启用了流量限制。

backlog 队列 NAPI 轮询器

每个 CPU 的 backlog 队列插入 NAPI 的方式与设备驱动程序相同。提供了一个 poll 函数,从 softirq 上下文处理数据包。就像设备驱动程序一样,还提供了一个 weight

这个 NAPI 结构在初始化网络系统时提供。来自 net/core/dev.c 中的 net_dev_init

sd->backlog.poll = process_backlog;
sd->backlog.weight = weight_p;
sd->backlog.gro_list = NULL;
sd->backlog.gro_count = 0;

backlog NAPI 结构与设备驱动程序 NAPI 结构的不同之处在于 weight 参数是可调整的,其中驱动程序编码其 NAPI 权重硬为 64。 我们将在下面的调优部分看到如何使用 sysctl 调整权重。

process_backlog

process_backlog 函数是一个循环,直到其权重(如前一节所述)被消耗完或 backlog 中没有更多数据为止。

backlog 队列中的每个数据都从 backlog 队列中移除,并传递给 __netif_receive_skb。一旦数据进入 __netif_receive_skb,代码路径与上面解释的 RPS 禁用情况相同。也就是说,在调用 __netif_receive_skb_core 传递网络数据到协议层之前,__netif_receive_skb 会进行一些簿记工作。

process_backlog 遵循与设备驱动程序相同的 NAPI 契约,即:如果不使用总权重,则禁用 NAPI。通过上面描述的 enqueue_to_backlog 中对 ____napi_schedule 的调用,轮询器重新启动。

该函数返回完成的工作量,net_rx_action(上面描述)将从预算中扣减该工作量(使用上面描述的 net.core.netdev_budget 进行调整)。

目录
相关文章
|
10月前
|
运维 监控 网络协议
译|llustrated Guide to Monitoring and Tuning the Linux Networking Stack: Receiving Data
译|llustrated Guide to Monitoring and Tuning the Linux Networking Stack: Receiving Data
73 0
|
6月前
|
Linux
linux下的内存查看(virt,res,shr,data的意义)
linux下的内存查看(virt,res,shr,data的意义)
109 0
|
10月前
|
SQL 缓存 监控
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(十一)
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(十一)
84 0
|
10月前
|
SQL 存储 缓存
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(十)
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(十)
207 1
|
10月前
|
缓存 监控 Linux
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(九)
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(九)
138 0
|
10月前
|
监控 Linux 调度
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(八)
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(八)
70 0
|
10月前
|
存储 Ubuntu Linux
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(七)
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(七)
85 0
|
2天前
|
运维 网络协议 Linux
【专栏】运维工程师工作时最常用的 20 个 Linux 命令有哪些?建议收藏
【4月更文挑战第28天】本文介绍了运维工程师常用的20个Linux命令,包括`ls`、`cd`、`pwd`、`mkdir`、`rm`、`cp`、`mv`、`cat`、`more`、`less`、`head`、`tail`、`grep`、`find`、`chmod`、`chown`、`chgrp`、`ps`、`top`和`ifconfig`,帮助提升工作效率。此外,还提到了其他常用的命令如`df`、`free`、`tar`、`ssh`、`scp`、`ping`、`netstat`、`iptables`、`systemctl`、`hostname`等,建议运维人员掌握以应对各种运维场景。
|
16小时前
|
存储 Linux Shell
linux课程第二课------命令的简单的介绍2
linux课程第二课------命令的简单的介绍2
|
17小时前
|
安全 Linux C语言
linux课程第一课------命令的简单的介绍
linux课程第一课------命令的简单的介绍