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