文章目录

  • 数据结构
    • 排队规则: Qdisc
  • 默认网络设备排队规则
    • dev_init_scheduler_queue()
    • dev_activate()
      • attach_one_default_qdisc()
      • transition_one_qdisc()
    • dev_deactivate()
  • 发送过程中的入队出队
    • qdisc_enqueue_root()
    • dequeue_skb()
  • 排队规则: noop_qdisc
  • 排队规则:noqueue_qdisc
  • 排队规则: pfifo_fast

绝大多数的场景下,网络设备都是使用默认的流量控制机制在工作,这篇笔记分析了默认的流量控制机制。

数据结构

排队规则: Qdisc

struct Qdisc
{// 入队与出队操作int (*enqueue)(struct sk_buff *skb, struct Qdisc *dev);struct sk_buff *(*dequeue)(struct Qdisc *dev);unsigned        flags;
#define TCQ_F_BUILTIN   1
#define TCQ_F_THROTTLED 2
#define TCQ_F_INGRESS   4int    padded; // Qdisc的第一个成员是字节对齐的,这使得开头可能会有一定的paddingstruct Qdisc_ops *ops; // 具体排队规则实现的函数操作集struct qdisc_size_table  *stab;u32   handle; // 句柄u32    parent; // 父节点句柄,通过这两个字段可以构建复杂的排队规则atomic_t      refcnt;unsigned long        state;struct sk_buff        *gso_skb;struct sk_buff_head    q;struct netdev_queue   *dev_queue;struct Qdisc *next_sched;struct list_head    list;struct gnet_stats_basic    bstats;struct gnet_stats_queue  qstats;struct gnet_stats_rate_est   rate_est;int            (*reshape_fail)(struct sk_buff *skb,struct Qdisc *q);void *u32_node;/* This field is deprecated, but it is still used by CBQ* and it will live until better solution will be invented.*/struct Qdisc *__parent;
};

默认网络设备排队规则

dev_init_scheduler_queue()

在register_netdevice()中有调用dev_init_scheduler()对网络设备对象中的流量控制字段进行初始化。

static void dev_init_scheduler_queue(struct net_device *dev,struct netdev_queue *dev_queue,void *_qdisc)
{struct Qdisc *qdisc = _qdisc;dev_queue->qdisc = qdisc;dev_queue->qdisc_sleeping = qdisc;
}void dev_init_scheduler(struct net_device *dev)
{// 将每个发送队列的排队规则都设置为noop_qdiscnetdev_for_each_tx_queue(dev, dev_init_scheduler_queue, &noop_qdisc);// 将接收队列的排队规则也设置为noop_qdiscdev_init_scheduler_queue(dev, &dev->rx_queue, &noop_qdisc);// 初始化watchdog_timersetup_timer(&dev->watchdog_timer, dev_watchdog, (unsigned long)dev);
}

可见,设备注册时,会将所有的发送队列和接收队列的排队规则都设置为noop_qdisc,顾名思义,这个队列什么都不做,仅仅是释放入队列的skb,所以使用该队列是没有办法进行数据包收发的。

dev_activate()

设备打开时,在dev_open()中会调用dev_activate()激活网络设备对象的发送队列,激活后,设备接口层的dev_queue_xmit()才能通过流控机制向网卡发送数据。

void dev_activate(struct net_device *dev)
{int need_watchdog;/* No queueing discipline is attached to device;create default one i.e. pfifo_fast for devices,which need queueing and noqueue_qdisc forvirtual interfaces*/// 如果所有发送队列的排队规则都是noop_qdisc,那么将默认的改为pfifo_fastif (dev_all_qdisc_sleeping_noop(dev))netdev_for_each_tx_queue(dev, attach_one_default_qdisc, NULL);// 没有__LINK_STATE_NOCARRIER标记说明网络设备物理层信号正常,可以发送数据if (!netif_carrier_ok(dev))/* Delay activation until next carrier-on event */return;need_watchdog = 0;netdev_for_each_tx_queue(dev, transition_one_qdisc, &need_watchdog);// 设置接收队列排队规则transition_one_qdisc(dev, &dev->rx_queue, NULL);if (need_watchdog) {dev->trans_start = jiffies;dev_watchdog_up(dev);}
}

attach_one_default_qdisc()

static void attach_one_default_qdisc(struct net_device *dev,struct netdev_queue *dev_queue,void *_unused)
{struct Qdisc *qdisc;// 设备驱动程序若要使用排队规则,必须设置该字段,指定每个发送队列可以排队的skb数目if (dev->tx_queue_len) {// 使用pfifo_fast排队规则作为默认的排队规则qdisc = qdisc_create_dflt(dev, dev_queue, &pfifo_fast_ops, TC_H_ROOT);if (!qdisc) {printk(KERN_INFO "%s: activation failed\n", dev->name);return;}} else {// 使用noqueue排队规则作为默认的排队规则,该排队规则没有提供enqueue()方法,使得// dev_queue_xmit()过程中走无流量控制发送流程qdisc =  &noqueue_qdisc;}dev_queue->qdisc_sleeping = qdisc;
}// 使用qdisc_create_dflt()创建默认的pfifo_fast排队规则
struct Qdisc * qdisc_create_dflt(struct net_device *dev,struct netdev_queue *dev_queue,struct Qdisc_ops *ops,unsigned int parentid)
{struct Qdisc *sch;// 根据ops分配一个排队规则sch = qdisc_alloc(dev_queue, ops);if (IS_ERR(sch))goto errout;// 复杂的排队规则可以定义父子关系sch->parent = parentid;// 调用排队规则提供的初始化回调if (!ops->init || ops->init(sch, NULL) == 0)return sch;// 初始化失败qdisc_destroy(sch);
errout:return NULL;
}struct Qdisc *qdisc_alloc(struct netdev_queue *dev_queue,struct Qdisc_ops *ops)
{void *p;struct Qdisc *sch;unsigned int size;int err = -ENOBUFS;/* ensure that the Qdisc and the private data are 32-byte aligned */// 计算排队规则实际的内存占用,包括私有数据结构size = QDISC_ALIGN(sizeof(*sch));size += ops->priv_size + (QDISC_ALIGNTO - 1);p = kzalloc(size, GFP_KERNEL);if (!p)goto errout;// 初始化成员sch = (struct Qdisc *) QDISC_ALIGN((unsigned long) p);sch->padded = (char *) sch - (char *) p;INIT_LIST_HEAD(&sch->list);skb_queue_head_init(&sch->q);sch->ops = ops;sch->enqueue = ops->enqueue;sch->dequeue = ops->dequeue;sch->dev_queue = dev_queue;dev_hold(qdisc_dev(sch));atomic_set(&sch->refcnt, 1);return sch;
errout:return ERR_PTR(err);
}

transition_one_qdisc()

让排队规则真正生效。

static void transition_one_qdisc(struct net_device *dev,struct netdev_queue *dev_queue,void *_need_watchdog)
{struct Qdisc *new_qdisc = dev_queue->qdisc_sleeping;int *need_watchdog_p = _need_watchdog;if (!(new_qdisc->flags & TCQ_F_BUILTIN))clear_bit(__QDISC_STATE_DEACTIVATED, &new_qdisc->state);rcu_assign_pointer(dev_queue->qdisc, new_qdisc);if (need_watchdog_p && new_qdisc != &noqueue_qdisc)*need_watchdog_p = 1;
}

dev_deactivate()

设备关闭时,在dev_close()中会调用dev_deactivate()将设备的排队规则恢复为初始化时指定的noop_qdisc,这样后续设备将无法发送数据包。

void dev_deactivate(struct net_device *dev)
{// 将发送队列的排队规则都设置为noop_qdiscnetdev_for_each_tx_queue(dev, dev_deactivate_queue, &noop_qdisc);// 将接收队列的排队规则设置为noop_qdiscdev_deactivate_queue(dev, &dev->rx_queue, &noop_qdisc);// 停止watchdog定时器dev_watchdog_down(dev);/* Wait for outstanding qdisc-less dev_queue_xmit calls. */synchronize_rcu();/* Wait for outstanding qdisc_run calls. */// 等待所有cpu中发送过程的结束while (some_qdisc_is_busy(dev))yield();
}

发送过程中的入队出队

在dev_queue_xmit()中,有看到入队是调用qdisc_enqueue_root()接口;在qdisc_restart()中,有看到出队是调用dequeue_skb(),其实现如下:

qdisc_enqueue_root()

static inline int qdisc_enqueue(struct sk_buff *skb, struct Qdisc *sch)
{#ifdef CONFIG_NET_SCHEDif (sch->stab)qdisc_calculate_pkt_len(skb, sch->stab);
#endifreturn sch->enqueue(skb, sch);
}static inline int qdisc_enqueue_root(struct sk_buff *skb, struct Qdisc *sch)
{qdisc_skb_cb(skb)->pkt_len = skb->len;return qdisc_enqueue(skb, sch) & NET_XMIT_MASK;
}

dequeue_skb()

static inline struct sk_buff *dequeue_skb(struct Qdisc *q)
{struct sk_buff *skb = q->gso_skb;// 发送时,优先处理GSO剩余的skb,然后才是队列中的数据包if (unlikely(skb)) {struct net_device *dev = qdisc_dev(q);struct netdev_queue *txq;/* check the reason of requeuing without tx lock first */txq = netdev_get_tx_queue(dev, skb_get_queue_mapping(skb));if (!netif_tx_queue_stopped(txq) && !netif_tx_queue_frozen(txq))q->gso_skb = NULL;elseskb = NULL;} else {skb = q->dequeue(q);}return skb;
}

排队规则: noop_qdisc

noop_qdisc是网络设备在注册时指定的排队规则,意思是这种排队规则是无法进行数据包发送,其实现如下。

/* "NOOP" scheduler: the best scheduler, recommended for all interfacesunder all circumstances. It is difficult to invent anything faster orcheaper.*/
static int noop_enqueue(struct sk_buff *skb, struct Qdisc * qdisc)
{kfree_skb(skb);return NET_XMIT_CN;
}static struct sk_buff *noop_dequeue(struct Qdisc * qdisc)
{return NULL;
}struct Qdisc_ops noop_qdisc_ops __read_mostly = {.id      =  "noop",.priv_size =  0,.enqueue  =  noop_enqueue,.dequeue   =  noop_dequeue,.peek      =  noop_dequeue,.owner     =  THIS_MODULE,
};static struct netdev_queue noop_netdev_queue = {.qdisc       =  &noop_qdisc,.qdisc_sleeping =  &noop_qdisc,
};struct Qdisc noop_qdisc = {.enqueue  =  noop_enqueue,.dequeue   =  noop_dequeue,.flags     =  TCQ_F_BUILTIN,.ops      =  &noop_qdisc_ops,.list       =  LIST_HEAD_INIT(noop_qdisc.list),.q.lock     =  __SPIN_LOCK_UNLOCKED(noop_qdisc.q.lock),.dev_queue  =  &noop_netdev_queue,
};

可见,如果配置成了noop_qdisc,那么设备是无法发送数据的,因为其入队操作是释放skb,出队操作返回空,表示出队失败。

排队规则:noqueue_qdisc

如上介绍,noqueue_qdisc是用来给那些不需要流量控制的网络设备使用的,之所以定义这样一个排队规则,完全是为了让设备接口层的框架部分代码能够统一处理有流量控制和无流量控制两种发送过程。

static struct Qdisc_ops noqueue_qdisc_ops __read_mostly = {.id      =  "noqueue",.priv_size  =  0,.enqueue  =  noop_enqueue,.dequeue   =  noop_dequeue,.peek      =  noop_dequeue,.owner     =  THIS_MODULE,
};static struct Qdisc noqueue_qdisc;
static struct netdev_queue noqueue_netdev_queue = {.qdisc      =  &noqueue_qdisc,.qdisc_sleeping  =  &noqueue_qdisc,
};static struct Qdisc noqueue_qdisc = {// 这里的enqueue指定为NULL会使得dev_queue_xmit()中走无流量控制发送流程.enqueue  =  NULL,.dequeue   =  noop_dequeue,.flags     =  TCQ_F_BUILTIN,.ops      =  &noqueue_qdisc_ops,.list        =  LIST_HEAD_INIT(noqueue_qdisc.list),.q.lock      =  __SPIN_LOCK_UNLOCKED(noqueue_qdisc.q.lock),.dev_queue   =  &noqueue_netdev_queue,
};

排队规则: pfifo_fast

如前面介绍,pfifo_fast是设备打开后默认使用的排队规则,下面看其入队、出队操作都是如何实现的。

#define TC_PRIO_MAX  15
static const u8 prio2band[TC_PRIO_MAX+1] ={ 1, 2, 2, 2, 1, 2, 0, 0 , 1, 1, 1, 1, 1, 1, 1, 1 };/* 3-band FIFO queue: old style, but should be a bit faster thangeneric prio+fifo combination.*/
#define PFIFO_FAST_BANDS 3static inline struct sk_buff_head *prio2list(struct sk_buff *skb,  struct Qdisc *qdisc)
{// 根据skb的priority字段确定将skb放入那个发送队列struct sk_buff_head *list = qdisc_priv(qdisc);return list + prio2band[skb->priority & TC_PRIO_MAX];
}static int pfifo_fast_enqueue(struct sk_buff *skb, struct Qdisc* qdisc)
{// 确定要把skb放入3个skb队列中的哪一个struct sk_buff_head *list = prio2list(skb, qdisc);// 如果发送队列有剩余空间,则将skb入队列if (skb_queue_len(list) < qdisc->dev->tx_queue_len) {qdisc->q.qlen++;// 除了入队列,__qdisc_enqueue_tail()还会更新统计信息return __qdisc_enqueue_tail(skb, qdisc, list);}// 队列已满,则丢弃return qdisc_drop(skb, qdisc);
}static struct sk_buff *pfifo_fast_dequeue(struct Qdisc* qdisc)
{int prio;struct sk_buff_head *list = qdisc_priv(qdisc);// 从0~2的顺序遍历,找到第一个skb。从遍历顺序来看,显然这种排队规则规定// 0号队列的发送优先级最高、1号次之、2号最低for (prio = 0; prio < PFIFO_FAST_BANDS; prio++) {if (!skb_queue_empty(list + prio)) {qdisc->q.qlen--;return __qdisc_dequeue_head(qdisc, list + prio);}}return NULL;
}static int pfifo_fast_requeue(struct sk_buff *skb, struct Qdisc* qdisc)
{qdisc->q.qlen++;return __qdisc_requeue(skb, qdisc, prio2list(skb, qdisc));
}static int pfifo_fast_init(struct Qdisc *qdisc, struct nlattr *opt)
{int prio;struct sk_buff_head *list = qdisc_priv(qdisc);// 初始化3个私有的skb队列for (prio = 0; prio < PFIFO_FAST_BANDS; prio++)skb_queue_head_init(list + prio);return 0;
}static struct Qdisc_ops pfifo_fast_ops __read_mostly = {.id       =  "pfifo_fast",// 私有数据结构是3个skb队列.priv_size  =  PFIFO_FAST_BANDS * sizeof(struct sk_buff_head),.enqueue =  pfifo_fast_enqueue,.dequeue =  pfifo_fast_dequeue,.peek        =  pfifo_fast_peek,.init       =  pfifo_fast_init,.reset      =  pfifo_fast_reset,.dump      =  pfifo_fast_dump,.owner      =  THIS_MODULE,
};

默认网络设备流量控制相关推荐

  1. Sentinel(五)之流量控制

    转载自  流量控制 概述 流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性 ...

  2. SpringCloud Alibaba Sentinel 流量控制规则介绍与配置

    概述:流量控制(flow control),其原理是sentinel断路器通过监控应用服务调用的QPS或调用并发线程数来实现调用控制.当QPS或线程数达到配置的阈值时,进行响应的服务降级功能,从而到达 ...

  3. 玩转Sentinel流量控制/熔断降级自定义异常和兜底数据返回

    目录 开篇介绍: Sentinel有2种规则: 流控设置面板的介绍: 基于并发线程数进行限流配置验证 流量控制的效果有几种? Sentinel熔断降级规则 服务调用常见的熔断状态和恢复 代码演示服务调 ...

  4. Sentinel流量控制

    1.导学 1.1.软件环境 系统:windows10 开发工具:IntelliJ IDEA.maven JDK:1.8+ 1.2.技术储备要求 spring.springmvc.springboot. ...

  5. sentinel 限流熔断神器详细介绍

    一.限流熔断神器 sentinel 1.什么是 sentinel: 在基于 SpringCloud 构建的微服务体系中,服务间的调用链路会随着系统的演进变得越来越长,这无疑会增加了整个系统的不可靠因素 ...

  6. iptables nat实验_【零基础学云计算】LVS负载均衡群集之NAT模式搭建 (实践篇)...

    实验原理图 实验环境 LVS调度器作为web服务器池的网关 LVS服务器配置两块网卡分别连接内外网 使用轮询(rr)调度算法 LVS负载调度器网段规划 内网33网关:192.168.144.1 外网3 ...

  7. 微服务技术栈:流量整形算法,服务熔断与降级

    本文源码:GitHub·点这里 || GitEE·点这里 一.流量控制 1.基本概念 流量控制的核心作用是限制流出某一网络的某一连接的流量与突发,使这类报文以比较均匀的速度流动发送,达到保护系统相对稳 ...

  8. 1命名规则 sentinel_Sentinel实战:为系统做限流保护

    我们已经知道了 Sentinel 的三大功能:限流 降级 系统保护.现在让我们来了解下具体的使用方法,以限流来演示具体的步骤. 引入依赖 首先肯定是要先引入需要的依赖,如下所示: com.alibab ...

  9. [COURSE_PTHE] 4. 枚举

    1. 简介:枚举(Enumeration) 下面课程会介绍到基于NetBios.TCP/IP.谷歌.LDAP.端口分析工具及其他途径来枚举并揭示网络信息. 2. 框架 该视频会介绍枚举作为渗透测试中的 ...

最新文章

  1. 7-9 用天平找小球 (C语言)
  2. Web 前沿——HTML5 Form Data 对象的使用(转)
  3. 数据结构--二叉查找树 Binary Search Tree
  4. java静态初始化块无法直接调用,关于JAVA静态初始化块,初始化块,构造器调用顺序的有关问题...
  5. 在一台物理服务器上搭建VSAN实验测试
  6. github生成燃尽图
  7. 两个前置摄像头_前后六颗摄像头?vivo V17 Pro率先实现升降式前置双摄
  8. 动态图php打不开,PHP如何判断一个gif图片是否为动态图片
  9. Maple 教程(一)---初认识
  10. python3源码剖析新版_《Python源码剖析》
  11. 六自由度机械手正逆运动学
  12. linux系统自动获取ip地址,Linux系统怎么自动获取ip地址用什么命令
  13. PhotoShop的10大误区
  14. MySQL的两种登录方式
  15. 电子墨水+android+平板,请推荐一款电子墨水屏的安卓平板
  16. TypeScript Property ‘XXX‘ does not exist on type ‘never‘
  17. Linux系统load average异常值处理的trick
  18. Nim 游戏和 SG 函数
  19. 倾斜模型精细化处理_无人机倾斜摄影技术的三维精细模型制作
  20. [Power Query] 快速计算列

热门文章

  1. WPS以及它的两种方式PIN与PBC的理解
  2. 但是生活总不是完美的
  3. win12服务器文件设置只读,高手亲自讲解win10文件夹只读属性改不了的修复办法...
  4. 数学分析 微分中值定理与应用(第6章)
  5. 软件工程应用与实践(1)——项目简介和小组分工
  6. 强大的 mysql管理工具之 Mysql Query Browser
  7. mysql upgrade 报错_mysql_upgrade
  8. Windows运行加速
  9. 左特征向量与右特征向量
  10. 大学毕业4年-未来展望(1)-商业研究