FQ (Fair Queue)是一个无类别(classless)的报文调度器,其主意用于本地产生的流量。设计为控制每个流的发送节奏(pacing)。FQ完成流的区分,并且可完成TCP层要求的发送节奏。所有的属于一个套接口的报文认为属于一个流(flow)。对于非本地生成的报文(如对于路由器设备),可使用sk_buff结构中的成员hash作为区分流的备选项。
应用程序可使用setsockopt系统调用的选项SO_MAX_PACING_RATE指定最大的pacing速率。FQ调度器可在发送报文之间增加延时以达到TCP栈要求的发送速率。
出队列以轮询Round-Robin的方式进行。但是,预留了一个特殊的FIFO队列用于高优先级的报文(最高优先级:TC_PRIO_CONTROL,用于IGMP,IPv6多播,STP等协议报文),此队列的报文总是优先被取出发送。
TCP pacing对于有空闲时刻的流是有益处的,由于拥塞窗口可允许TCP栈在队列中缓存大量的报文,在进入空闲时,由pacing进行发送,从而避免了TCP在空闲之后重新进入慢启动的问题。但是这对于BDP较大的流量和发送大量数据的应用(如视频流)有不利影响。
配置如下:
$ sudo tc qdisc add dev ens38 root fq $ $ sudo tc -s qdisc show dev ens38 qdisc fq 8001: root refcnt 2 limit 10000p flow_limit 100p buckets 1024 orphan_mask 1023 quantum 3028 initial_quantum 15140 low_rate_threshold 550Kbit refill_delay 40.0ms Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0) backlog 0b 0p requeues 0 0 flows (0 inactive, 0 throttled) 0 gc, 0 highprio, 0 throttledFQ参数:
limit : 真实的队列长度的硬性限值。当达到这一限值时,新的报文将被丢弃。如果降低此值,当满足新设值时,报文被丢弃,默认情况下,此值为:10000。 flow_limit : 每个流最大入队的报文数量的硬性限值。默认为:100。 quantum : 每次轮询出队列的信用值(credit),例如,每个流每次可出队的字节数量。此值设置的较大意味值下一个流等待服务的时间更长,默认为2倍的接口MTU值。 refill_delay : 为非活动流重新分配信用值的时间间隔,默认为40ms。 low_rate_threshold : 当pacing速率低于此值时,在发送报文之间增加延时。 initial_quantum : 初始的发送信用值(credit),例如,当一个新的流刚被出队列处理时,可处理的字节数量。此选项特别的为允许IW10而设置(TCP的初始窗口值已调整为10)。默认值为10倍的接口MTU值,对于标准的以太网为15140。 maxrate : 流的最大发送速率。默认是不限速的。另外,应用程序也可通过套接口选项SO_MAX_PACING_RATE设置最大速率,但是,真正生效的速率为两者之中的较小值。 buckets : 流查找使用的hash表的大小。为进行高效的冲突检测,每个bucket桶赋予了一个红黑树结构(red-black tree)。默认值为:1024。 [no]pacing : 流的pacing功能控制,默认为开启。 orphan_mask : 对于不属于某个套接口的报文,或者TCP连接还未建立完成的报文(套接口状态为TCPF_LISTEN或者TCPF_NEW_SYN_RECV),FQ将保存在报文skb结构中的成员hash的值进行mask操作(使用orphan_mask),这将导致这些报文不能够侵占所有的bucket,达到一种预防DDOS攻击的效果。默认值为1023(十六进制为:0x3FF),意味着不会为此类报文分配超过1024数量的流结构。 ce_threshold : 对于报文的正常发送时间戳,如果超过此阈值间隔还没有被发送出去的所有报文都将被设置ECN标记ECE(ECN Congestion Experienced)。这对于类似DCTCP的拥塞控制算法比较有用,其要求在队列占用率较低的情况下进行标记操作。
函数fq_init实现FQ的初始化,由以下可见FQ各个参数的默认值。其中bucket选项对应的参数fq_trees_log,保存的为bucket数量的取2的对数,对应于1024,其去2的对数之后为10。
static int fq_init(struct Qdisc *sch, struct nlattr *opt, struct netlink_ext_ack *extack) { struct fq_sched_data *q = qdisc_priv(sch); sch->limit = 10000; q->flow_plimit = 100; q->quantum = 2 * psched_mtu(qdisc_dev(sch)); q->initial_quantum = 10 * psched_mtu(qdisc_dev(sch)); q->flow_refill_delay = msecs_to_jiffies(40); q->flow_max_rate = ~0UL; q->time_next_delayed_flow = ~0ULL; q->rate_enable = 1; q->new_flows.first = NULL; q->old_flows.first = NULL; q->delayed = RB_ROOT; q->fq_root = NULL; q->fq_trees_log = ilog2(1024); q->orphan_mask = 1024 - 1; q->low_rate_threshold = 550000 / 8; /* Default ce_threshold of 4294 seconds */ q->ce_threshold = (u64)NSEC_PER_USEC * ~0U; qdisc_watchdog_init_clockid(&q->watchdog, sch, CLOCK_MONOTONIC); if (opt) err = fq_change(sch, opt, extack); else err = fq_resize(sch, q->fq_trees_log); return err;内核初始化的low_rate_threshold参数单位为bit,换算为byte的话为:
550000 / 8 = (550000 / 8) * 8 = 550000bytes = 550kbytes。参数ce_threshold的值默认值等于: ~0U,内核变量q->ce_threshold中保存的为纳秒值:(NSEC_PER_USEC * ~0U),转换为秒:4294s。
1000L * 0xFFFF FFFF = 4294967295000 ns ≈ 4294stc命令在显示ce_threshold值时,由内核得到的为转换后的微秒值(即:0xFFFF FFFF),,对于默认值0xFFFF FFFF,tc不会进行显示。超过正常发送时刻如此长的时间还没有发送的报文应该没有。
static int fq_dump(struct Qdisc *sch, struct sk_buff *skb) { struct fq_sched_data *q = qdisc_priv(sch); u64 ce_threshold = q->ce_threshold; opts = nla_nest_start(skb, TCA_OPTIONS); if (opts == NULL) goto nla_put_failure; /* TCA_FQ_FLOW_DEFAULT_RATE is not used anymore */ do_div(ce_threshold, NSEC_PER_USEC);内核版本 5.0
