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

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

最后,我们来看看我们的朋友 dev_hard_start_xmit

因此,我们已经遍历了整个网络栈,直到 dev_hard_start_xmit。 也许你是经 sendmsg 系统调用直接到达这里的,或者你是经 qdisc 上处理网络数据的 softirq 线程到达这里的。dev_hard_start_xmit 将向下调用设备驱动程序来实际执行传输操作。

dev_hard_start_xmit函数处理两种主要情况:

  • 准备发送的网络数据,或
  • 具有需要处理的分段卸载的网络数据。

我们将看到这两种情况是如何处理的,从准备发送的网络数据开始。 让我们一起来看看(如下所示:./net/code/dev.c

int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev,
                        struct netdev_queue *txq)
{
        const struct net_device_ops *ops = dev->netdev_ops;
        int rc = NETDEV_TX_OK;
        unsigned int skb_len;
        if (likely(!skb->next)) {
                netdev_features_t features;
                /*
                 * If device doesn't need skb->dst, release it right now while
                 * its hot in this cpu cache
                 */
                if (dev->priv_flags & IFF_XMIT_DST_RELEASE)
                        skb_dst_drop(skb);
                features = netif_skb_features(skb);

这段代码首先 ops 获取设备驱动程序暴露的操作的引用。当需要驱动程序执行一些工作来传输数据时,将使用它。 代码检查 skb->next 以确保此数据不是数据链的一部分,该数据链已分段准备就绪,并继续执行两件事:

  1. 首先,它检查设备是否设置了 IFF_XMIT_DST_RELEASE 标志。 此内核中的任何“真正的”以太网设备都不使用此标志。 但是,它被环回设备和其他一些软件设备使用。 如果启用此标志,则可以减少目标缓存条目的引用计数,因为驱动程序不需要它。
  2. 接下来,netif_skb_features 从设备获取特性标志,并根据数据的目的协议(dev->protocol)对它们进行一些修改。 例如,如果协议是设备可以校验和的协议,则标记 skb 为这样的协议。 VLAN 标记(如果已设置)也会导致其他功能标志翻转。

接下来,将检查 VLAN 标记,如果设备无法卸载 VLAN 标记,则将在软件中__vlan_put_tag 来执行此操作:

if (vlan_tx_tag_present(skb) &&
    !vlan_hw_offload_capable(features, skb->vlan_proto)) {
        skb = __vlan_put_tag(skb, skb->vlan_proto,
                             vlan_tx_tag_get(skb));
        if (unlikely(!skb))
                goto out;
        skb->vlan_tci = 0;
}

接下来,将检查数据是否是封装卸载请求,例如,可能是 GRE。 在这种情况下,更新功能标志,以包括可用的任何特定于设备的硬件封装功能:

/* If encapsulation offload request, verify we are testing
 * hardware encapsulation features instead of standard
 * features for the netdev
 */
if (skb->encapsulation)
        features &= dev->hw_enc_features;

接下来,netif_needs_gso 来确定 skb 本身是否需要分段。 如果 skb 需要分段,但设备不支持,则 netif_needs_gso 将返回 true 指示分段应在软件中进行。 在本例中,调用dev_gso_segment 来执行分段,代码将跳转到 gso 来传输数据包。 稍后我们将看到 GSO 路径。

if (netif_needs_gso(skb, features)) {
        if (unlikely(dev_gso_segment(skb, features)))
                goto out_kfree_skb;
        if (skb->next)
                goto gso;
}

如果数据不需要分割,则处理一些其他情况。 第一:数据是否需要线性化? 也就是说,如果数据分布在多个缓冲区中,设备是否可以支持发送网络数据,或者是否需要首先组合所有数据到单个线性缓冲区中? 绝大多数网卡不需要在传输之前对数据进行线性化,因此在几乎所有情况下,这将被计算为 false 并跳过。

else {
              if (skb_needs_linearize(skb, features) &&
                  __skb_linearize(skb))
                      goto out_kfree_skb;

接下来提供了一个有用的注释,解释了下一个分支。 检查数据包以确定它是否仍需要校验和。 如果设备不支持校验和,则在软件中生成校验和:

/* If packet is not checksummed and device does not
         * support checksumming for this protocol, complete
         * checksumming here.
         */
        if (skb->ip_summed == CHECKSUM_PARTIAL) {
                if (skb->encapsulation)
                        skb_set_inner_transport_header(skb,
                                skb_checksum_start_offset(skb));
                else
                        skb_set_transport_header(skb,
                                skb_checksum_start_offset(skb));
                if (!(features & NETIF_F_ALL_CSUM) &&
                     skb_checksum_help(skb))
                        goto out_kfree_skb;
        }
}

现在我们继续讨论数据包抓取!回想一下,在 接收端博客文章 中,我们看到了如何传递数据包给数据包抓取(例如 PCAP)。此函数中的下一块代码将即将传输的数据包交给数据包抓取(如果有的话)。

if (!list_empty(&ptype_all))
        dev_queue_xmit_nit(skb, dev);

最后,驱动程序的 ops 调用 ndo_start_xmit 向下传递数据到设备:

skb_len = skb->len;
        rc = ops->ndo_start_xmit(skb, dev);
        trace_net_dev_xmit(skb, rc, dev, skb_len);
        if (rc == NETDEV_TX_OK)
                txq_trans_update(txq);
        return rc;
}

返回 ndo_start_xmit 的返回值,指示数据包是否被传输。 我们看到了这个返回值将如何影响上层:由该函数调用方的 QDisc 重新排队数据,以便它可以稍后再次传输。

让我们来看看 GSO 的案例。 如果 skb 已经由于在此函数中发生的分段,而被分离成一个数据包链,或者先前分段但未能发送并排队等待再次发送的数据包,则此代码将运行。

gso:
        do {
                struct sk_buff *nskb = skb->next;
                skb->next = nskb->next;
                nskb->next = NULL;
                if (!list_empty(&ptype_all))
                        dev_queue_xmit_nit(nskb, dev);
                skb_len = nskb->len;
                rc = ops->ndo_start_xmit(nskb, dev);
                trace_net_dev_xmit(nskb, rc, dev, skb_len);
                if (unlikely(rc != NETDEV_TX_OK)) {
                        if (rc & ~NETDEV_TX_MASK)
                                goto out_kfree_gso_skb;
                        nskb->next = skb->next;
                        skb->next = nskb;
                        return rc;
                }
                txq_trans_update(txq);
                if (unlikely(netif_xmit_stopped(txq) && skb->next))
                        return NETDEV_TX_BUSY;
        } while (skb->next);

您可能已经猜到了,这段代码是一个 while 循环,它遍历在数据分段时生成的 skb 列表。

每个数据包:

  • 通过数据包抓取(如果有)。
  • 通过 ndo_start_xmit 传递给驱动器进行传输。

传输数据包中的任何错误都会调整需要发送的 skb 列表来处理。 错误将返回堆栈,未发送的 skb 可能会被重新排队,以便稍后再次发送。

此函数的最后一部分处理清理,并可能在出现上述错误时释放数据:

out_kfree_gso_skb:
        if (likely(skb->next == NULL)) {
                skb->destructor = DEV_GSO_CB(skb)->destructor;
                consume_skb(skb);
                return rc;
        }
out_kfree_skb:
        kfree_skb(skb);
out:
        return rc;
}
EXPORT_SYMBOL_GPL(dev_hard_start_xmit);

在继续讨论设备驱动程序之前,让我们看一下可以对我们刚刚浏览的代码进行的一些监控和调优。

监控 qdiscs

使用 tc 命令行工具

使用 tc 监控您的 qdisc 统计数据

$ tc -s qdisc show dev eth1
qdisc mq 0: root
 Sent 31973946891907 bytes 2298757402 pkt (dropped 0, overlimits 0 requeues 1776429)
 backlog 0b 0p requeues 1776429

为了监控系统的数据包传输状况,检查连接到网络设备的队列规则的统计信息至关重要。 您可以运行命令行工具 tc 来检查状态。 上面的示例显示了如何检查 eth1 接口的统计信息。

  • bytes:下推到驱动程序进行传输的字节数。
  • pkt:下推到驱动程序进行传输的数据包数量。
  • dropped:qdisc 丢弃的数据包数。 如果传输队列长度不足以容纳排队的数据,则可能发生这种情况。
  • overlimits:取决于排队规则,但可以是由于达到限制而无法入队的数据包数量,和/或在出队时触发节流事件的数据包数量。
  • requeues:调用 dev_requeue_skb 重新排队 skb 的次数。 请注意,多次重新排队的 skb 将在每次重新排队时增加此计数器。
  • backlog:当前在 qdisc 队列中的字节数。 这个数字通常在每次数据包入队时增加。

某些 qdics 可能会导出其他统计信息。 每个 qdisc 是不同的,并且可以在不同的时间增加这些计数器。 您可能需要研究您正在使用的 qdisc 的源代码,以准确了解这些值何时可以在您的系统上增加,从而帮助了解对您的影响。

调优 qdiscs

增加 __qdisc_run

您可以调整前面看到 __qdisc_run 循环的权重(上面看到的quota变量),这将导致执行更多__netif_schedule 的调用。 结果是当前 qdisc 更多次被添加到当前 CPU 的 output_queue 列表中,这应该会导致对传输数据包的额外处理。

示例:使用 sysctl 增加所有 qdisc 的 __qdisc_run 配额。

$ sudo sysctl -w net.core.dev_weight=600
增加传输队列长度

每个网络设备都有一个可以修改的 txqueuelen 调节旋钮。大多数 qdisc 在对最终应由 qdisc 传输的数据排队时,都会检查设备是否具有足够的 txqueuelen 字节。您可以调整此参数以增加 qdisc 可排队的字节数。

示例:增加 eth0txqueuelen10000

$ sudo ifconfig eth0 txqueuelen 10000

以太网设备的默认值为 1000。 您可以读取 ifconfig 的输出来检查网络设备的 txqueuelen

网络设备驱动程序

我们的旅程就要结束了。 关于数据包传输有一个重要的概念需要理解。 大多数设备和驱动程序将数据包传输处理分为两步过程:

  1. 数据被正确地排列,并且触发设备从 RAM DMA 写入数据到网络
  2. 传输完成后,设备引发中断,以便驱动程序可以取消缓冲区映射、释放内存或以其他方式清除其状态。

第二阶段通常被称为“传输完成”阶段。 我们将研究这两个阶段,但我们将从第一阶段开始:传输阶段。

我们看到 dev_hard_start_xmit 调用了 ndo_start_xmit(持有锁)来传输数据,所以让我们从检查驱动程序如何注册 ndo_start_xmit 开始,然后我们将深入研究该函数如何工作。

上一篇博文一样, 我们将研究 igb 驱动程序。

目录
相关文章
|
7月前
|
监控 Linux
Linux的epoll用法与数据结构data、event
Linux的epoll用法与数据结构data、event
92 0
|
运维 监控 网络协议
译|llustrated Guide to Monitoring and Tuning the Linux Networking Stack: Receiving Data
译|llustrated Guide to Monitoring and Tuning the Linux Networking Stack: Receiving Data
143 0
|
Linux
linux下的内存查看(virt,res,shr,data的意义)
linux下的内存查看(virt,res,shr,data的意义)
172 0
|
SQL 存储 缓存
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(十)
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(十)
363 1
|
SQL 缓存 监控
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(十一)
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(十一)
156 0
|
监控 Linux 调度
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(八)
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(八)
99 0
|
存储 Ubuntu Linux
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(七)
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(七)
147 0
|
15天前
|
监控 Linux
如何检查 Linux 内存使用量是否耗尽?这 5 个命令堪称绝了!
本文介绍了在Linux系统中检查内存使用情况的5个常用命令:`free`、`top`、`vmstat`、`pidstat` 和 `/proc/meminfo` 文件,帮助用户准确监控内存状态,确保系统稳定运行。
104 6
|
16天前
|
Linux
在 Linux 系统中,“cd”命令用于切换当前工作目录
在 Linux 系统中,“cd”命令用于切换当前工作目录。本文详细介绍了“cd”命令的基本用法和常见技巧,包括使用“.”、“..”、“~”、绝对路径和相对路径,以及快速切换到上一次工作目录等。此外,还探讨了高级技巧,如使用通配符、结合其他命令、在脚本中使用,以及实际应用案例,帮助读者提高工作效率。
57 3
|
16天前
|
监控 安全 Linux
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景,包括 ping(测试连通性)、traceroute(跟踪路由路径)、netstat(显示网络连接信息)、nmap(网络扫描)、ifconfig 和 ip(网络接口配置)。掌握这些命令有助于高效诊断和解决网络问题,保障网络稳定运行。
48 2