网络驱动发送队列的停止和冻结_netif_stop_subqueue-程序员宅基地

技术标签: 网卡驱动  

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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/sinat_20184565/article/details/104287749

智能推荐

c# 调用c++ lib静态库_c#调用lib-程序员宅基地

文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib

deepin/ubuntu安装苹方字体-程序员宅基地

文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang

html表单常见操作汇总_html表单的处理程序有那些-程序员宅基地

文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些

PHP设置谷歌验证器(Google Authenticator)实现操作二步验证_php otp 验证器-程序员宅基地

文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器

【Python】matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距-程序员宅基地

文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距

docker — 容器存储_docker 保存容器-程序员宅基地

文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器

随便推点

网络拓扑结构_网络拓扑csdn-程序员宅基地

文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn

JS重写Date函数,兼容IOS系统_date.prototype 将所有 ios-程序员宅基地

文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios

如何将EXCEL表导入plsql数据库中-程序员宅基地

文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql

Git常用命令速查手册-程序员宅基地

文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...

分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120-程序员宅基地

文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120

【C++缺省函数】 空类默认产生的6个类成员函数_空类默认产生哪些类成员函数-程序员宅基地

文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数

推荐文章

热门文章

相关标签