网络驱动发送队列的停止和冻结

内核中的枚举类型netdev_queue_state_t定义了三种情况,包括设备驱动层面的发送队列停止,协议栈层面的发送队列停止以及发送队列的冻结。另外,定义了三个宏表示了三种组合,其中QUEUE_STATE_ANY_XOFF包含驱动和协议栈两个层面的发送队列停止;其它两个定义如下所示,意义直观。

enum netdev_queue_state_t {
    __QUEUE_STATE_DRV_XOFF,
    __QUEUE_STATE_STACK_XOFF,
    __QUEUE_STATE_FROZEN,
};

#define QUEUE_STATE_DRV_XOFF    (1 << __QUEUE_STATE_DRV_XOFF)
#define QUEUE_STATE_STACK_XOFF  (1 << __QUEUE_STATE_STACK_XOFF)
#define QUEUE_STATE_FROZEN      (1 << __QUEUE_STATE_FROZEN)

#define QUEUE_STATE_ANY_XOFF    (QUEUE_STATE_DRV_XOFF | QUEUE_STATE_STACK_XOFF)
#define QUEUE_STATE_ANY_XOFF_OR_FROZEN (QUEUE_STATE_ANY_XOFF | QUEUE_STATE_FROZEN)
#define QUEUE_STATE_DRV_XOFF_OR_FROZEN (QUEUE_STATE_DRV_XOFF | QUEUE_STATE_FROZEN)

以下介绍QUEUE_STATE_DRV_XOFF和QUEUE_STATE_FROZEN标志在内核网络系统中的使用。而QUEUE_STATE_STACK_XOFF标志主要由BQL(Byte Queue Limit)算法用来控制网卡队列中的数据量,达到减小延迟作用而使用的。

驱动层发送队列停止

宏定义__QUEUE_STATE_DRV_XOFF标志位由网络设备驱动层用于停止发送队列,封装了以下的函数,分别用于设置、清除及判断此标志位,此三个函数都是以设备队列结构netdev_queue为操作参数。

static __always_inline void netif_tx_stop_queue(struct netdev_queue *dev_queue)
{
    set_bit(__QUEUE_STATE_DRV_XOFF, &dev_queue->state);
}
static __always_inline void netif_tx_start_queue(struct netdev_queue *dev_queue)
{
    clear_bit(__QUEUE_STATE_DRV_XOFF, &dev_queue->state);
}
static inline bool netif_tx_queue_stopped(const struct netdev_queue *dev_queue)
{
    return test_bit(__QUEUE_STATE_DRV_XOFF, &dev_queue->state);
}

发送队列的停止通常发生在驱动程序资源不足,典型的如发送描述符不足,函数netif_tx_stop_queue用于停止发生此状况的发送队列。如以下的Broadcom的bnxt网卡驱动,在判断发送ring空间不足时,使用此函数停止当前的发送队列。

static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
    struct bnxt *bp = netdev_priv(dev);

    txq = netdev_get_tx_queue(dev, i);
    txr = &bp->tx_ring[bp->tx_ring_map[i]];
    prod = txr->tx_prod;

    free_size = bnxt_tx_avail(bp, txr);
    if (unlikely(free_size < skb_shinfo(skb)->nr_frags + 2)) {
        netif_tx_stop_queue(txq);
        return NETDEV_TX_BUSY;
    }

对于一些仅有单个发送队列的网络设备,以下函数netif_stop_queue可用于停止网络设备发送队列。函数netif_stop_subqueue与以上的函数netif_tx_stop_queue功能相同,区别在于这里的参数为发送队列的索引值。

static inline void netif_stop_queue(struct net_device *dev)
{
    netif_tx_stop_queue(netdev_get_tx_queue(dev, 0));
}
static inline void netif_stop_subqueue(struct net_device *dev, u16 queue_index)
{
    struct netdev_queue *txq = netdev_get_tx_queue(dev, queue_index);
    netif_tx_stop_queue(txq);
}

封装函数netif_tx_stop_all_queues,以网络设备结构net_device为参数,用于停止网络设备的所有发送队列。驱动层在关闭(shutdown)网络设备,或者设备链路断开及设备进入挂起节电模式等情况时,使用此函数停止所有发送队列。

void netif_tx_stop_all_queues(struct net_device *dev)
{    
    unsigned int i;

    for (i = 0; i < dev->num_tx_queues; i++) {
        struct netdev_queue *txq = netdev_get_tx_queue(dev, i);

        netif_tx_stop_queue(txq);
    }
}

与上一个函数类似,函数netif_tx_disable也用于停止网络设备的所有发送队列,但是,此处将关闭中断下半部,并且获取网络设备发送队列的发送锁,通常使用在驱动程序的非发送路径中,如关闭(shutdown)网络设备。在驱动层的ndo_start_xmit发送函数实现中,不能使用netif_tx_disable函数,因为上层发送函数(如:__dev_queue_xmit)已经获取了发送锁。

static inline void netif_tx_disable(struct net_device *dev)
{
    unsigned int i;
    int cpu;

    local_bh_disable();
    cpu = smp_processor_id();
    for (i = 0; i < dev->num_tx_queues; i++) {
        struct netdev_queue *txq = netdev_get_tx_queue(dev, i);

        __netif_tx_lock(txq, cpu);
        netif_tx_stop_queue(txq);
        __netif_tx_unlock(txq);
    }
    local_bh_enable();
}

驱动层发送队列开启

与上一节介绍的相反,发送队列的开启通过清除__QUEUE_STATE_DRV_XOFF标志实现,如下函数netif_tx_start_queue所示。

static __always_inline void netif_tx_start_queue(struct netdev_queue *dev_queue)
{
    clear_bit(__QUEUE_STATE_DRV_XOFF, &dev_queue->state);
}

对于单发送队列的设备,可使用封装函数netif_start_queue开启发送队列。函数netif_start_subqueue与以上的函数netif_tx_start_queue功能相同,区别在于前者根据发送队列索引值来开启发送队列。

static inline void netif_start_queue(struct net_device *dev)
{
    netif_tx_start_queue(netdev_get_tx_queue(dev, 0));
}
static inline void netif_start_subqueue(struct net_device *dev, u16 queue_index)
{
    struct netdev_queue *txq = netdev_get_tx_queue(dev, queue_index);

    netif_tx_start_queue(txq);
}

与上一节介绍的关闭所有发送队列函数netif_tx_stop_all_queues相反,以下函数netif_tx_wake_all_queues可在网络设备的链路恢复之后,开启所有的发送队列。子函数netif_tx_wake_queue除了清除发送队列的__QUEUE_STATE_DRV_XOFF标志,还将重新调度软中断,以处理发送中断的下半部。

static inline void netif_tx_wake_all_queues(struct net_device *dev)
{
    unsigned int i;
 
    for (i = 0; i < dev->num_tx_queues; i++) {
        struct netdev_queue *txq = netdev_get_tx_queue(dev, i);
        netif_tx_wake_queue(txq);
    }
} 
void netif_tx_wake_queue(struct netdev_queue *dev_queue)
{
    if (test_and_clear_bit(__QUEUE_STATE_DRV_XOFF, &dev_queue->state)) {
        struct Qdisc *q;

        rcu_read_lock();
        q = rcu_dereference(dev_queue->qdisc);
        __netif_schedule(q);
        rcu_read_unlock();
    }
}

对驱动层发送队列的判断

对发送队列停止标志位__QUEUE_STATE_DRV_XOFF的判断由函数netif_tx_queue_stopped完成。内核重要的驱动层发送接口函数dev_hard_start_xmit如下,如果判断到驱动层已经停止了发送队列,返回错误码NETDEV_TX_BUSY,表明驱动发送繁忙。

struct sk_buff *dev_hard_start_xmit(struct sk_buff *first, struct net_device *dev, struct netdev_queue *txq, int *ret)
{
    struct sk_buff *skb = first;
    int rc = NETDEV_TX_OK;

    while (skb) {
        struct sk_buff *next = skb->next;

        skb_mark_not_on_list(skb);
        rc = xmit_one(skb, dev, txq, next != NULL);
        ...

        skb = next;
        if (netif_tx_queue_stopped(txq) && skb) {
            rc = NETDEV_TX_BUSY;
            break;
        }

另外,如在Intel的i40e驱动程序中,在回收发送资源之后,如果可用的发送环超过阈值,将清除发送队列的__QUEUE_STATE_DRV_XOFF停止标志。

static bool i40e_clean_tx_irq(struct i40e_vsi *vsi, struct i40e_ring *tx_ring, int napi_budget)
{
    ...
#define TX_WAKE_THRESHOLD ((s16)(DESC_NEEDED * 2))
    if (unlikely(total_packets && netif_carrier_ok(tx_ring->netdev) &&
             (I40E_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(__I40E_VSI_DOWN, vsi->state)) {
            netif_wake_subqueue(tx_ring->netdev, tx_ring->queue_index);
            ++tx_ring->tx_stats.restart_queue;

驱动层发送队列的冻结

发送队列的冻结发生在以下函数netif_tx_lock中,主要用于控制流控系统队列的发送操作。在设置__QUEUE_STATE_FROZEN之前,将获取相应队列的发送锁(__netif_tx_lock),因此,在驱动层的ndo_start_xmit函数实现中,不能使用netif_tx_lock函数,因为上层发送函数(如:sch_direct_xmit)已经获取了发送锁。

static inline void netif_tx_lock(struct net_device *dev)
{
    spin_lock(&dev->tx_global_lock);
    cpu = smp_processor_id();
    for (i = 0; i < dev->num_tx_queues; i++) {
        struct netdev_queue *txq = netdev_get_tx_queue(dev, i);
 
        /* We are the only thread of execution doing a freeze, but we have to grab the _xmit_lock in
         * order to synchronize with threads which are in the ->hard_start_xmit() handler and already
         * checked the frozen bit. 
         */
        __netif_tx_lock(txq, cpu); 
        set_bit(__QUEUE_STATE_FROZEN, &txq->state);
        __netif_tx_unlock(txq);
    }
}

与以上介绍的__QUEUE_STATE_DRV_XOFF标志不同,此处的冻结标志用于在驱动层的处理中需要暂时关闭流控系统的发送操作的地方。例如,在Mellanox的mlx4驱动中,函数mlx4_en_stop_port在停止发送队列时,首先冻结了设备的所有队列。

但是,Intel所有网卡的驱动实现中,都没有使用__QUEUE_STATE_FROZEN冻结标志。在以下的冻结标志判断函数中可见,冻结标志也不会单独使用。

void mlx4_en_stop_port(struct net_device *dev, int detach)
{
    ...
    /* Synchronize with tx routine */
    netif_tx_lock_bh(dev);
    if (detach)
        netif_device_detach(dev);
    netif_tx_stop_all_queues(dev);
    netif_tx_unlock_bh(dev);

冻结标志的判断函数有以下两个,其中netif_xmit_frozen_or_stopped函数除判断冻结标志之外,还判断驱动和协议栈两个停止发送标志(QUEUE_STATE_ANY_XOFF);后一个函数netif_xmit_frozen_or_drv_stopped除判断冻结标志外,还判断驱动停止发送标志(QUEUE_STATE_DRV_XOFF)。所以,在一些驱动程序中,仅设置驱动发送停止标志,也可达到相同的效果。

#define QUEUE_STATE_ANY_XOFF    (QUEUE_STATE_DRV_XOFF | QUEUE_STATE_STACK_XOFF)
#define QUEUE_STATE_ANY_XOFF_OR_FROZEN (QUEUE_STATE_ANY_XOFF | QUEUE_STATE_FROZEN)
#define QUEUE_STATE_DRV_XOFF_OR_FROZEN (QUEUE_STATE_DRV_XOFF | QUEUE_STATE_FROZEN)
 
static inline bool netif_xmit_frozen_or_stopped(const struct netdev_queue *dev_queue)
{
    return dev_queue->state & QUEUE_STATE_ANY_XOFF_OR_FROZEN;
}
static inline bool netif_xmit_frozen_or_drv_stopped(const struct netdev_queue *dev_queue)
{
    return dev_queue->state & QUEUE_STATE_DRV_XOFF_OR_FROZEN;
}

流控系统的直接发送函数(不进入流控队列)sch_direct_xmit,或者由流控队列中取出skb的函数dequeue_skb,都调用以上的netif_xmit_frozen_or_stopped函数进行发送前的判断。

bool sch_direct_xmit(struct sk_buff *skb, struct Qdisc *q, ...)
{
    ...
    if (likely(skb)) {
        HARD_TX_LOCK(dev, txq, smp_processor_id());
        if (!netif_xmit_frozen_or_stopped(txq))
            skb = dev_hard_start_xmit(skb, dev, txq, &ret);
        HARD_TX_UNLOCK(dev, txq);
    }
	
static struct sk_buff *dequeue_skb(struct Qdisc *q, bool *validate,int *packets)
{
    if (unlikely(!skb_queue_empty(&q->gso_skb))) {
        ...
        txq = skb_get_tx_queue(txq->dev, skb);
        if (!netif_xmit_frozen_or_stopped(txq)) {
            skb = __skb_dequeue(&q->gso_skb);

由于netif_xmit_frozen_or_drv_stopped函数并没有判断协议栈发送停止标志QUEUE_STATE_STACK_XOFF,其专门用在一些不需要进行协议栈处理的系统中,目前内核中使用此函数的有XDP和AF_PACKET套接口系统。例如XDP中的sendmsg接口函数的一个子发送函数xsk_generic_xmit,其调用如下的dev_direct_xmit函数执行发送,在调用驱动发送函数前,首先执行发送队列的状态判断。

int dev_direct_xmit(struct sk_buff *skb, u16 queue_id)
{
    ...
    HARD_TX_LOCK(dev, txq, smp_processor_id());
    if (!netif_xmit_frozen_or_drv_stopped(txq))
        ret = netdev_start_xmit(skb, dev, txq, false);
    HARD_TX_UNLOCK(dev, txq);

最后,解冻操作实现在函数netif_tx_unlock中,清除发送队列的__QUEUE_STATE_FROZEN标志。与冻结操作不同,这里并没有获取队列的发送锁,但是在清除冻结标志后,如果发送队列未处于停止状态,将主动调度下半部执行流控系统中已有的发送任务。

static inline void netif_tx_unlock(struct net_device *dev)
{
    for (i = 0; i < dev->num_tx_queues; i++) {
        struct netdev_queue *txq = netdev_get_tx_queue(dev, i);

        /* No need to grab the _xmit_lock here.  If the queue is not stopped for another reason, we
         * force a schedule.
         */
        clear_bit(__QUEUE_STATE_FROZEN, &txq->state);
        netif_schedule_queue(txq);
    }
    spin_unlock(&dev->tx_global_lock);
}

内核版本 5.0

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值