译|Monitoring and Tuning the Linux Networking Stack: Sending Data(十一)

简介: 译|Monitoring and Tuning the Linux Networking Stack: Sending Data(十一)

接下来,在上面的循环内部开始另一个循环:

/* clear last DMA location and unmap remaining buffers */
while (tx_desc != eop_desc) {
        tx_buffer++;
        tx_desc++;
        i++;
        if (unlikely(!i)) {
                i -= tx_ring->count;
                tx_buffer = tx_ring->tx_buffer_info;
                tx_desc = IGB_TX_DESC(tx_ring, 0);
        }
        /* unmap any remaining paged data */
        if (dma_unmap_len(tx_buffer, len)) {
                dma_unmap_page(tx_ring->dev,
                               dma_unmap_addr(tx_buffer, dma),
                               dma_unmap_len(tx_buffer, len),
                               DMA_TO_DEVICE);
                dma_unmap_len_set(tx_buffer, len, 0);
        }
}

该内部循环将在每个传输描述符上循环,直到 tx_desc 到达 eop_desc。 这段代码取消映射任何附加描述符引用的数据。

外部循环继续:

/* move us one more past the eop_desc for start of next pkt */
        tx_buffer++;
        tx_desc++;
        i++;
        if (unlikely(!i)) {
                i -= tx_ring->count;
                tx_buffer = tx_ring->tx_buffer_info;
                tx_desc = IGB_TX_DESC(tx_ring, 0);
        }
        /* issue prefetch for next Tx descriptor */
        prefetch(tx_desc);
        /* update budget accounting */
        budget--;
} while (likely(budget));

外部循环增加迭代器并减少 budget 值。 检查循环不变量以确定循环是否应继续。

netdev_tx_completed_queue(txring_txq(tx_ring),
                          total_packets, total_bytes);
i += tx_ring->count;
tx_ring->next_to_clean = i;
u64_stats_update_begin(&tx_ring->tx_syncp);
tx_ring->tx_stats.bytes += total_bytes;
tx_ring->tx_stats.packets += total_packets;
u64_stats_update_end(&tx_ring->tx_syncp);
q_vector->tx.total_bytes += total_bytes;
q_vector->tx.total_packets += total_packets;

此代码:

  1. 调用 netdev_tx_completed_queue,它是上面解释的 DQL API 的一部分。 如果处理了足够的传输完成,这将潜在地重新启用传输队列。
  2. 统计数据被添加到适当位置,以便用户可以访问它们,我们将在后面看到。

代码继续执行,首先检查是否设置了 IGBIGB_RING_FLAG_TX_DETECT_HANG 标志。 看门狗定时器在每次运行定时器回调时设置此标志,以强制执行传输队列的定期检查。 如果该标志现在恰好打开,代码将继续并检查传输队列是否挂起:

if (test_bit(IGB_RING_FLAG_TX_DETECT_HANG, &tx_ring->flags)) {
        struct e1000_hw *hw = &adapter->hw;
        /* Detect a transmit hang in hardware, this serializes the
         * check with the clearing of time_stamp and movement of i
         */
        clear_bit(IGB_RING_FLAG_TX_DETECT_HANG, &tx_ring->flags);
        if (tx_buffer->next_to_watch &&
            time_after(jiffies, tx_buffer->time_stamp +
                       (adapter->tx_timeout_factor * HZ)) &&
            !(rd32(E1000_STATUS) & E1000_STATUS_TXOFF)) {
                /* detected Tx unit hang */
                dev_err(tx_ring->dev,
                        "Detected Tx Unit Hang\n"
                        "  Tx Queue             <%d>\n"
                        "  TDH                  <%x>\n"
                        "  TDT                  <%x>\n"
                        "  next_to_use          <%x>\n"
                        "  next_to_clean        <%x>\n"
                        "buffer_info[next_to_clean]\n"
                        "  time_stamp           <%lx>\n"
                        "  next_to_watch        <%p>\n"
                        "  jiffies              <%lx>\n"
                        "  desc.status          <%x>\n",
                        tx_ring->queue_index,
                        rd32(E1000_TDH(tx_ring->reg_idx)),
                        readl(tx_ring->tail),
                        tx_ring->next_to_use,
                        tx_ring->next_to_clean,
                        tx_buffer->time_stamp,
                        tx_buffer->next_to_watch,
                        jiffies,
                        tx_buffer->next_to_watch->wb.status);
                netif_stop_subqueue(tx_ring->netdev,
                                    tx_ring->queue_index);
                /* we are about to reset, no point in enabling stuff */
                return true;
        }

上面的 if 语句检查:

  • 设置了 tx_buffer->next_to_watch,并且
  • 当前 jiffies 大于在传输路径上记录到 tx_buffertime_stamp,其中添加了超时因子,以及
  • 设备的传输状态寄存器未设置为 E1000_STATUS_TXOFF

如果这三个测试都为真,则打印一个错误,表明检测到挂起。使用 netif_stop_subqueue 关闭队列,并返回 true

让我们继续阅读代码,看看如果没有传输挂起检查,或者如果有,但没有检测到挂起,会发生什么:

#define TX_WAKE_THRESHOLD (DESC_NEEDED * 2)
        if (unlikely(total_packets &&
            netif_carrier_ok(tx_ring->netdev) &&
            igb_desc_unused(tx_ring) >= TX_WAKE_THRESHOLD)) {
                /* Make sure that anybody stopping the queue after this
                 * sees the new next_to_clean.
                 */
                smp_mb();
                if (__netif_subqueue_stopped(tx_ring->netdev,
                                             tx_ring->queue_index) &&
                    !(test_bit(__IGB_DOWN, &adapter->state))) {
                        netif_wake_subqueue(tx_ring->netdev,
                                            tx_ring->queue_index);
                        u64_stats_update_begin(&tx_ring->tx_syncp);
                        tx_ring->tx_stats.restart_queue++;
                        u64_stats_update_end(&tx_ring->tx_syncp);
                }
        }
        return !!budget;

在上面的代码中,驱动程序重新启动传输队列(如果先前已禁用)。它首先检查是否:

  • 某些数据包已经处理完成(total_packets 非零),并且
  • netif_carrier_ok 以确保设备未被关闭,以及
  • 传输队列中未使用的描述符数量大于或等于 TX_WAKE_THRESHOLD。在我的 x86_64 系统上,此阈值似乎为 42

如果所有条件都满足,则使用写屏障(smp_mb)。接下来检查另一组条件:

  • 如果队列已停止,并且
  • 设备未关闭

然后调用 netif_wake_subqueue 唤醒传输队列并向更高层次发出信号,表示它们可以再次排队数据。增加 restart_queue 统计计数器。接下来我们将看到如何读取此值。

最后,返回一个布尔值。如果有任何剩余的未使用预算,则返回 true,否则返回 false。在 igb_poll 中检查此值以确定返回给 net_rx_action 的内容。

igb_poll 返回值

igbigb_poll 函数有以下代码来确定返回给 net_rx_action

if (q_vector->tx.ring)
        clean_complete = igb_clean_tx_irq(q_vector);
if (q_vector->rx.ring)
        clean_complete &= igb_clean_rx_irq(q_vector, budget);
/* If all work not completed, return budget and keep polling */
if (!clean_complete)
        return budget;

换句话说,如果:

  • igb_clean_tx_irq 清除了所有传输完成,而没有耗尽其传输完成预算,以及
  • igb_clean_rx_irq 清除了所有传入数据包,而没有耗尽其数据包处理预算

然后,将返回整个预算数量(对于大多数驱动程序,它被硬编码为 64,包括 igb)。 如果传输或传入处理中的任何一个不能完成(因为还有更多的工作要做),则调用 napi_complete 并返回 0

/* If not enough Rx work done, exit the polling mode */
        napi_complete(napi);
        igb_ring_irq_enable(q_vector);
        return 0;
}

监控网络设备

有几种不同的方法可以监控网络设备,提供不同级别的粒度和复杂性。 让我们从最细粒度开始,然后转到最细粒度。

使用 ethtool -S

你可以运行以下命令在 Ubuntu 系统上安装 ethtoolsudo apt-get install ethtool.

安装后,您可以传递 -S 标志以及需要统计信息的网络设备的名称来访问统计信息。

使用 ethtool -S 监控详细的 NIC 设备统计信息(例如, 传输错误)。

$ sudo ethtool -S eth0
NIC statistics:
     rx_packets: 597028087
     tx_packets: 5924278060
     rx_bytes: 112643393747
     tx_bytes: 990080156714
     rx_broadcast: 96
     tx_broadcast: 116
     rx_multicast: 20294528
     ....

监测这些数据可能很困难。 它很容易获得,但字段值没有标准化。 不同的驱动程序,甚至不同版本的同一 驱动可能会产生具有相同含义的不同字段名称。

你应该在标签中寻找带有“drop”、“buffer”、“miss”、“errors”等的值。接下来,您将不得不阅读驱动程序源代码。您将能够确定哪些值完全在软件中计算(例如,在没有内存时增加)以及哪些值直接通过寄存器从硬件读取获得。对于寄存器值,您应该查阅硬件的数据表以确定计数器的真实含义; ethtool 给出的许多标签可能会产生误导。

使用 sysfs

sysfs 也提供了许多统计值,但它们比直接提供的 NIC 级别统计值略高一些。

您可以使用 cat 在文件上查找丢弃的传入网络数据帧的数量,例如 eth0。

使用 sysfs 监控更高级别的 NIC 统计信息。

$ cat /sys/class/net/eth0/statistics/tx_aborted_errors
2

计数器值将被拆分为 tx_aborted_errorstx_carrier_errorstx_compressedtx_dropped 等文件。

不幸的是,由驱动程序来决定每个字段的含义,以及何时增加它们以及值来自何处。 您可能会注意到,一些驱动程序将某种类型的错误情况视为丢弃,但其他驱动程序可能会将其视为未命中。

如果这些值对您很重要,您需要阅读驱动程序源代码和设备数据表,以准确了解驱动程序认为的每个值的含义。

使用 /proc/net/dev

更高级的文件是 /proc/net/dev,它为系统上的每个网络适配器提供高级摘要式信息。

读取 /proc/net/dev 来监视高级 NIC 统计信息。

$ cat /proc/net/dev
Inter-|   Receive                                                |  Transmit
 face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed
  eth0: 110346752214 597737500    0    2    0     0          0  20963860 990024805984 6066582604    0    0    0     0       0          0
    lo: 428349463836 1579868535    0    0    0     0          0         0 428349463836 1579868535    0    0    0     0       0          0

这个文件显示了您在上面提到的 sysfs 文件中找到的值的子集,但它可能作为一个有用的一般参考。

上面提到的警告也适用于这里:如果这些值对您很重要,您仍然需要阅读驱动程序源代码,以准确了解何时、何地以及为什么它们会增加,以确保您对 error、drop 或 fifo 的理解与驱动程序相同。

监控动态队列限制

您可以读取位于以下位置的文件来监控网络设备的动态队列限制:

/sys/class/net/NIC/queues/tx-QUEUE_NUMBER/byte_queue_limits/

替换 NIC 为您的设备名称(eth0eth1 等),替换 tx-QUEUE_NUMBER 为传输队列号(tx-0tx-1tx-2 等)。

其中一些文件是:

  • hold_time:初始化为 HZ(单个赫兹)。 如果队列在 hold_time 内已满,则减小最大大小。
  • inflight:它是尚未处理完成的正在传输的数据包的当前数量。该值等于(排队的数据包数量-完成的数据包数量)。
  • limit_max:硬编码值,设置为 DQL_MAX_LIMIT(在我的 x86_64 系统上为 1879048192)。
  • limit_min:硬编码值,设置为 0
  • limit:一个介于 limit_minlimit_max 之间的值,表示当前可以排队的对象的最大数量。

在修改任何这些值之前,强烈建议阅读这些演示幻灯片,以深入了解算法。

读取 /sys/class/net/eth0/queues/tx-0/byte_queue_limits/inflight 监控在传输过程中的数据包情况。

$ cat /sys/class/net/eth0/queues/tx-0/byte_queue_limits/inflight
350

调优网络设备

检查正在使用的传输队列数

如果您的 NIC 和系统上加载的设备驱动程序支持多个传输队列,则通常可以使用 ethtool 调整 TX 队列(也称为 TX 通道)的数量 ethtool

使用 ethtool 检查 NIC 传输队列的数量 ethtool

$ sudo ethtool -l eth0
Channel parameters for eth0:
Pre-set maximums:
RX:   0
TX:   0
Other:    0
Combined: 8
Current hardware settings:
RX:   0
TX:   0
Other:    0
Combined: 4

此输出显示预设的最大值(由驱动程序和硬件强制执行)和当前设置。

注意: 并非所有设备驱动程序都支持此操作。

如果您的 NIC 不支持此操作,则会出现错误。

$ sudo ethtool -l eth0
Channel parameters for eth0:
Cannot get device channel parameters
: Operation not supported

这意味着您的驱动程序尚未实现 ethtool get_channels 操作。 这可能是因为 NIC 不支持调整队列数量,不支持多个传输队列,或者您的驱动程序尚未更新以处理此功能。

调整使用的传输队列数

找到当前和最大队列计数后,可以使用 sudo ethtool -L 调整这些值。

注意: 某些设备及其驱动程序仅支持为发送和接收配对的组合队列,如上一节中的示例所示。

使用 ethtool -L 设置组合 NIC 传输和接收队列为 8

$ sudo ethtool -L eth0 combined 8

如果您的设备和驱动程序支持 RX 和 TX 的单独设置,并且您只想更改 TX 队列计数为 8,则可以运行:

使用 ethtool -L 设置 NIC 传输队列的数量为 8。

$ sudo ethtool -L eth0 tx 8

注意: 对于大多数驱动程序来说,进行这些更改将关闭接口,然后再重新打开; 与该接口的连接将被中断。 不过,这对于一次性的改变来说可能并不重要。

调整传输队列的大小

某些 NIC 及其驱动程序还支持调整 TX 队列的大小。 具体的工作原理是硬件相关的,但幸运的是,ethtool 为用户提供了一种通用的方法来调整大小。 由于使用了 DQL 来防止更高层次的网络代码在某些时候排队更多数据,因此增加发送队列的大小可能不会产生巨大的差异。尽管如此,您可能仍然希望增加发送队列到最大大小,并让 DQL 为您处理其他所有事情:

使用 ethtool -g 检查当前网卡队列大小。

$ sudo ethtool -g eth0
Ring parameters for eth0:
Pre-set maximums:
RX:   4096
RX Mini:  0
RX Jumbo: 0
TX:   4096
Current hardware settings:
RX:   512
RX Mini:  0
RX Jumbo: 0
TX:   512

上面的输出指示硬件支持多达 4096 个接收和发送描述符,但是它当前仅使用 512 个。

使用 ethtool -G 增加每个 TX 队列的大小到 4096

$ sudo ethtool -G eth0 tx 4096

注意: 对于大多数驱动程序来说,进行这些更改将关闭接口,然后再重新打开;与该接口的连接将被中断。 不过,这对于一次性的改变来说可能并不重要。

结束

结束了! 现在你已经知道了 Linux 上数据包传输的工作原理:从用户程序到设备驱动程序再返回。

其他

有一些额外的事情值得一提,值得一提的是,似乎不太正确的其他任何地方。

减少 ARP 流量(MSG_CONFIRM

sendsendtosendmsg 系统调用都采用 flags 参数。 如果您传递 MSG_CONFIRM 标志给应用程序中的这些系统调用,它将导致内核中发送路径上的 dst_neigh_output 函数更新邻居结构的时间戳。 这样做的结果是相邻结构将不会被垃圾收集。 这可以防止产生额外的 ARP 流量,因为邻居缓存条目将保持更热、更长时间。

UDP Corking

我们在整个 UDP 协议栈中广泛地研究了 UDP corking。 如果要在应用程序中使用它,可以调用 setsockopt 启用 UDP corking,设置 level 为 IPPROTO_UDP,optname 设置为 UDP_CORKoptval 设置为 1

时间戳

正如上面的博客文章中提到的,网络栈可以收集传出数据的时间戳。 请参阅上面的网络栈演练,了解软件中的传输时间戳发生的位置。 一些 NIC 甚至还支持硬件中的时间戳。

如果您想尝试确定内核网络栈在发送数据包时增加了多少延迟,这是一个有用的特性。

关于时间戳的内核文档非常好,甚至还有一个包含的示例程序和 Makefile,你可以查看

使用 ethtool -T 确定您的驱动程序和设备支持的时间戳模式。

$ sudo ethtool -T eth0
Time stamping parameters for eth0:
Capabilities:
  software-transmit     (SOF_TIMESTAMPING_TX_SOFTWARE)
  software-receive      (SOF_TIMESTAMPING_RX_SOFTWARE)
  software-system-clock (SOF_TIMESTAMPING_SOFTWARE)
PTP Hardware Clock: none
Hardware Transmit Timestamp Modes: none
Hardware Receive Filter Modes: none

不幸的是,这个网卡不支持硬件传输时间戳,但是软件时间戳仍然可以在这个系统上使用,以帮助我确定内核给我的数据包传输路径增加了多少延迟。

结论

Linux 网络栈很复杂。

正如我们上面看到的,即使像 NET_RX 这样简单的东西也不能保证像我们期望的那样工作。 即使RX 在名称中,传输完成仍在此 softIRQ 中处理。

这突出了我认为是问题的核心:除非您仔细阅读并理解网络栈的工作原理,否则无法优化和监控网络栈。您无法监控您不深入了解的代码。

原文:Monitoring and Tuning the Linux Networking Stack: Sending Data

本文作者 : cyningsun

本文地址https://www.cyningsun.com/04-25-2023/monitoring-and-tuning-the-linux-networking-stack-sent-cn.html

版权声明 :本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!

# Network

  1. 译|A scalable, commodity data center network architecture
  2. 译|llustrated Guide to Monitoring and Tuning the Linux Networking Stack: Receiving Data
  3. 译|Monitoring and Tuning the Linux Networking Stack: Receiving Data
  4. TCP/IP 网络传输
  5. TCP/IP 网络设备与基础概念
目录
相关文章
|
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
76 0
|
6月前
|
Linux
linux下的内存查看(virt,res,shr,data的意义)
linux下的内存查看(virt,res,shr,data的意义)
111 0
|
10月前
|
SQL 存储 缓存
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(十)
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(十)
211 1
|
10月前
|
缓存 监控 Linux
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(九)
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(九)
141 0
|
10月前
|
监控 Linux 调度
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(八)
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(八)
70 0
|
17小时前
|
安全 Linux 测试技术
|
1天前
|
缓存 关系型数据库 Linux
Linux目录结构:深入理解与命令创建指南
Linux目录结构:深入理解与命令创建指南
|
1天前
|
数据挖掘 Linux vr&ar
Linux命令实战:解决日常问题的利器
Linux命令实战:解决日常问题的利器
|
1天前
|
NoSQL Linux Redis
Redis的介绍,以及Redis的安装(本机windows版,虚拟机Linux版)和Redis常用命令的介绍
Redis的介绍,以及Redis的安装(本机windows版,虚拟机Linux版)和Redis常用命令的介绍
12 0
|
1天前
|
安全 Linux Shell
linux基础命令详解
linux基础命令详解
8 0