egress队列主要作用于报文出方向,分为两类:无类队列和分类队列。下面分析下源码。

无类别队列

image.png

添加下面这条qdisc时,kernel端代码流程
tc qdisc add dev eth0 root tbf rate 1024kbit limit 1024kbit burst 1024

//linux/net/sched/sck_api.c
static int tc_modify_qdisc(struct sk_buff *skb, struct nlmsghdr *n)struct net_device *dev;struct Qdisc *q;struct tcmsg *tcm;struct nlattr *tca[TCA_MAX + 1];//解析出配置的参数,保存到tca中nlmsg_parse(n, sizeof(*tcm), tca, TCA_MAX, NULL);tcm = nlmsg_data(n);clid = tcm->tcm_parent;dev = __dev_get_by_index(net, tcm->tcm_ifindex);q = dev->qdisc;/* It may be default qdisc, ignore it *///默认的qdisc,handle为0if (q && q->handle == 0)q = NULL;struct netdev_queue *dev_queue;//a. 取出一个 tx queue来保存新创建的qdiscdev_queue = netdev_get_tx_queue(dev, 0);q = qdisc_create(dev, dev_queue, p,tcm->tcm_parent, tcm->tcm_handle,tca, &err);//b. 将新创建的qdisc赋给ingress queueqdisc_graft(dev, p, skb, n, clid, q, NULL);

a. 创建qdisc

static struct Qdisc * qdisc_create(struct net_device *dev, struct netdev_queue *dev_queue, struct Qdisc *p, u32 parent, u32 handle, struct nlattr **tca, int *errp)//kind为tbfstruct nlattr *kind = tca[TCA_KIND];struct Qdisc *sch;struct Qdisc_ops *ops;//根据kind到链表qdisc_base查找是否已经加载,如果没有,则动态加载//对于tbf来说,ops为tbf_qdisc_opsops = qdisc_lookup_ops(kind);//申请qdisc内存sch = qdisc_alloc(dev_queue, ops);//命令行指定了parent为root,所以parent为TC_H_ROOT(0xFFFFFFFFU)sch->parent = parent;//如果没有指定handle,则自动分配一个(范围为[8000-FFFF]:0000)if (handle == 0) {handle = qdisc_alloc_handle(dev);err = -ENOMEM;if (handle == 0)goto err_out3;sch->handle = handle;//如果ops提供了init函数,则调用,对于tbf来说,init为tbf_initops->init(sch, tca[TCA_OPTIONS])//参考下面对于qdisc_list_add的注释,因为parent为root,所以不会执行qdisc_list_add(sch);return sch;

tbf qdisc初始化函数,解析命令行参数,填充私有数据tbf_sched_data

static int tbf_init(struct Qdisc *sch, struct nlattr *opt)
{struct tbf_sched_data *q = qdisc_priv(sch);if (opt == NULL)return -EINVAL;q->t_c = ktime_get_ns();qdisc_watchdog_init(&q->watchdog, sch);q->qdisc = &noop_qdisc;return tbf_change(sch, opt);
}

b. 将新创建的qdisc赋给tx queue

static int qdisc_graft(struct net_device *dev, struct Qdisc *parent,struct sk_buff *skb, struct nlmsghdr *n, u32 classid,struct Qdisc *new, struct Qdisc *old)//如果parent为NULL,说明是第一次替换默认qdiscif (parent == NULL) {num_q = dev->num_tx_queues;for (i = 0; i < num_q; i++) {struct netdev_queue *dev_queue = netdev_get_tx_queue(dev, i);//先将new qdisc赋给 dev_queue->qdisc_sleepingold = dev_graft_qdisc(dev_queue, new);}dev->qdisc = new ? : &noop_qdisc;//如果网卡是up的,才会将dev_queue->qdisc_sleeping赋给dev_queue->qdisc,处理报文时,用的也是dev_queue->qdisc。if (dev->flags & IFF_UP)dev_activate(dev);}else {//替换之前添加的qdiscconst struct Qdisc_class_ops *cops = parent->ops->cl_ops;err = -EOPNOTSUPP;//调用old qdisc的cops->graft嫁接新的qdiscif (cops && cops->graft) {unsigned long cl = cops->get(parent, classid);if (cl) {err = cops->graft(parent, cl, new, &old);cops->put(parent, cl);} elseerr = -ENOENT;}

有类别队列

image.png

a. 添加根队列
tc qdisc add dev eth0 root handle 1: cbq bandwidth 10Mbit avpkt 1000 cell 8 mpu 64

除了ops->init的具体实现,创建qdisc部分和无类别队列基本上一致。对于cbq来说,ops->init函数为cbq_init

tc_modify_qdisc --> qdisc_create -->ops->init(sch, tca[TCA_OPTIONS])static int cbq_init(struct Qdisc *sch, struct nlattr *opt)//cbq_sched_data 是cbq队列的私有数据struct cbq_sched_data *q = qdisc_priv(sch);//clhash是用于存放class的hash链表qdisc_class_hash_init(&q->clhash);//link是struct cbq_class类型的class,为默认值类。q->link.refcnt = 1;q->link.sibling = &q->link;q->link.common.classid = sch->handle; //classid为qdisc的handle值q->link.qdisc = sch;//在默认class上也要创建默认qdisc pfifo_qdisc_opsq->link.q = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, sch->handle);...cbq_link_class(&q->link);//将默认的class link添加到hash链表qdisc_class_hash_insert(&q->clhash, &this->common);

b. 在根队列上添加class
当通过tc命令行添加class时,内核调用tc_ctl_tclass
tc class add dev eth0 parent 1:0 classid 1:1 cbq bandidth 10Mbit

static int tc_ctl_tclass(struct sk_buff *skb, struct nlmsghdr *n)portid = tcm->tcm_parent; //值为1:0clid = tcm->tcm_handle; //值为1:1//根据qid查找qdisc,qid就是handle id,即通过主序列号查找qdisc。//不管添加几个类,parent是父队列还是父类,只会用冒号前面的主序列号来查找qdisc#define TC_H_MAJ_MASK (0xFFFF0000U)#define TC_H_MAJ(h) ((h)&TC_H_MAJ_MASK)qid = TC_H_MAJ(clid);//获取主序列号q = qdisc_lookup(dev, qid);if (!q)return -ENOENT;//获取 cl_ops cbq_class_opscops = q->ops->cl_ops;if (cops == NULL)return -EINVAL;//根据class id查找是不是已经存在,对应cbq来说,就是调用cbq_get函数(调用cbq_class_lookup(q, classid)进行查找)cl = cops->get(q, clid);if (cl == 0) {err = -ENOENT;//如果不存在,但是命令不是添加新class或者没有create flag,则返回错误if (n->nlmsg_type != RTM_NEWTCLASS ||!(n->nlmsg_flags & NLM_F_CREATE))goto out;} else {switch (n->nlmsg_type) {case RTM_NEWTCLASS://如果已经存在,但是flag为NLM_F_EXCL,意思是已存在则返回err = -EEXIST;if (n->nlmsg_flags & NLM_F_EXCL)goto out;break;case RTM_DELTCLASS:err = -EOPNOTSUPP;//删除此classif (cops->delete)err = cops->delete(q, cl);if (err == 0)tclass_notify(net, skb, n, q, cl, RTM_DELTCLASS);goto out;new_cl = cl;err = -EOPNOTSUPP;//如果没有提供change函数,则返回EOPNOTSUPP错误,说明不支持class操作if (cops->change)//调用change函数,对cbq来说就是 cbq_change_classerr = cops->change(q, clid, portid, tca, &new_cl);static int cbq_change_class(struct Qdisc *sch, u32 classid, u32 parentid, struct nlattr **tca, unsigned long *arg)//获取cbq的私有数据struct cbq_sched_data *q = qdisc_priv(sch);//获取默认的classstruct cbq_class *parent;parent = &q->link;//如果指定了parentid,则需要进行查找。//查找失败直接返回if (parentid) {parent = cbq_class_lookup(q, parentid);err = -EINVAL;if (parent == NULL)goto failure;//分配新class结构体cl = kzalloc(sizeof(*cl), GFP_KERNEL);//为class分配默认队列cl->q = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, classid);//保存classidcl->common.classid = classid;//指向父类cl->tparent = parent;//保存根队列cl->qdisc = sch;//将class添加到cbq队列的clhash链表中cbq_link_class(cl);struct cbq_sched_data *q = qdisc_priv(this->qdisc);qdisc_class_hash_insert(&q->clhash, &this->common);

按照上面的分析,可通过如下命令创建一个根队列1:,再在根队列的命令空间1:下创建四个类1:1, 1:2, 1:3, 1:4

#创建根队列cbq,替换默认的根队列pfifo_qdisc_ops
#分配默认类,classid为handle id,即1:, 并且在类上分配默认队列pfifo_qdisc_ops
tc qdisc add dev eth0 root handle 1: cbq bandwidth 10Mbit avpkt 1000 cell 8 mpu 64#添加新类,classid为1:1, 父类id为1:0
#并在新类上添加默认队列pfifo_qdisc_ops
tc class add dev eth0 parent 1:0 classid 1:1 cbq bandidth 10Mbit rate 10Mbit maxburst 20 allot 1514 prio 8 avpkt 1000 cell 8 weight 1Mbit#添加新类,classid为1:2, 父类id为1:1
#并在新类上添加默认队列pfifo_qdisc_ops
tc class add dev eth0 parent 1:1 classid 1:2 cbq bandwidth 10Mbit rate 8Mbit maxburst 20 allot 1514 prio 2 avpkt 1000 cell 8 weight 800Kbit split 1:0 bounded#添加新类,classid为1:3, 父类id为1:1
#并在新类上添加默认队列pfifo_qdisc_ops
tc class add dev eth0 parent 1:1 classid 1:3 cbq bandwidth 10Mbit rate 1Mbit maxburst 20 allot 1514 prio 1 avpkt 1000 cell 8 weight 100Kbit split 1:0#添加新类,classid为1:4, 父类id为1:1
#并在新类上添加默认队列pfifo_qdisc_ops
tc class add dev eth0 parent 1:1 classid 1:4 cbq bandwidth 10 Mbit rate 1Mbit maxburst 20 allot 1514 prio 6 avpkt 1000 cell 8 weight 100Kbit split 1:0至此,根队列上有五个类,即默认类和四个新添加类, 这五个类都会添加到 cbq_sched_data->clhash 链表中,并且每个类都会有默认队列pfifo_qdisc_ops。
默认类1:0是1:1的父类,1:1是1:2,1:3和1:4的父类,并且每个类都有默认队列pfifo_qdisc_opsroot 0xffffffff
|
root qdisc handle = 1:0
|
default class 1:0 -> default qdisc pfifo_qdisc_ops
|
class 1:1 -> default qdisc pfifo_qdisc_ops
|
class 1:2    class 1:3   class 1:4 -> default qdisc pfifo_qdisc_ops

c. 添加filter

应用路由分类器到cbq队列的根,父分类编号为1:0;过滤协议为ip,优先级别为100,过滤器为基于路由表。
tc filter add dev eth0 parent 1:0 protocol ip prio 100 route建立路由映射分类 1:2 , 1:3 , 1:4
tc filter add dev eth0 parent 1:0 protocol ip prio 100 route to 2 flowid 1:2
tc filter add dev eth0 parent 1:0 protocol ip prio 100 route to 3 flowid 1:3
tc filter add dev eth0 parent 1:0 protocol ip prio 100 route to 4 flowid 1:4
#通过命令行tc 添加filter规则时,kernel调用tc_ctl_tfilter
static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n)
//如果没有指定parent,则使用根队列的handle/* Find qdisc */if (!parent) {q = dev->qdisc;parent = q->handle;} else {//根据主序列号查找qdiscq = qdisc_lookup(dev, TC_H_MAJ(t->tcm_parent));if (q == NULL)return -EINVAL;}//获取cl_ops/* Is it classful? */cops = q->ops->cl_ops;if (!cops)return -EINVAL;//如果cl_ops没有提供tcf_chain函数,则说明不支持filter,返回错误EOPNOTSUPP,//对于cbq来说,tcf_chain  就是 cbq_find_tcfif (cops->tcf_chain == NULL)return -EOPNOTSUPP;unsigned long cl = 0;//parent的从序列号不为0,说明指定了class id,否则使用默认类if (TC_H_MIN(parent)) {//对于cbq来说,cbq_get//根据指定的class id,即parent的值查找classcl = cops->get(q, parent);struct cbq_sched_data *q = qdisc_priv(sch);struct cbq_class *cl = cbq_class_lookup(q, classid);//到clhash中查找clc = qdisc_class_find(&q->clhash, classid);if (cl) {cl->refcnt++;return (unsigned long)cl;}return 0;//如果查找失败,说明指定的class不存在,返回错误if (cl == 0)return -ENOENT;//根据 cl 获取 filter链表,对于cbq来说,就是cbq_find_tcf/* And the last stroke */chain = cops->tcf_chain(q, cl);struct cbq_sched_data *q = qdisc_priv(sch);struct cbq_class *cl = (struct cbq_class *)arg;//如果cl为空,则使用默认类q->linkif (cl == NULL)cl = &q->link;//struct tcf_proto __rcu    *filter_list;return &cl->filter_list;//根据protocol和prio查找要添加的filter是否已经在 filter_list 中/* Check the chain for existence of proto-tcf with this priority */for (back = chain; (tp = rtnl_dereference(*back)) != NULL; back = &tp->next) {if (tp->prio >= prio) {if (tp->prio == prio) {if (!nprio || (tp->protocol != protocol && protocol))goto errout;} elsetp = NULL;break;}}//如果第一次添加if (tp == NULL) {tp = kzalloc(sizeof(*tp), GFP_KERNEL);//根据命令行参数 route 查找ops,ops通过     register_tcf_proto_ops 注册tp_ops = tcf_proto_lookup_ops(tca[TCA_KIND]);tp->ops = tp_ops;tp->protocol = protocol;tp->classify = tp_ops->classify;tp->classid = parent;//调用tp_ops的init函数,对于route来说就是route4_init,此函数为空,什么都不做tp_ops->init(tp);else if (tca[TCA_KIND] && nla_strcmp(tca[TCA_KIND], tp->ops->kind))//如果查找到了tp,但是命令行指定的kind和tp中保存的kind不一样,则报错返回。//即上次添加filter时,kind为route,则会将bpf保存到tp->ops->kind,如果这次添加filter//时,kind为u32,则报错goto errout;//对于route来说,ops->get为 route4_get//t->tcm_handle 为命令行指定的 flowidfh = tp->ops->get(tp, t->tcm_handle);struct route4_head *head = rtnl_dereference(tp->root);//如果head为空,说明是第一次添加filterif (!head)return 0;h1 = to_hash(handle);if (h1 > 256)return 0;h2 = from_hash(handle >> 16);if (h2 > 32)return 0;//h1指定hash桶,每个桶中是hash链表//h2指定hash链表的key,指向链表头b = rtnl_dereference(head->table[h1]);if (b) {for (f = rtnl_dereference(b->ht[h2]);f;f = rtnl_dereference(f->next))if (f->handle == handle)return (unsigned long)f;}if (fh == 0) {//没有找到,并且命令行想要删除,则返回报错if (n->nlmsg_type == RTM_DELTFILTER && t->tcm_handle == 0) {goto errout;//没有找到,但是命令行没有指定创建,则返回报错if (n->nlmsg_type != RTM_NEWTFILTER ||!(n->nlmsg_flags & NLM_F_CREATE))goto errout;} else {switch (n->nlmsg_type) {case RTM_NEWTFILTER://命令行指定删除case RTM_DELTFILTER:err = tp->ops->delete(tp, fh);}//添加或者替换filter rule,route4_changetp->ops->change(net, skb, tp, cl, t->tcm_handle, tca, &fh,n->nlmsg_flags & NLM_F_CREATE ? TCA_ACT_NOREPLACE : TCA_ACT_REPLACE);struct route4_head *head = rtnl_dereference(tp->root);if (head == NULL) {head = kzalloc(sizeof(struct route4_head), GFP_KERNEL);rcu_assign_pointer(tp->root, head);//分配 route filter结构体f = kzalloc(sizeof(struct route4_filter), GFP_KERNEL);route4_set_parmsif (tb[TCA_ROUTE4_TO])f->id = to;if (tb[TCA_ROUTE4_CLASSID]) {f->res.classid = nla_get_u32(tb[TCA_ROUTE4_CLASSID]);//最后将f添加到 head指定的表中。//将tp插入链表,安装优先级,优先级值越大越靠后RCU_INIT_POINTER(tp->next, rtnl_dereference(*back));rcu_assign_pointer(*back, tp);
  1. 添加route
    该路由是与前面所建立的路由映射一一对应。
1)发往主机192.168.1.24的数据包通过分类2转发(分类2的速率8Mbit)ip route add 192.168.1.24 dev eth0 via 192.168.1.66 realm 2
2)发往主机192.168.1.30的数据包通过分类3转发(分类3的速率1Mbit)ip route add 192.168.1.30 dev eth0 via 192.168.1.66 realm 3
3)发往子网192.168.1.0/24 的数据包通过分类4转发(分类4的速率1Mbit)ip route add 192.168.1.0/24 dev eth0 via 192.168.1.66 realm 4

添加realms代码流程

#通过ip route add 添加realms
rtnl_register(PF_INET, RTM_NEWROUTE, inet_rtm_newroute, NULL, NULL);
static int inet_rtm_newroute(struct sk_buff *skb, struct nlmsghdr *nlh)rtm_to_fib_config(net, skb, nlh, &cfg);case RTA_FLOW:cfg->fc_flow = nla_get_u32(attr);break;fib_table_insert(tb, &cfg);fib_create_info(cfg);nh->nh_tclassid = cfg->fc_flow;#查找路由时,将 nh_tclassid 赋给 rt->dst.tclassid
__mkroute_input
__mkroute_outputrt_set_nexthop(rth, fl4->daddr, res, fnhe, fi, type, 0);rt->dst.tclassid = nh->nh_tclassid;

出方向过滤流程

前面流程已经添加了规则,数据包发送时,如何匹配?
下面已有类队列cbq为例,分析enqueue和dequeue的操作。

int dev_queue_xmit(struct sk_buff *skb)__dev_queue_xmit(skb, NULL);q = rcu_dereference_bh(txq->qdisc);//提供了enqueue函数的qdisc,比如cbqif (q->enqueue) {//报文入队或者直接发送__dev_xmit_skb(skb, q, dev, txq);goto out;}//lo,macvlan等虚拟接口默认qdisc为noqueue,没提供enqueue函数,会在此处直接发送到网卡if (dev->flags & IFF_UP) {//对数据包做校验,比如添加vlan等skb = validate_xmit_skb(skb, dev);//调用网卡驱动的函数发送报文skb = dev_hard_start_xmit(skb, dev, txq, &rc);//qdisc提供enqueue函数的流程
//走到这个函数也不一定走enqueue流程,也有可能直接发送报文
static inline int __dev_xmit_skb(struct sk_buff *skb, struct Qdisc *q,struct net_device *dev,struct netdev_queue *txq)
{//qdisc的状态为__QDISC_STATE_DEACTIVATED时,可能是因为网卡down了,这个时候直接drop报文if (unlikely(test_bit(__QDISC_STATE_DEACTIVATED, &q->state))) {kfree_skb(skb);rc = NET_XMIT_DROP;} else if ((q->flags & TCQ_F_CAN_BYPASS) && !qdisc_qlen(q) &&qdisc_run_begin(q)) {//满足上面三个条件会走到这个流程//a. qdisc支持TCQ_F_CAN_BYPASS,即qdisc允许bypass qdisc的处理,大部分的qdisc都不支持,必须走dqisc流程,//对报文进行处理后才能发出去。//b. qdisc缓存报文的队列长度为0//c. qdisc之前没有running,本次将qdisc设置为running/** This is a work-conserving queue; there are no old skbs* waiting to be sent out; and the qdisc is not running -* xmit the skb directly.*/qdisc_bstats_update(q, skb);//sch_direct_xmit 返回值大于0,说明还有报文要发送,调用__qdisc_run继续发送if (sch_direct_xmit(skb, q, dev, txq, root_lock, true)) {if (unlikely(contended)) {spin_unlock(&q->busylock);contended = false;}__qdisc_run(q);} else//报文发送完毕,清除标志位 __QDISC___STATE_RUNNING//qdisc->__state &= ~__QDISC___STATE_RUNNING;qdisc_run_end(q);rc = NET_XMIT_SUCCESS;} else {//对于不支持bypass tx处理的qdisc来说,会在此处将报文入队//对于cbq来说,q->enqueue指向 cbq_enqueuerc = q->enqueue(skb, q) & NET_XMIT_MASK;if (qdisc_run_begin(q)) {if (unlikely(contended)) {spin_unlock(&q->busylock);contended = false;}__qdisc_run(q);}}
}//调用dequeue循环从队列取包发送。
//但是不能一直发送,满足下面两个条件之一就会停止发送,并启动tx软中断,在软中断中会继续发送报文
//a. 发送数据包个数超过了quota,即weight_p(默认64)
//b. 有其他进程需要占用cpu,即被其他进程抢占了cpu
void __qdisc_run(struct Qdisc *q)
{int quota = weight_p;int packets;while (qdisc_restart(q, &packets)) {/** Ordered by possible occurrence: Postpone processing if* 1. we've exceeded packet quota* 2. another process needs the CPU;*/quota -= packets;if (quota <= 0 || need_resched()) {__netif_schedule(q);//使能发送软中断,会在软中断处理函数 net_tx_action 中继续发送报文if (!test_and_set_bit(__QDISC_STATE_SCHED, &q->state))__netif_reschedule(q);sd = this_cpu_ptr(&softnet_data);q->next_sched = NULL;*sd->output_queue_tailp = q;sd->output_queue_tailp = &q->next_sched;raise_softirq_irqoff(NET_TX_SOFTIRQ);break;}}qdisc_run_end(q);
}
static inline int qdisc_restart(struct Qdisc *q, int *packets)
{struct netdev_queue *txq;struct net_device *dev;spinlock_t *root_lock;struct sk_buff *skb;bool validate;/* Dequeue packet */skb = dequeue_skb(q, &validate, packets);//调用qdisc的dequeue从队列中取出报文,对于cbq来说,就是 cbq_dequeueskb = q->dequeue(q);//skb为空,说明队列已经没有报文,返回0if (unlikely(!skb))return 0;root_lock = qdisc_lock(q);dev = qdisc_dev(q);txq = skb_get_tx_queue(dev, skb);//发送报文return sch_direct_xmit(skb, q, dev, txq, root_lock, validate);
}//报文入队
cbq_enqueue(struct sk_buff *skb, struct Qdisc *sch)//找到合适的 classstruct cbq_class *cl = cbq_classify(skb, sch, &ret);struct cbq_sched_data *q = qdisc_priv(sch);//取出存放class的链表struct cbq_class *head = &q->link;u32 prio = skb->priority;/**  Step 1. If skb->priority points to one of our classes, use it.*///首先根据skb的priority作为classid查找类//用户程序可以通过套接字选项SO_PRIORITY在skb->priority设置一个类的classid//TC_H_MAJ(prio ^ sch->handle) == 0 这个是判断classid是否是这个qdisc下的,因为classid的高16位代表这个类//所在的qdisc,应该和handle的高16为相同。if (TC_H_MAJ(prio ^ sch->handle) == 0 &&(cl = cbq_class_lookup(q, prio)) != NULL)return cl;//遍历所有类的所有filter,找到合适的类并返回for (;;) {//取出类head的过滤链表fl = rcu_dereference_bh(head->filter_list);//遍历过滤链表进行匹配result = tc_classify_compat(skb, fl, &res);//取出classidcl = (void *)res.class;if (cl->level == 0)return cl;//如果没有找到合适的类,则使用默认类if (TC_H_MAJ(prio) == 0 &&!(cl = head->defaults[prio & TC_PRIO_MAX]) &&!(cl = head->defaults[TC_PRIO_BESTEFFORT]))return head;//将skb放入cl类的队列中,cl->q 默认为 pfifo_qdisc_opsret = qdisc_enqueue(skb, cl->q);//sch->enqueue指向 pfifo_enqueuereturn sch->enqueue(skb, sch);if (ret == NET_XMIT_SUCCESS) {sch->q.qlen++;cbq_mark_toplevel(q, cl);if (!cl->next_alive)//将cl保存到q->active,并设置q->activemask,这样在dequeue时,才知道哪个类上有数据cbq_activate_class(cl);return ret;}//报文出队
static struct sk_buff *cbq_dequeue(struct Qdisc *sch)for (;;) {q->wd_expires = 0;skb = cbq_dequeue_1(sch);struct cbq_sched_data *q = qdisc_priv(sch);struct sk_buff *skb;unsigned int activemask;//如果activemask不为0,说明类上有报文需要发送activemask = q->activemask & 0xFF;while (activemask) {int prio = ffz(~activemask);activemask &= ~(1<<prio);//取出类,调用类的cl->q->dequeue将报文出队,并返回skb = cbq_dequeue_prio(sch, prio);skb = cl->q->dequeue(cl->q);if (skb)return skb;}if (skb) {qdisc_bstats_update(sch, skb);sch->q.qlen--;qdisc_unthrottled(sch);return skb;}}

参考

https://blog.csdn.net/wdscq1234/article/details/51926808

也可参考:traffic control 之 egress 队列 - 简书 (jianshu.com)

traffic control 之 egress 队列相关推荐

  1. Linux 操作系统原理 — Traffic Control 流量控制与 IP QoS 技术解析

    目录 文章目录 目录 Traffic Control Traffic Control 的基本实现原理 流量处理的三个层面 流量处理的实现模型 流量队列的常见类型 FIFO 队列 PFIFO_FAST ...

  2. Linux Kernel TCP/IP Stack — L2 Layer — Traffic Control(流量控制)的实现原理

    目录 文章目录 目录 基本概念 QoS.Bandwidth 和 Traffic Control 队列 FIFO 队列 pfifo_fast 队列 SFQ 队列 令牌桶队列 数据流(Data Flow) ...

  3. Linux流量控制指南 (Traffic control HOWTO)

    流量控制指南 版本 1.0.2 Martin A. Brown 原文地址:http://www.tldp.org/HOWTO/html_single/Traffic-Control-HOWTO/ 翻译 ...

  4. Linux Kernel TCP/IP Stack — L2 Layer — Traffic Control(流量控制)

    目录 文章目录 目录 tc CLI - Linux 流量控制工具 TC 的基本原理 TC 的组件 Qdisc 无类别队列规定(Classless Qdiscs) 分类队列规定(Classful Qdi ...

  5. Linux TC(Traffic Control)框架原理解析

    近日的工作多多少少和Linux的流控有点关系.自打几年前知道有TC这么一个玩意儿而且多多少少理解了它的原理之后,我就没有再动过它,由于我不喜欢TC命令行,实在是太繁琐了.iptables命令行也比較繁 ...

  6. 【网络】inux流量控制器TC(Traffic Control)

    目录 实施 实施步骤概览 实施步骤 原理 经验之谈: 实施 原文:https://blog.csdn.net/tycoon1988/article/details/40832325 实施步骤概览 在L ...

  7. 【测试】linux tc命令|Linux模拟网络延迟、丢包等|traffic control(流量控制)

    目录 一.工具介绍 1.netem 2.tc 二.命令使用说明 1.模拟延迟传输 2.模拟网络丢包: 3.模拟包重复: 4.模拟数据包损坏: 5.模拟数据包乱序: 6.删除模拟配置: 更多高级用法 一 ...

  8. Traffic Control

    Qos/DSCP TC 在Linux Kernel中完成Traffic Control功能主要包含以下几个组成部分: Qdiscs (queuing disciplines): 排队规则, 该obje ...

  9. 利用traffic control模拟网络延迟和丢包

    1. 模拟延迟和丢包语句介绍 # 设置延迟语句: 从 eno1.enp7s0f0 网口出去的包将延迟20ms tc qdisc add dev eno1 root netem delay 20ms t ...

最新文章

  1. (linux中alias的用法)给一些常用的长命令取别名
  2. 用Microwindows(Nano-X)编写“hello world”
  3. codeforces1498 D. Bananas in a Microwave(背包+优化)
  4. 驱动级的自动按键_空调遥控器特殊按键使用方法及注意事项
  5. 38线性映射05——代数与代数同构
  6. qq批量登录软件_20191228分享,雪藏了几天的软件合集分享,心痛一小编,开心一大家。...
  7. 彻底解决SP2下ALEXA工具条无法显示(转)
  8. FPS游戏方框透视基本原理
  9. mysql 按照姓氏排序
  10. 2-2.基金的投资交易与结算
  11. 人工智能算法(一)进化算法
  12. flyaway mysql_golang通用连接池,支持GRPC,RPC,TCP
  13. 计算机ppt音乐,ppt背景音乐_适合ppt播放的轻音乐
  14. php stringimplode,PHP之string之implode()函数使用
  15. 基于MATLAB的图像融合设计
  16. 计算机网络第七版 谢希仁 3-33答案
  17. 拼多多商家一件代发,一键打单有什么软件?
  18. 【机器学习】缺失值的处理方法总结
  19. 【数据结构】折半查找法
  20. VUE中IE浏览器下载文件的解决方案

热门文章

  1. 【★】生成树算法终极解析!
  2. VBA-循环语句之Do...Loop
  3. 用蚕茧表示法写简洁实用的接口文档
  4. 运用Python完成五角星随机颜色的绘制
  5. QtCreator-----Kits选项选择
  6. 实践▍用大数据扒一扒蔡徐坤的真假流量粉 | Alfred数据室
  7. 普元框架-那些年一起走过的坑
  8. div 自定义拉宽_纯Css实现Div高度根据自适应宽度(百分比)调整
  9. Sentinel流量防控卫兵
  10. UnityShader 简单护盾效果