quantum 表示每次出队列轮询的信用值(credit),例如,每个流每次可允许出队列的字节数量。此值设置的较大意味值下一个流等待服务的时间更长,默认为2倍的接口MTU值。
static int fq_init(struct Qdisc *sch, struct nlattr *opt, struct netlink_ext_ack *extack)
{
struct fq_sched_data *q = qdisc_priv(sch);
int err;
sch->limit = 10000;
q->flow_plimit = 100;
q->quantum = 2 * psched_mtu(qdisc_dev(sch));
以上为man文档上给出的解释(man tc-fq),但是,实际中报文是不能够按照字节进行拆分的,也不是严格的和quantum的数值相等的。在函数fq_dequeue中,如果流的credit耗尽,包括credit为零,或者credit为负值的情况,负值表明上一个发送的报文长度超过了credit的数值,这是欠下了credit。在本轮中,增加quantum数值,一部分用于偿还上一次的欠值,剩下的部分用于下一轮的发送使用,当前轮中不发送此流的报文。
注意这里是为credit加上quantum的值,不能进行简单的赋值。
static struct sk_buff *fq_dequeue(struct Qdisc *sch)
{
...
f = head->first;
if (f->credit <= 0) {
f->credit += q->quantum;
head->first = f->next;
fq_flow_add_tail(&q->old_flows, f);
goto begin;
}
在找到credit不为零的适合发送的流之后,由流结构的信用值中减去要发送的报文的长度值,此后credit的值可能为零或者负值。如果没有开启pacing功能,结束处理,返回要发送的报文。
prefetch(&skb->end);
plen = qdisc_pkt_len(skb);
f->credit -= plen;
if (!q->rate_enable) goto out;
否则,进行限速处理,如果报文中没有给出EDT发送时间(tstamp为零),速率限制将选取TC命令设定的最大速率flow_max_rate和套接口设置的sk_pacing_rate速率,两者之间的较小值。如果此速率小于TC命令设置的low_rate_threshold值,清空流结构的信用值,制造一定的延时,下一次轮询时,此流中的报文得不到发送。
否则,如果此流剩余的credit小于等于零,计算其下一个报文的发送时间。首先,需要确定发送当前报文所需的时长,这个时长等于报文长度除以所要求的速率。注意,在报文没有设置EDT时间的情况下,以上计算所使用的报文长度最小为quantum的值,由于此时credit已经耗尽,如果报文长度小于quantum时,表明已发送过此流的报文,这里是再次发送此流的后续报文,为避免频繁发送此流的小报文,这里将其长度设置为quantum值以上。
rate = q->flow_max_rate;
/* If EDT time was provided for this skb, we need to
* update f->time_next_packet only if this qdisc enforces a flow max rate.
*/
if (!skb->tstamp) {
if (skb->sk)
rate = min(skb->sk->sk_pacing_rate, rate);
if (rate <= q->low_rate_threshold) {
f->credit = 0;
} else {
plen = max(plen, q->quantum);
if (f->credit > 0)
goto out;
}
}
在报文设置了EDT时间的情况下,使用报文的真实长度计算发送时长,最后,由于受到调度以及定时器的偏差影响,按照计算而来的时间发送下一个报文,可能导致发送的滞后,对于随后发送的报文,以下代码将发送时刻进行一定的提前。
if (rate != ~0UL) {
u64 len = (u64)plen * NSEC_PER_SEC;
if (likely(rate))
len = div64_ul(len, rate);
/* Since socket rate can change later, clamp the delay to 1 second.
* Really, providers of too big packets should be fixed !
*/
if (unlikely(len > NSEC_PER_SEC)) {
len = NSEC_PER_SEC;
q->stat_pkts_too_long++;
}
/* Account for schedule/timers drifts.
* f->time_next_packet was set when prior packet was sent,
* and current time (@now) can be too late by tens of us.
*/
if (f->time_next_packet)
len -= min(len/2, now - f->time_next_packet);
f->time_next_packet = now + len;
}
out:
qdisc_bstats_update(sch, skb);
return skb;
内核版本 5.0