Linux公平队列FQ配置

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较大的流量和发送大量数据的应用(如视频流)有不利影响。

1. FQ配置

配置如下:

$ 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 throttled

FQ参数:

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的拥塞控制算法比较有用,其要求在队列占用率较低的情况下进行标记操作。

2. 内核FQ初始化

函数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 ≈ 4294s

tc命令在显示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

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页