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

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

调优:Transmit Packet Steering(XPS)

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

这些位掩码类似于 RPS 位掩码,您可以在内核文档中找到关于这些位掩码的一些 文档

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

/sys/class/net/DEVICE_NAME/queues/QUEUE/xps_cpus

因此,对于 eth0 和传输队列 0,您需要修改文件:/sys/class/net/eth0/queues/tx-0/xps_cpus,其中十六进制数指示哪些 CPU 应处理来自 eth0 的传输队列 0 的传输完成。 正如文档所指出的,XPS 在某些配置中可能是不必要的。

排队规则!

要了解网络数据的路径,我们需要稍微了解一下 qdisc 代码。本文不打算涵盖每个不同传输队列选项的具体细节。 如果你对此感兴趣,请查看这本优秀的指南

在这篇博客文章中,我们将继续代码路径,研究通用包调度器代码是如何工作的。 特别是,我们将探索 qdisc_run_beginqdisc_run_end__qdisc_runsch_direct_xmit 如何移动网络数据到更靠近传输驱动程序的位置。

让我们先看看 qdisc_run_begin 是如何工作的,并从那里开始。

qdisc_run_beginqdisc_run_end

qdisc_run_begin 函数可以在 ./include/net/sch_generic.h 中找到:

static inline bool qdisc_run_begin(struct Qdisc *qdisc)
{
        if (qdisc_is_running(qdisc))
                return false;
        qdisc->__state |= __QDISC___STATE_RUNNING;
        return true;
}

这个函数很简单:检查 qdisc 的 __state 标志。 如果它已经在运行,则返回 false。 否则,更新 __state 以启用 __QDISC___STATE_RUNNING 位。

同样,qdisc_run_end也是寡淡的

static inline void qdisc_run_end(struct Qdisc *qdisc)
{
        qdisc->__state &= ~__QDISC___STATE_RUNNING;
}

它只是禁用 qdisc __state 字段中的 __QDISC__STATE_RUNNING 位。 需要注意的是,这两个函数都只是翻转位;自己既不实际开始,也不停止处理。 另一方面,函数 __qdisc_run 实际上开始处理。

__qdisc_run

__qdisc_run 看起来很简短:

void __qdisc_run(struct Qdisc *q)
{
        int quota = weight_p;
        while (qdisc_restart(q)) {
                /*
                 * Ordered by possible occurrence: Postpone processing if
                 * 1. we've exceeded packet quota
                 * 2. another process needs the CPU;
                 */
                if (--quota <= 0 || need_resched()) {
                        __netif_schedule(q);
                        break;
                }
        }
        qdisc_run_end(q);
}

该函数首先获取 weight_p 值。 该值通常是 sysctl 设置的,也会在接收路径中使用。我们稍后会看到如何调整这个值。 这个循环做两件事:

  1. 它在一个繁忙的循环中调用 qdisc_restart,直到返回 false(或者触发下面的 break)。
  2. 确定配额是否降至零以下或 need_resched() 返回 true。 如果其中一个为 true,则调用 __netif_schedule 并中断循环。

记住:到现在为止,内核仍然在执行代表用户程序对 sendmsg 的原始调用;用户程序当前正在累积系统时间。 如果用户程序已经用完了内核中的时间配额,那么 need_resched 将返回 true。 如果仍然有可用的配额,并且用户程序尚未使用完其时间片,qdisc_restart 将再次被调用。

让我们看看 qdisc_restart(q) 是如何工作的,然后我们将深入研究 __netif_schedule(q)

qdisc_restart

让我们跳到 qdisc_restart 的代码中:

/*
 * NOTE: Called under qdisc_lock(q) with locally disabled BH.
 *
 * __QDISC_STATE_RUNNING guarantees only one CPU can process
 * this qdisc at a time. qdisc_lock(q) serializes queue accesses for
 * this queue.
 *
 *  netif_tx_lock serializes accesses to device driver.
 *
 *  qdisc_lock(q) and netif_tx_lock are mutually exclusive,
 *  if one is grabbed, another must be free.
 *
 * Note, that this procedure can be called by a watchdog timer
 *
 * Returns to the caller:
 *                                0  - queue is empty or throttled.
 *                                >0 - queue is not empty.
 *
 */
static inline int qdisc_restart(struct Qdisc *q)
{
        struct netdev_queue *txq;
        struct net_device *dev;
        spinlock_t *root_lock;
        struct sk_buff *skb;
        /* Dequeue packet */
        skb = dequeue_skb(q);
        if (unlikely(!skb))
                return 0;
        WARN_ON_ONCE(skb_dst_is_noref(skb));
        root_lock = qdisc_lock(q);
        dev = qdisc_dev(q);
        txq = netdev_get_tx_queue(dev, skb_get_queue_mapping(skb));
        return sch_direct_xmit(skb, q, dev, txq, root_lock);
}

qdisc_restart 函数以一个有用的注释开始,该注释描述了调用此函数的一些加锁约束。 此函数执行的第一个操作是尝试从 qdisc 出队 skb。

函数 dequeue_skb 尝试获得下一个要传输的数据包。 如果队列为空 qdisc_restart 将返回 false(导致 __qdisc_run 退出)。

假设存在要传输的数据,则代码继续获取 qdisc 队列锁、qdisc 的关联设备和传输队列的引用。

所有这些都会传递到 sch_direct_xmit。 让我们先看一下 dequeue_skb,然后再看 sch_direct_xmit

dequeue_skb

让我们看一下 ./net/sched/sch_generic.c 中的 dequeue_skb。 此函数处理两种主要情况:

  1. 将之前无法发送而重新排队的数据出队,或
  2. 将要处理的新数据从 qdisc 出队。

我们来看一下第一个案例:

static inline struct sk_buff *dequeue_skb(struct Qdisc *q)
{
        struct sk_buff *skb = q->gso_skb;
        const struct netdev_queue *txq = q->dev_queue;
        if (unlikely(skb)) {
                /* check the reason of requeuing without tx lock first */
                txq = netdev_get_tx_queue(txq->dev, skb_get_queue_mapping(skb));
                if (!netif_xmit_frozen_or_stopped(txq)) {
                        q->gso_skb = NULL;
                        q->q.qlen--;
                } else
                        skb = NULL;

请注意,该代码首先引用 qdisc 的 gso_skb 字段。 此字段保存重新排队的数据的引用。 如果未重新排队数据,则此字段将为 NULL。 如果该字段不为 NULL,则代码继续获取数据的传输队列并检查队列是否停止。 如果队列没有停止,则清除 gso_skb 字段,并且减少队列长度计数器。 如果队列停止,数据仍然关联到 gso_skb,但此函数将返回 NULL

让我们检查下一个案例,其中没有重新排队的数据:

} else {
                if (!(q->flags & TCQ_F_ONETXQUEUE) || !netif_xmit_frozen_or_stopped(txq))
                        skb = q->dequeue(q);
        }
        return skb;
}

在没有数据被重新排队的情况下,另一个复杂的复合 if 语句被求值。 如果:

  1. qdisc 没有单个传输队列,或者
  2. 传输队列未停止

然后,调用 qdisc 的 dequeue 函数以获取新数据。 dequeue 的内部实现根据 qdisc 的实现和特性而有所不同。

该函数以返回待处理的数据结束。

sch_direct_xmit

现在我们来看看 sch_direct_xmit(在 ./net/sched/sch_generic.c 中),它是向下移动数据到网络设备的重要参与者。 让我们一点一点地来看看:

/*
 * Transmit one skb, and handle the return status as required. Holding the
 * __QDISC_STATE_RUNNING bit guarantees that only one CPU can execute this
 * function.
 *
 * Returns to the caller:
 *                                0  - queue is empty or throttled.
 *                                >0 - queue is not empty.
 */
int sch_direct_xmit(struct sk_buff *skb, struct Qdisc *q,
                    struct net_device *dev, struct netdev_queue *txq,
                    spinlock_t *root_lock)
{
        int ret = NETDEV_TX_BUSY;
        /* And release qdisc */
        spin_unlock(root_lock);
        HARD_TX_LOCK(dev, txq, smp_processor_id());
        if (!netif_xmit_frozen_or_stopped(txq))
                ret = dev_hard_start_xmit(skb, dev, txq);
        HARD_TX_UNLOCK(dev, txq);

该代码首先释放 qdisc 锁,然后锁定传输锁。 注意,HARD_TX_LOCK 是一个宏:

#define HARD_TX_LOCK(dev, txq, cpu) {                   \
        if ((dev->features & NETIF_F_LLTX) == 0) {      \
                __netif_tx_lock(txq, cpu);              \
        }                                               \
}

此宏检查设备功能标志中是否设置了 NETIF_F_LLTX 标志。 此标志已弃用,新设备驱动程序不应使用此标志。 此内核版本中的大多数驱动程序都不使用此标志,因此此检查将评估为 true,并将获得此数据的传输队列的锁。

接下来,检查传输队列以确保它没有停止,然后调用 dev_hard_start_xmit。 我们将在后面看到,dev_hard_start_xmit 从 Linux 内核的网络设备子系统转换网络数据到设备驱动程序本身以进行传输。 存储此函数的返回码,然后检查该返回码以确定传输是否成功。

一旦这已经运行(或者由于队列停止而被跳过),则释放队列的传输锁。 让我们继续:

spin_lock(root_lock);
if (dev_xmit_complete(ret)) {
        /* Driver sent out skb successfully or skb was consumed */
        ret = qdisc_qlen(q);
} else if (ret == NETDEV_TX_LOCKED) {
        /* Driver try lock failed */
        ret = handle_dev_cpu_collision(skb, txq, q);

接下来,再次获取此 qdisc 的锁,然后检查 dev_hard_start_xmit。 第一种情况是调用 dev_xmit_complete 检查,它只是检查返回值以确定数据是否成功发送。 如果是,则设置 qdisc 队列长度为返回值。

如果 dev_xmit_complete 返回 false,则将检查返回值以查看 dev_hard_start_xmit 是否从设备驱动程序返回 NETDEV_TX_LOCKED。 当驱动程序尝试自己锁定传输队列并失败时,具有不推荐使用的 NETIF_F_LLTX 功能标志的设备可以返回 NETDEV_TX_LOCKED。 在这种情况下,调用 handle_dev_cpu_collision 来处理锁竞争。 我们稍后会仔细研究 handle_dev_cpu_collision,但现在,让我们继续 sch_direct_xmit 并查看捕获所有的分支:

} else {
        /* Driver returned NETDEV_TX_BUSY - requeue skb */
        if (unlikely(ret != NETDEV_TX_BUSY))
                net_warn_ratelimited("BUG %s code %d qlen %d\n",
                                     dev->name, ret, q->q.qlen);
        ret = dev_requeue_skb(skb, q);
}

因此,如果驱动程序没有传输数据,并且传输锁未被持有,则可能是由于 NETDEV_TX_BUSY (如果没有打印警告)。NETDEV_TX_BUSY 可以由驱动程序返回,以指示设备或驱动程序“忙碌”并且现在不能传输数据。 在本例中,调用 dev_requeue_skb 将要重试的数据重新入队。

该函数(可能)调整返回值来结束:

if (ret && netif_xmit_frozen_or_stopped(txq))
        ret = 0;
return ret;

让我们深入了解 handle_dev_cpu_collisiondev_requeue_skb

目录
相关文章
|
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(九)
241 0
|
监控 Linux 调度
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(八)
译|Monitoring and Tuning the Linux Networking Stack: Sending Data(八)
99 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