Packet Fair Queuing Scheduler
前面介绍的几种简单调度算法,都没有很好的考虑调度的公平性。比如在调度决策时没有考虑实际要发送的这个pkt的长度,如果在一个低优先级上一个长度很长的pkt,将会占用输出端口相对很长的时间,可能造成高优先级的pkt等待时间过长。对于不同优先级的流调度,最理想的情况就是将各个流都想象成为流体的形式,无论何时整个输出管道都可以同时为所有的输入流进行服务,不过不同优先级对输出带宽的占用比例不同,这种理想的调度一般成为Generalized
Processor Sharing(GPS)。如下图所示:
上图可以看到,在基于流的模式下可以同时对多个pkt进行发送,而实际在于我们基于pkt的网络中这是不可能实现的,但是这种调度方式代表了一个理想的公平调度,可作为现实比较各种调度算法的标准,也可以给实际的调度算法的研究很多启发。
在上面的基于流的调度算法,在任意时刻t,queue_i收到的调度服务比例为:g_i(t) = r * w_i /
sum(w_j, for j in
B(t)),其中r为整个输出带宽,w_i是queue_i的weight,B(t)是在时间t所有的active
queue。由于GPS无法用于pkt调度,因此只能利用模拟的方法,来使实际的调度算法来逼近GPS的性能。
在实际中,通过在系统中引入一个虚拟系统时间V(t)的概念,来模拟GPS中的流的概念。通常利用V(t)
模拟当采用GPS调度时,每个队列第一个pkt应当离开系统的时间(finish time,
FT),然后按照调度时按照pkt的FT进行调度,这种调度策略一般称为Smallest virtual Finish time
First
(SFF)。因此对于调度器的设计转变为对V(t)函数的选择和对FT的计算方法的选择。首先我们考虑一个相对简单的情况,就是所有队列都有很多pkt在排队的情况,即不存在空的队列的情况。这种情况下,一个队首pkt的FT,在GPS调度的情况下可以计算为:F(i,
k) = F(i, k-1) + L(i, k) / Ri,其中F(i, k)为queue_i的第k的pkt的理想结束时间,L(i,
k)为这个pkt的长度,Ri是queue_i在总带宽中分配的带宽。因此queue_i中第k个pkt的结束时间就是第k-1个pkt的结束时间再加上利用queue_i的带宽发送第k个pkt的时间,与GPS中流的概念完全符合。但是,实际情况中queue总是有空的情况,这种情况下此queue中新来的pkt的FT如何进行计算就需要借助V(t)函数,V(t)是系统虚拟时间,函数必须满足时间的性质,即加入t1
> t2,那么V(t1) > V(t2)。此时对于F(i,
k)的计算方式为:F(i, k) = max(V(a_k), F(i, k-1) )+ L(i, k) /
Ri,其中a_k是队列queue_i中第k个pkt的到达时间,V(a_k)是其到达的系统虚拟时间,因此对于队列为空时,pkt的FT就是其到达时间再加上其发送时间,也符合GPS中流的概念。因此我们利用FT进行调度的方法应该可以完成对GPS算法的模拟,具体算法的调度公平性将很大取决于我们对V(t)函数的选择。
Deficit Round-Robin
DRR也是基于RR的一个调度算法,但是这个算法考虑了pkt
length。对于DRR,每个queue都有一个deficit,而且queue的deficit值在每个单位时间都会增加,但是每发送一个pkt将会消耗此pkt
length的deficit,而只有一个queue的deficit值大于发送queue
head的那个pkt所需要的deficit时,此queue才会成为active queue,而一个active
queue在发送完一个pkt后,造成当前的deficit不够完成后续pkt的发送时将会不再是active
queue;DRR的调度就是对当前所有的active queue进行RR调度。具体如下图所示:
Weighted Fair Queuing (WFQ)
WFQ是一种基于SFF策略进行调度的算法。他定义其V(t)为
(1)V(0)=
0,
(2)V(t + dt) = V(t) +
dt * r / sum(r_i, for i in B(t, t+dt)),
其中r为整个输出带宽,r_i为queue_i的输出带宽,B(t, t+dt)为[t, t+dt]时间内active
queue的集合。
这个算法可以解释为,比如对于queue_i,第k个pkt的到达时间为t,第k+1个pkt的到达时间为(t+dt),那个k+1的pkt的虚拟到达时间就是V(t)再加上dt乘上一个倍数,这个倍数就是在dt时间内所有active
queue的带宽和占总带宽的比例的倒数,如此计算是因为当active
queue的带宽和小于总带宽的时候,就相当于各个queue受到的服务是多于其实际应有的比例的,因此也可以认为是系统的时间加快了,所以active
queue在dt时间内获得了这个倍数比例的服务。
具体FT的计算和前面提到的方法相同,F(i,
k) = max(V(a_k), F(i, k-1) )+ L(i, k) / Ri
WFQ是一种经典的packet fair
queuing的调度算法,下面我们分析一下他的性能。一个调度算法性能主要从两个方面进行分析,计算复杂度和delay
bound。首先对计算复杂度进行分析,通过上面的介绍,我们可以看出WFQ对每个pkt的FT的计算复杂度是O(1),其中需要维护系统中所有active
queue的sum(r_i),这个采用增量更新的方式O(1)实现,并且保存每个queue最后一个pkt的FT,以及整个调度的最后一个pkt的VT,因此利用上面的结果O(1)可以完成对FT的计算。WFQ调度采用SFF策略,选择所有pkt中FT最小的pkt进行调度,由于每个class的queue都是FIFO,即对一个class当前队列中所有pkt的FT是单调递增的,因此也就是对所有queue的HOL
pkt中选择最小的FT进行调度,这个操作的复杂度是O(logN)。因此WFQ的复杂度就是O(logN)。
对于scheduler的delay
bound的定义是其调度时间相对于GPS调度时间的最大延迟时间:一个pkt在t1时间入队,如果采用GPS调度在t2时间被调度到,而实际scheduler在t2’时间才调度到,这个scheduler的delay
bound就是max(t2’ – t2)。一个scheduler的delay
bound越好,说明其对GPS的近似越好,因此公平性也越好。已有证明,WFQ的delay bound为N/2 * Lmax /
r,其中N为queue的数目,Lmax为最大包长,r为链路发送速率。也就是说WFQ的delay
bound与queue数目呈线性关系,这也是WFQ调度的一个主要缺陷,为此提出了WF2Q的调度方式,可以实现delay
bound与queue数目无关,而只由Lmax和r决定。
接下来将会对Linux中QoS实现进行简单介绍。
QoS in
Linux
前面介绍了QoS的各种参数,实现QoS的各种算法,下面结合Linux介绍一个系统如何QoS(当然,Linux系统和实际网络设备中运行的系统还是有很大差距,但是对于QoS的基本思想是一致的)。下面首先介绍一下Linux中queuing的软件架构,然后结合几个QoS算法介绍如何在Linux中实现自己的QoS算法。
Packet
queuing in Linux
Linux中QoS的软件架构如下图所示:
从上图可以看出整个QoS的管理,从enqueue到dequeue,主要由三种元素连接而成,有Qdisc,Class和Filter。其中,Qdisc主要负责pkt队列的管理,Filter完成对pkt的分类,决定一个enqueue的pkt应该入队到哪个队列,class就是Filter分类后的结果,并对应有一组QoS参数,即这个class的CIR,PIR等等;dequeue时将由调度算法判断应该从哪个class对应的Qdisc中dequeue一个pkt发送。因此,一个pkt的从enqueue到dequeue的流程就是,首先Linux的QoS是针对一个网络设备的,每个网络设备本身有一个根Qdisc,在对此Qdisc
enqueue的时候,首先需要对pkt进行分类,利用挂接在Qdisc下的filter链表分类到具体的class的Qdisc中;dequeue一般发生在发送DMA完成或发现DMA空闲但Qdisc中不为空时,此时将从根Qdisc中dequeue,然后调用到此设备所指定的调度算法完成对各个queue的dequeue工作。
从代码执行流程上看,如下图所示:
在Linux中实现一个调度
在Linux中,针对QoS有一个良好的框架,并对Qdisc,Class和Filter都有良好的接口定义。要在Linux中添加一个自己的调度算法需要时自己的算法按照已定义的接口实现,这样就可以无缝地加入到Linux中,并可以利用现成的tc工具进行配置。对于Qdisc定义如下:
struct Qdisc
{
int (*enqueue)(struct
sk_buff *skb, struct Qdisc *dev);
struct sk_buff *(*dequeue)(struct
Qdisc *dev);
unsigned flags;
int padded;
struct Qdisc_ops *ops;
struct qdisc_size_table
*stab;
struct list_head
list;
u32 handle;
u32 parent;
atomic_t refcnt;
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;
struct netdev_queue
*dev_queue;
struct Qdisc *next_sched;
struct sk_buff *gso_skb;
unsigned long state;
struct sk_buff_head
q;
struct gnet_stats_basic_packed
bstats;
struct gnet_stats_queue
qstats;
}
|
其中对于实现QoS算法最重要的struct
Qdisc_ops定义如下:
struct Qdisc_ops
{
struct Qdisc_ops *next;
const struct Qdisc_class_ops
*cl_ops;
char id[IFNAMSIZ];
int priv_size;
int (*enqueue)(struct
sk_buff *, struct Qdisc *);
struct sk_buff * (*dequeue)(struct
Qdisc *);
struct sk_buff * (*peek)(struct
Qdisc *);
unsigned int (*drop)(struct
Qdisc *);
int (*init)(struct
Qdisc *, struct nlattr *arg);
void (*reset)(struct
Qdisc *);
void (*destroy)(struct
Qdisc *);
int (*change)(struct
Qdisc *, struct nlattr *arg);
…
struct module *owner;
};
|
在此结构中,enqueue和dequeue两个函数是整个QoS调度的入口函数。其中的Qdisc_class_ops用于对此Qdisc的filter
list进行操作,添加删除等,通过对Qdisc添加fliter,而filter对enqueue到此Qdisc的pkt进行分类,从而归类到此Qdisc的子class中,而每个子class都有自己的Qdisc进行pkt
queue的管理,因此实现一个树形的filter结构,当然可以多个filter将pkt分到同一个子class中,但对于根Qdisc和其下的子class的Qdisc来说还是一个树形的分层结构。
Qdisc_class_ops的定义如下:
struct Qdisc_class_ops
{
/* Child qdisc manipulation
*/
int (*graft)(struct
Qdisc *, unsigned long cl,
struct Qdisc *,
struct Qdisc **);
struct Qdisc * (*leaf)(struct
Qdisc *, unsigned long cl);
void (*qlen_notify)(struct
Qdisc *, unsigned long);
/* Class manipulation routines
*/
unsigned long (*get)(struct
Qdisc *, u32 classid);
void (*put)(struct
Qdisc *, unsigned long);
int (*change)(struct
Qdisc *, u32,
u32, struct nlattr **,
unsigned long *);
int (*delete)(struct
Qdisc *, unsigned long);
void (*walk)(struct
Qdisc *, struct qdisc_walker * arg);
/* Filter manipulation
*/
struct tcf_proto **
(*tcf_chain)(struct
Qdisc *, unsigned long);
unsigned long (*bind_tcf)(struct
Qdisc *, unsigned long, u32 classid);
void (*unbind_tcf)(struct
Qdisc *, unsigned long);
。。。
};
|
对于filter由结构体tcf_proto定义,其函数tcf_proto_ops为:
struct tcf_proto_ops
{
struct tcf_proto_ops
*next;
char kind[IFNAMSIZ];
int (*classify)(struct
sk_buff*, struct tcf_proto*,
struct tcf_result *);
int (*init)(struct
tcf_proto*);
void (*destroy)(struct
tcf_proto*);
unsigned long (*get)(struct
tcf_proto*, u32 handle);
void (*put)(struct
tcf_proto*, unsigned long);
int (*change)(struct
tcf_proto*, unsigned long,
u32 handle, struct nlattr **,unsigned long *);
int (*delete)(struct
tcf_proto*, unsigned long);
void (*walk)(struct
tcf_proto*, struct tcf_walker *arg);
…
}
|
要在Linux中实现一个QoS算法,其入口函数就是struct Qdisc_ops,并通过register_qdisc
将自己的实现注册到系统中即可。
通过TC可以将QoS应用到网络设备上,命令如下:
> tc qdisc add dev eth1 root handle 1: htb
> tc class add dev eth1 parent 1:0 classid 1:10 htb rate 100Mbit
> tc class add dev eth1 parent 1:10 classid 1:20 htb rate 1Mbit
> tc filter add dev eth1 protocol ip parent 1:0 prio 5 u32 match ipdst 192.168.1.200 flowid 1:20
|
以上命令,首先为eth1定义root qdisc,handle为1:0,其对应class带宽为100Mbps,class
ID为1:10,然后在此class下定义子class带宽为1Mbps,class
ID为1:20,并为此子class定义filter,filter定义为目标ip地址192.168.1.200
下面介绍DRR和HTB在Linux中的实现.
DRR在Linux中的实现
前面介绍了DRR算法,这里我们看看如何在Linux中实现DRR。从上面DRR介绍中,我们知道,对于DRR整个调度算法有一个active_queue,每个class都有一个deficit和weight。Linux中的DRR算法实现于net/sched/sch_drr.c,从中可以发现定义如下:
struct drr_sched {
struct list_head
active;
struct tcf_proto *filter_list;
struct Qdisc_class_hash
clhash;
};
struct drr_class {
struct Qdisc_class_common
common;
unsigned int refcnt;
unsigned int
filter_cnt;
struct gnet_stats_basic_packed
bstats;
struct gnet_stats_queue
qstats;
struct gnet_stats_rate_est
rate_est;
struct list_head
alist;
struct Qdisc *qdisc;
u32 quantum;
u32 deficit;
};
|
在这里filter_list用于保存挂在这个Qdisc下面的filter,用于对子class进行进一步分类。每个class的weight用quantum表示,即每次class的deficit用完后一次添加的数目。
其enqueue函数如下(有删节):
static int drr_enqueue(struct
sk_buff *skb, struct Qdisc *sch)
{
struct drr_sched *q =
qdisc_priv(sch);
struct drr_class *cl;
cl = drr_classify(skb,
sch,
&err);
/* 首先进行利用q->filter_list进行分类
*/
len = qdisc_pkt_len(skb);
err = qdisc_enqueue(skb,
cl->qdisc);
/* enqueue到子class的Qdisc中
*/
if (unlikely(err !=
NET_XMIT_SUCCESS)) {
。。。
return err;
}
if (cl->qdisc->q.qlen ==
1) { /* class qdisc原来为空 */
list_add_tail(&cl->alist, &q->active);
/* 添加到active queue中
*/
cl->deficit = cl->quantum; /*
同时更新deficit到最大 = quantum
*/
}
/* 更新统计信息 */
cl->bstats.packets++;
cl->bstats.bytes +=
len;
sch->bstats.packets++;
sch->bstats.bytes +=
len;
sch->q.qlen++;
return err;
}
|
接下来看dequeue的实现
(有删节):
static struct sk_buff *drr_dequeue(struct
Qdisc *sch)
{
struct drr_sched *q =
qdisc_priv(sch);
struct drr_class *cl;
while (1) {
/* 在此循环中对active queue进行RR调度
*/
cl = list_first_entry(&q->active, struct drr_class, alist);
/* 取出第一个class */
skb = cl->qdisc->ops->peek(cl->qdisc);
if (skb ==
NULL) /* 没有pkt,退出 */
goto out;
len = qdisc_pkt_len(skb);
if (len <= cl->deficit) { /*
一个可调度的class */
cl->deficit -=
len; /* 更新class的deficit */
skb =
qdisc_dequeue_peeked(cl->qdisc);
if (cl->qdisc->q.qlen ==
0) /* 此class调度后为空,移出active queue */
list_del(&cl->alist);
sch->q.qlen--;
return skb; /* 返回这个pkt */
}
cl->deficit +=
cl->quantum; /*
class不可调度,顺便更新其下一轮的deficit */
list_move_tail(&cl->alist, &q->active);
/* class移到active queue末尾,等待下一轮
*/
}
out:
return NULL;
}
|
HTB在Linux中的实现
HTB(Hierarchical Token
Bucket)是一种层次化的调度方式,调度器的结构如下图所示,树形结构中的每个一个节点都是一个class,由根节点通过filter将pkt分类到叶子节点中,每个pkt的enqueue都是enqueue到叶子节点的Qdisc中。对树中每个class都有自己的CIR和PIR,因此节点随着自身所余deficit的不同可能处于GREEN,
YELLOW和RED三种状态,GREEN就是rate小于CIR,YELLOW就是rate已经大于CIR,但是还小于PIR,此时此节点将通过向其父节点借用deficit来获得调度,RED就是rate已经大于PIR,此时class将被置于waiting
queue中等待deficit的更新才可能从RED变为可调度的状态。
HTB的调度策略是按照低level优先,高优先级优先的原则,由于叶子节点都位于level 0,所有在level
0上可调度的class都必然是GREEN状态,应当优先调度,然后在同一level上,选择优先级最高的进行调度。对于非叶子节点,只有当其有子节点向其借用deficit的时候才会被调度,因此可能有多个不同的priority的子节点同时向其借用deficit,因此非叶子节点可能有多个priority,但调度仍然以高priority为准,并在level的调度中也严格按照priority优先的形式。
通过上面说明,实际上HTB的enqueue和dequeue都十分简单。Enqueue就是将pkt分类到叶子节点,然后enqueue到叶子节点的Qdisc中,如果此叶子节点之前为idle状态,则activate之。Dequeue就是选择现有可调度的最低level,最高priority的class进行调度。如果是同时存在多个相同level,priority的情况,则按照DRR进行调度。
下面对Linux中HTB的实现详细演示。
在图中,我们可以看到一共有三个level,每个level有两个优先级,分别为红色和蓝色,其中红色为高优先级。5个class,其中C,D,E为叶子节点,A,B为非叶子节点,即pkt只能enqueue到CDE上。对于每个level都有三个队列,分别对应两个不同priority和waiting
queue(白色),当此level中class有pkt在Qdisc中时,对于非叶子节点来说是有子class向其借用deficit时,即一个class需要被调度时,这个class将被enqueue到其所在level的对应队列中。如图1,所有class都没有pkt在Qdisc中,所有level的队列都没有class。图2中,两个class,D,E都有pkt,其中C为BLUE,D为RED。因此调度时,就是选择level0,搞priority的D进行调度。
由于D的priority较高,可以一直占用,如图3所示,随着他的rate超出CIR,将不得不向B借用deficit,此时D将处于YELLOW,被放到level
0的waiting queue中,并且在B的借用队列中排队,由于D本身为RED,因此B也将在level 1的RED
queue中。此时调度时,将选择C进行调度,因为C的level为0。如图4所示,C的rate随着调度超过了其PIR,此时C将处于RED,无法向其父节点借用deficit,因此只能放到level
0的waiting
queue中等待deficit的更新。此时调度将只能调度B,从而继续调度D中的pkt,但是如果也超出了B的CIR,那么B也将处于YELLOW的状态,从而被enqueue到level
1的waiting queue中,并且向其父节点A开始借用deficit,此时A将被activate,从而被放到level
2的可调度队列中,优先级仍为RED。因此在图4中,BCD都在各自level的waiting
queue中,但是BD都未超出其PIR,可以向其父节点借用deficit,因此仍然会得到调度。
但是不幸,A很快超出其PIR了,此时A将处于不可调度的状态,从而被加入到level 2的等待队列中。并且此时class
E被enqueue新的pkt,从而被activate,处于GREEN,因此被加入到level
0的可调度队列中。因此图5中调度将会对E进行调度。
图6演示了非叶子节点会有多个priority的情况,BCDE都是YELLOW,向A借用deficit,此时A有RED和BLUE两个priority,调度时将首先对A的RED的B进行调度,B对RED的D进行调度,因此首先是对D进行调度。D的Qdisc为空后,A将会按照DRR对BC进行调度,如果调度到B将间接调度到E。
整个HTB的工作流程如上所述。下面对Linux中的实现进行简单介绍。首先看htb的class的定义:
struct htb_class
{
struct
Qdisc_class_common common;
/* general class
parameters */
struct
gnet_stats_basic_packed
bstats;
struct
gnet_stats_queue qstats;
struct
gnet_stats_rate_est rate_est;
struct
tc_htb_xstats xstats; /* our special
stats */
int
refcnt; /* usage count
of
this
class */
/* topology
*/
int
level; /* our level
(see
above) */
unsigned int children;
struct
htb_class *parent; /* parent class
*/
int
prio; /* these two are used
only by leaves... */
int
quantum; /* but stored
for
parent-to-leaf return
*/
union
{
struct
htb_class_leaf {
struct
Qdisc *q;
int
deficit[TC_HTB_MAXDEPTH];
struct
list_head
drop_list;
}
leaf;
struct
htb_class_inner {
struct
rb_root
feed[TC_HTB_NUMPRIO]; /* feed trees
*/
struct
rb_node *ptr[TC_HTB_NUMPRIO]; /* current class
ptr
*/
u32
last_ptr_id[TC_HTB_NUMPRIO];
}
inner;
}
un;
struct rb_node
node[TC_HTB_NUMPRIO];
struct rb_node
pq_node;
psched_time_t
pq_key;
int
prio_activity;
enum htb_cmode
cmode;
struct tcf_proto
*filter_list;
int
filter_cnt;
struct qdisc_rate_table
*rate;
struct qdisc_rate_table
*ceil;
long buffer,
cbuffer;
psched_tdiff_t
mbuffer;
long tokens,
ctokens;
psched_time_t
t_c;
};
|
还有htb_sched,负责管理htb整体的调度
struct htb_sched {
struct Qdisc_class_hash
clhash;
struct list_head
drops[TC_HTB_NUMPRIO];/*
active leaves (for drops) */
struct rb_root
row[TC_HTB_MAXDEPTH][TC_HTB_NUMPRIO];
int
row_mask[TC_HTB_MAXDEPTH];
struct rb_node
*ptr[TC_HTB_MAXDEPTH][TC_HTB_NUMPRIO];
u32
last_ptr_id[TC_HTB_MAXDEPTH][TC_HTB_NUMPRIO];
struct rb_root
wait_pq[TC_HTB_MAXDEPTH];
psched_time_t
near_ev_cache[TC_HTB_MAXDEPTH];
int defcls;
struct tcf_proto
*filter_list;
int
rate2quantum;
psched_time_t
now;
struct qdisc_watchdog
watchdog;
struct sk_buff_head
direct_queue;
int
direct_qlen;
long
direct_pkts;
#define HTB_WARN_TOOMANYEVENTS
0x1
unsigned int
warned;
struct work_struct
work;
};
|
下面看enqueue的实现:
static int htb_enqueue(struct
sk_buff *skb, struct Qdisc *sch)
{
int
uninitialized_var(ret);
struct htb_sched *q =
qdisc_priv(sch);
struct htb_class *cl
= htb_classify(skb,
sch,
&ret);
/* 首先对pkt进行分类,找到叶子class
*/
if (cl
== HTB_DIRECT) {
/* enqueue to helper queue
*/
if (q->direct_queue.qlen < q->direct_qlen) {
__skb_queue_tail(&q->direct_queue, skb);
q->direct_pkts++;
} else {
kfree_skb(skb);
sch->qstats.drops++;
return NET_XMIT_DROP;
}
} else if ((ret
= qdisc_enqueue(skb,
cl->un.leaf.q))
!= NET_XMIT_SUCCESS) {
if (net_xmit_drop_count(ret)) {
/* enqueue到叶子class,结束
*/
sch->qstats.drops++;
cl->qstats.drops++;
}
return ret;
} else {
cl->bstats.packets +=
skb_is_gso(skb)?skb_shinfo(skb)->gso_segs:1;
cl->bstats.bytes +=
qdisc_pkt_len(skb);
htb_activate(q, cl);
/* 叶子class之前可能队列为空,activate之
*/
}
sch->q.qlen++;
sch->bstats.packets +=
skb_is_gso(skb)?skb_shinfo(skb)->gso_segs:1;
sch->bstats.bytes +=
qdisc_pkt_len(skb);
return NET_XMIT_SUCCESS;
}
|
Dequeue的实现:htb的dequeue首先检查bitmap,找出最低level最高priority的class
list,然后对这个class
list调用htb_dequeue_tree来实现dequeue,下面是htb_dequeue_tree的实现。
static struct sk_buff *htb_dequeue_tree(struct
htb_sched *q, int
prio,
int level)
{
struct sk_buff *skb =
NULL;
struct htb_class *cl,
*start;
/* look initial class up
in the row */
start = cl =
htb_lookup_leaf(q->row[level]
+ prio, prio,
q->ptr[level]
+ prio,
q->last_ptr_id[level]
+ prio);
/* 此处考虑的DRR的调度 */
do {
next:
if (unlikely(!cl))
return NULL;
/* class can be empty
- it is unlikely but can be
true if leaf
qdisc drops packets in enqueue
routine or if someone used
graft operation on the leaf since last dequeue;
simply deactivate and skip such
class */
if (unlikely(cl->un.leaf.q->q.qlen ==
0)) {
struct htb_class *next;
htb_deactivate(q, cl);
/* 队列为空了??deactivate之
*/
/* row/level might become empty
*/
if ((q->row_mask[level]
& (1
<< prio))
== 0)
return NULL;
next =
htb_lookup_leaf(q->row[level]
+ prio,
prio, q->ptr[level]
+ prio,
q->last_ptr_id[level]
+ prio);
/* DRR */
if (cl
== start) /*
fix start if we just deleted it
*/
start = next;
cl = next;
goto next;
}
skb = cl->un.leaf.q->dequeue(cl->un.leaf.q);
/* 从queue中dequeue一个pkt
*/
if (likely(skb !=
NULL))
break;
qdisc_warn_nonwc("htb",
cl->un.leaf.q);
htb_next_rb_node((level ? cl->parent->un.inner.ptr
: q->ptr[0])
+ prio);
/* RR */
cl = htb_lookup_leaf(q->row[level]
+ prio, prio,
q->ptr[level]
+ prio,
q->last_ptr_id[level]
+ prio);
/* DRR的实现 */
} while (cl
!= start);
if (likely(skb !=
NULL)) {
cl->un.leaf.deficit[level]
-= qdisc_pkt_len(skb);
/* 更新deficit */
if (cl->un.leaf.deficit[level]
< 0) { /*
deficit不够了 */
cl->un.leaf.deficit[level]
+= cl->quantum;
htb_next_rb_node((level ? cl->parent->un.inner.ptr
: q->
ptr[0])
+ prio);
/* 从此level的active queue中移除
*/
}
/* this used to be after charge_class but
this constelation
gives us slightly better performance */
if (!cl->un.leaf.q->q.qlen)
htb_deactivate(q, cl);
/* 队列为空,deactivate之
*/
htb_charge_class(q, cl,
level, skb);
/* 检查此class是否发生超标 */
}
return skb;
}
|
其中HTB层次化的实现主要体现在htb_activate, htb_deactivate, htb_charge_class,
htb_change_class_mode等几个函数中,htb_activate_prios和htb_deactivate_prios完成子class向父class借用deficit的具体操作。
|