HTB的类树支持8层,即HTB排队规程的类可以有8层,第0层表示叶子节点(叶子节点永远是第0层)。最高层为第7层。如果只有一层的话,那么久只有一个0层,有两层的话,父层为第7层,子层为第0层;如果有三层的话,祖父为第7层,父为第6层,孙为叶子层,即第0层,其它情况依次类推。这里说的是它的类树。
每一层支持8个优先级,那么优先级与类层一共组合成64个节点。在HTB中使用一个8*8的二维数组来表示,我们称之为调度矩阵。每一个节点包含一棵红黑树,这棵红黑树是由同一层级同样的优先级的类构成的,使用类id作为红黑树的key。
注意:类本身是存放在hash表中,在进行调度(报文入队列与出队列)的时候才会被送入调度矩阵
HTB调度模型分为三层。
第一层:基于level调度
在调度矩阵中,每一层中的每一个节点都存放着当前层级对应优先级的处于SEND模式(其它模式不会处于)下的类。调度时从level0开始调度到level7.从level0开始调度,表示优先调度处于SEND模式下的叶子类,对于处于BORROW状态下的叶子类,它会挂到其父类的供给树中(feed tree),如果其父类处于SEND模式,那么在调度完level0后,进入到level1时,才会处理该BORROW状态下的叶子类。这就是基于level调度的实际意义:优先调度处于SEND的类。分层调度是实现承诺速率与最高速率的基石,保证在满足所有类的承诺速率后才会考虑峰值速率。
static struct sk_buff *htb_dequeue(struct Qdisc *sch)
{
......
//从level0开始调度
for (level = 0; level < TC_HTB_MAXDEPTH; level++) {//从叶子节点开始调度
/* common case optimization - skip event handler quickly */
int m;
//获取最近事件的时间
s64 event = q->near_ev_cache[level];
//处理等待队列中的类,随着时间推移,供给的令牌增多后,需要将等待队列中的类移出来
if (q->now >= event) {//事件已经触发了,执行事件
event = htb_do_events(q, level, start_at);
if (!event)//等待队列中没有类
event = q->now + NSEC_PER_SEC;
q->near_ev_cache[level] = event;
}
if (next_event > event)//记录最小的需要等待的时间
next_event = event;
m = ~q->row_mask[level];//获取当前level的活跃队列红黑树掩码取反
//基于优先级调度,优先级从高到低,每一个bit表示一个优先级,优先级越高,位于m中的bit越高
while (m != (int)(-1)) {//根据优先级遍历每一个活跃的红黑树,这里完全按照优先级遍历,只要更高优先级有报文发送,那么就不会发送次优先级
int prio = ffz(m);//0的个数
m |= 1 << prio;//去掉一个0,以便下一次遍历下一个优先级
//查看该level的该优先级中是否有报文能够发送
skb = htb_dequeue_tree(q, prio, level);
if (likely(skb != NULL))
goto ok;//发送报文
}
}
//没有报文能够发送,添加统计
qdisc_qstats_overlimit(sch);
if (likely(next_event > q->now))//下一个等待事件发生还有一段时间,启动看门狗事件
qdisc_watchdog_schedule_ns(&q->watchdog, next_event);
else
schedule_work(&q->work);
fin:
return skb;
}
第二层:基于优先级调度
对于处于同一level的同样模式下的类,优先处理优先级较高的类,这就是基于优先级调度。
static struct sk_buff *htb_dequeue(struct Qdisc *sch)
{
......
//从level0开始调度
for (level = 0; level < TC_HTB_MAXDEPTH; level++) {//从叶子节点开始调度
.......
m = ~q->row_mask[level];//获取当前level的活跃队列红黑树掩码取反
//基于优先级调度,优先级从高到低,每一个bit表示一个优先级,优先级越高,位于m中的bit越高
while (m != (int)(-1)) {//根据优先级遍历每一个活跃的红黑树,这里完全按照优先级遍历,只要更高优先级有报文发送,那么就不会发送次优先级
int prio = ffz(m);//0的个数
m |= 1 << prio;//去掉一个0,以便下一次遍历下一个优先级
//查看该level的该优先级中是否有报文能够发送
skb = htb_dequeue_tree(q, prio, level);
if (likely(skb != NULL))
goto ok;//发送报文
}
}
return skb;
}
第三层:DRR调度
处于同一level同一优先级的同样模式下类,采用DRR进行调度,即每一个类都有一个调度额度,当该类的额度被用完后,将会切换到下一个类。调度红黑树是以类id作为key的在类因为令牌关系导致从调度树中添加或者删除,不会影响调度顺序,因为类id没变。
/* dequeues packet at given priority and level; call only if
* you are sure that there is active class at prio/level
* 从htb队列中的level层中的prio中的红黑树中提取一个可以拿报文的类。
*/
static struct sk_buff *htb_dequeue_tree(struct htb_sched *q, const int prio,
const int level)
{
struct sk_buff *skb = NULL;
struct htb_class *cl, *start;
struct htb_level *hlevel = &q->hlevel[level];
struct htb_prio *hprio = &hlevel->hprio[prio];
/* look initial class up in the row */
start = cl = htb_lookup_leaf(hprio, prio);
do {
next:
.....
//从队列中获取了一个报文
if (likely(skb != NULL)) {
bstats_update(&cl->bstats, skb);
//基于drr进行调度,这里减少本次报文所需的令牌数
cl->un.leaf.deficit[level] -= qdisc_pkt_len(skb);
if (cl->un.leaf.deficit[level] < 0) {//额度已经用完
cl->un.leaf.deficit[level] += cl->quantum;//分配下一次的额度
//将调度上下文切换到下一个类节点
htb_next_rb_node(level ? &cl->parent->un.inner.clprio[prio].ptr :
&q->hlevel[0].hprio[prio].ptr);
}
/* 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);
htb_charge_class(q, cl, level, skb);
}
return skb;
}