ouyangxibao

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;
}

分类:

技术点:

相关文章:

  • 2021-10-20
  • 2021-10-20
  • 2021-10-20
  • 2022-12-23
  • 2021-11-28
  • 2022-12-23
  • 2021-10-18
  • 2022-01-25
猜你喜欢
  • 2021-10-20
  • 2021-08-29
  • 2021-10-20
  • 2021-09-21
  • 2021-10-20
  • 2022-02-24
  • 2021-05-06
相关资源
相似解决方案