BQL队列控制

Byte Queue Limits算法试图控制网卡发送队列的大小,如下Intel的e1000驱动的网卡显示当前发送ring有256个,如果填满256个1500字节的报文,对于带宽为1G的e1000完全发送完成,需要256*1500/1G的时间,大约为3.84ms。意味值最后一个报文要在本地经历大约3.84ms的延迟,另一方面对于TCP协议,将对其RTT的估算等造成不利影响。

BQL与TCP的TSQ功能类似,但是TSQ位于更高层的协议栈,来控制发往Qdisc/Device队列中的报文的量。

$ sudo ethtool --show-ring ens33
Ring parameters for ens33:
Pre-set maximums:
RX:             4096
RX Mini:        0
RX Jumbo:       0
TX:             4096
Current hardware settings:
RX:             256
RX Mini:        0
RX Jumbo:       0
TX:             256

对于BQL,存在如何确定网卡发送队列大小限值的问题,首先,限值的表示方式,如果使用报文数量,有的报文可能包含很少的字节,占用很少的发送时间,而又存在比较大的报文,所以,采用报文数量进行控制造成不准确。BQL最终使用的是字节数量,但是其不是一个固定的限值,将根据系统负荷而改变,因为对于负荷重的系统,发送队列的填充可能会延迟,造成网卡的闲置。相反,对于负荷轻的系统,使用较小的限值,发送队列填充及时的话,也可保持网卡的持续工作。内核中实现此功能的为DQL(dynamic queue limits),BQL实现在此功能之上。

BQL在内核中增加了两类接口,第一个在网卡驱动程序中,当报文添加到网卡队列中后,通知BQL,其进行限值判断。第二个是在网卡报文发送完成之后,通知BQL,进行相应处理,比如,之前停止的发送队列是否可开启,以及更新BQL的队列限值。

BQL发送接口

核心函数为netdev_tx_sent_queue,如果DQL判段达到限值,发送队列设置__QUEUE_STATE_STACK_XOFF,停止发送。DQL算法暂不介绍(不懂)。这里,在再次检查之前设置STACK_OFF标志位,是因为在BQL的发送完成处理函数netdev_tx_completed_queue中,完成空间的释放之后(dql_completed),进行STACK_OFF的检查,如果为真,将再次进行发送调度。所以这里先将此标志设置为真。

static inline void netdev_tx_sent_queue(struct netdev_queue *dev_queue, unsigned int bytes)
{
#ifdef CONFIG_BQL
dql_queued(&dev_queue->dql, bytes);

if (likely(dql_avail(&dev_queue->dql) >= 0))
    return;

set_bit(__QUEUE_STATE_STACK_XOFF, &dev_queue->state);
/*
 * The XOFF flag must be set before checking the dql_avail below,
 * because in netdev_tx_completed_queue we update the dql_completed before checking the XOFF flag.
 */
smp_mb();

/* check again in case another CPU has just made room avail */
if (unlikely(dql_avail(&dev_queue->dql) >= 0))
    clear_bit(__QUEUE_STATE_STACK_XOFF, &dev_queue->state);

#endif
}

对于一些网卡驱动,目前主要是Mellanox的mlx4驱动,以下封装函数__netdev_tx_sent_queue用于在发送batch型的skb报文时,仅在最后一个skb处理时,进行是否要设置__QUEUE_STATE_STACK_XOFF标志的判断。

static inline bool __netdev_tx_sent_queue(struct netdev_queue *dev_queue, unsigned int bytes, bool xmit_more)
{
    if (xmit_more) {
#ifdef CONFIG_BQL
        dql_queued(&dev_queue->dql, bytes);
#endif
        return netif_tx_queue_stopped(dev_queue);
    }
    netdev_tx_sent_queue(dev_queue, bytes);
    return true;
}

对于Intel网卡的i40e驱动,在发送函数i40e_tx_map中,调用BQL函数netdev_tx_sent_queue进行处理。

static inline int i40e_tx_map(struct i40e_ring *tx_ring, struct sk_buff *skb,
                  struct i40e_tx_buffer *first, u32 tx_flags, const u8 hdr_len, u32 td_cmd, u32 td_offset)
{
    ...
    netdev_tx_sent_queue(txring_txq(tx_ring), first->bytecount);

BQL发送完成接口

核心函数为netdev_tx_completed_queue如下,首先是DQL的处理函数dql_completed,其次,注意这里的smp_mb,如果没有此语句,将由可能导致发送队列永远的停止,对比netdev_tx_sent_queue中的smp_mb语句的位置,如果在发送完成之后,存在可使用的发送空间,netdev_tx_sent_queue将清空STACK_XOFF标志,而这里的test_and_clear_bit将返回假,导致调度函数netif_schedule_queue不能够执行。

static inline void netdev_tx_completed_queue(struct netdev_queue *dev_queue, unsigned int pkts, unsigned int bytes)
{
#ifdef CONFIG_BQL
    if (unlikely(!bytes))
        return;

    dql_completed(&dev_queue->dql, bytes);
    /*
     * Without the memory barrier there is a small possiblity that
     * netdev_tx_sent_queue will miss the update and cause the queue to be stopped forever
     */
    smp_mb();

    if (dql_avail(&dev_queue->dql) < 0)
        return;

    if (test_and_clear_bit(__QUEUE_STATE_STACK_XOFF, &dev_queue->state))
        netif_schedule_queue(dev_queue);
#endif
}

仍然使用Intel的i40e为例,在函数i40e_clean_tx_irq中,调用BQL的函数netdev_tx_completed_queue。

static bool i40e_clean_tx_irq(struct i40e_vsi *vsi, struct i40e_ring *tx_ring, int napi_budget)
{
    /* notify netdev of completed buffers */
    netdev_tx_completed_queue(txring_txq(tx_ring),
                  total_packets, total_bytes);

BQL重置接口

驱动程序可使用BQL的函数netdev_tx_reset_queue重置BQL,包括清空STACK_XOFF标志,重置DQL算法。

static inline void netdev_tx_reset_queue(struct netdev_queue *q)
{
#ifdef CONFIG_BQL
    clear_bit(__QUEUE_STATE_STACK_XOFF, &q->state);
    dql_reset(&q->dql);
#endif
}

对于Intel的i40e驱动,在函数i40e_clean_tx_ring中,调用BQL系统的函数netdev_tx_reset_queue进行重置。

void i40e_clean_tx_ring(struct i40e_ring *tx_ring)
{
    ...
    /* Zero out the descriptor ring */
    memset(tx_ring->desc, 0, tx_ring->size);

    tx_ring->next_to_use = 0;
    tx_ring->next_to_clean = 0;

    if (!tx_ring->netdev)
        return;

    /* cleanup Tx queue statistics */
    netdev_tx_reset_queue(txring_txq(tx_ring));
}

内核版本 5.0

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页