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

网卡驱动 专栏收录该内容
14 篇文章 0 订阅

内核中的枚举类型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

  • 2
    点赞
  • 0
    评论
  • 5
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

文件为doc版,可自行转成txt,在手机上看挺好。 本资源来自网络,如有纰漏还请告知,如觉得还不错,请留言告知后来人,谢谢!!!!! 入门学习Linux常用必会60个命令实例详解 Linux必学60个命令 Linux提供了大量命令,利用它可以有效地完成大量工作,如磁盘操作、文件存取、目录操作、进程管理、文件权限设定等。所以,在Linux系统上工作离不开使用系统提供命令。要想真正理解Linux系统,就必须从Linux命令学起,通过基础命令学习可以进一步理解Linux系统。 不同Linux发行版命令数量不一样,但Linux发行版本最少命令也有200多个。这里笔者把比较重要使用频率最多命令,按照它们在系统中作用分成下面六个部分一一介绍。 ◆ 安装登录命令:login、shutdown、halt、reboot、install、mount、umount、chsh、exit、last; ◆ 文件处理命令:file、mkdir、grep、dd、find、mv、ls、diff、cat、ln; ◆ 系统管理相关命令:df、top、free、quota、at、lp、adduser、groupadd、kill、crontab; ◆ 网络操作命令:ifconfig、ip、ping、netstat、telnet、ftp、route、rlogin、rcp、finger、mail、 nslookup; ◆ 系统安全相关命令:passwd、su、umask、chgrp、chmod、chown、chattr、sudo ps、who; ◆ 其它命令:tar、unzip、gunzip、unarj、mtools、man、unendcode、uudecode。 本文以Mandrake Linux 9.1(Kenrel 2.4.21)为例,介绍Linux下安装登录命令。 immortality按:请用ctrl+f在本页中查找某一部分内容或某一命令用法。 -------------------------------------------------------------------------------- Linux必学60个命令(1)-安装与登陆命令 login 1.作用 login作用是登录系统,它使用权限是所有用户。 2.格式 login [name][-p ][-h 主机名称] 3.主要参数 -p:通知login保持现在环境参数。 -h:用来向远程登录之间传输用户名。 如果选择用命令行模式登录Linux话,那么看到第一个Linux命令就是login:。 一般界面是这样: Manddrake Linux release 9.1(Bamboo) for i586 renrel 2.4.21-0.13mdk on i686 / tty1 localhost login:root password: 上面代码中,第一行是Linux发行版本号,第二行是内核版本号登录虚拟控制台,我们在第三行输入登录名,按“Enter”键在Password后输入账户密码,即可登录系统。出于安全考虑,输入账户密码时字符不会在屏幕上回显,光标也不移动。 登录后会看到下面这个界面(以超级用户为例): [root@localhost root]# last login:Tue ,Nov 18 10:00:55 on vc/1 上面显示是登录星期、月、日、时间使用虚拟控制台。 4.应用技巧 Linux 是一个真正多用户操作系统,可以同时接受多个用户登录,还允许一个用户进行多次登录。这是因为Linux许多版本Unix一样,提供了虚拟控制台访问方式,允许用户在同一时间从控制台(系统控制台是与系统直接相连监视器键盘)进行多次登录。每个虚拟控制台可以看作是一个独立工作站,工作台之间可以切换。虚拟控制台切换可以通过按下Alt键一个功能键来实现,通常使用F1-F6 。 例如,用户登录后,按一下“Alt+ F2”键,用户就可以看到上面出现“login:”提示符,说明用户看到了第二个虚拟控制台。然后只需按“Alt+ F1”键,就可以回到第一个虚拟控制台。一个新安装Linux系统允许用户使用“Alt+F1”到“Alt+F6”键来访问前六个虚拟控制台。虚拟控制台最有用是,当一个程序出错造成系统死锁时,可以切换到其它虚拟控制台工作,关闭这个程序。 shutdown 1.作用 shutdown命令作用是关闭计算机,它使用权限是超级用户。 2.格式 shutdown [-h][-i][-k][-m][-t] 3.重要参数 -t:在改变到其它运行级别之前,
相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值