RED队列tc设置

如下tc命令配置RED(Random Early Detection)队列。

$ sudo tc qdisc add dev ens40 root red limit 400000 min 30000 max 100000 avpkt 1000 probability 0.02 burst 55 ecn adaptive harddrop bandwidth 1000Mbit          
$ 
$ tc -d -s qdisc show dev ens40   
qdisc red 8005: root refcnt 2 limit 400000b min 30000b max 100000b ecn harddrop adaptive ewma 5 probability 0.00956593 Scell_log 8
 Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0) 
 backlog 0b 0p requeues 0 
  marked 0 early 0 pdrop 0 other 0

参数说明:

limit 为一个RED算法无关的值,应当设置为大于最大队列长度与突发报文之和(max+burst),超过此值后,接收到的报文都将被丢弃,RED正常工作的情况下,队列长度不会达到此值。
minmax 分别对应RED算法中的最小minth和最大maxth队列长度,其中min通常设置为max的1/3值,max通常为limit值的1/4。
avpkt 为平均报文长度,与以下的burst参数一起决定平均队列长度avg计算时的时间常数。
burst 决定了平均队列长度avg的计算在多大程度上受到实际队列长度的影响,较大值是的avg的更新缓慢,允许处理更多的突发报文。通常根据等式:(min+min+max)/(3*avpkt)进行设置。
probability 即为RED算法中的maxp变量,表示最大报文标记概率,默认值为0.02,当队列长度达到max时的报文标记概率。
bandwidth 指定接口(这里为ens40)的带宽,用于计算队列空闲之后的平均队列长度,bindwidth的单位为kbps。
ecn Explicit Congestion Notification允许RED对支持ECN的主机以报文标记的形式通知拥塞,但是,如果队列长度超过limit值,报文都将丢弃。另外,如果设置了harddrop参数,当平均队列长度超过max值后,不执行标记,进行丢弃报文。
adaptive Adaptive-RED算法,旨在通过动态调整probability的值(范围:[0.01,0.50]),将队列长度维持在(max-min)/2。

以下参数介绍中涉及到的log均为以2为底。

1 Wlog参数设置

以下为iproute2-4.19.0/tc/q_red.c文件中对RED设置参数的解析处理,首先看一下Wlog参数的设置,其由函数tc_red_eval_ewma根据minth,burst和avpkt参数计算而得。

static int red_parse_opt(struct qdisc_util *qu, int argc, char **argv, ...)
{
    ...
    if ((parm = tc_red_eval_ewma(opt.qth_min, burst, avpkt)) < 0) {
        fprintf(stderr, "RED: failed to calculate EWMA constant.\n");
        return -1;
    }
    if (parm >= 10)
        fprintf(stderr, "RED: WARNING. Burst %u seems to be too large.\n", burst);
    opt.Wlog = parm;

如下tc_red_eval_ewma函数,Wlog的计算依据以下公式,详情参见:RED队列 中的介绍,其中wq最大值的计算部分。

b u r s t + 1 − q m i n a v p k t < 1 − ( 1 − W ) b u r s t W burst + 1 -\frac{q_{min}}{avpkt}<\frac{ 1-\left ( 1-W \right )^{burst} }{W} burst+1avpktqmin<W1(1W)burst

参数burst的值至少应为1 + qmin/avpkt。RED算法中wq(此处由W表示)的最大值设置为0.5,其对应于Wlog的值为1(log以2为底),由此开始,查找符合以上公式要求的W值,最终返回值Wlog,其等于log(1/W)也即log(1/wq),而wq=W=1.0/(1<<Wlog)。由tc命令可查看到ewma(Exponential Weighted Moving Average)的值为5,即wq=2**-5=0.03125。

int tc_red_eval_ewma(unsigned int qmin, unsigned int burst, unsigned int avpkt)
{
    int wlog = 1;
    double W = 0.5;
    double a = (double)burst + 1 - (double)qmin/avpkt;

    if (a < 1.0) {
        fprintf(stderr, "tc_red_eval_ewma() burst %u is too small ? Try burst %u\n",
                burst, 1 + qmin/avpkt);
        return -1;
    }
    for (wlog = 1; wlog < 32; wlog++, W /= 2) {
        if (a <= (1 - pow(1-W, burst))/W)
            return wlog;
    }
    return -1;

如果Wlog的值大于等于10,认为最终的wq过小,导致avg对于实际队列长度的变化反应缓慢,意味着突发burst会很大。这里限定Wlog需小于10(对于的wq等于:1/1024)。根据公式-1/ln(1-wq),可得大约需要1000个报文,avg才能由0增长为0.63。

2 Plog参数设置

其次,是对参数Plog的设置,其与RED算法中报文标记概率maxp的关系为:max_P = (qth_max-qth_min)/2^Plog。例如,当qth_max=100k,qth_min=30k,Plog=22的情况下,max_P=0.016。

static int red_parse_opt(struct qdisc_util *qu, int argc, char **argv,...)
{
    ...
    if ((parm = tc_red_eval_P(opt.qth_min, opt.qth_max, probability)) < 0) {
        fprintf(stderr, "RED: failed to calculate probability.\n");
        return -1;
    }
    opt.Plog = parm;

其由以下公式计算而得:

P l o g = l o g ( p r o b q m a x − q m i n ) Plog =log\left ( \frac{prob}{qmax-qmin} \right ) Plog=log(qmaxqminprob)

其中,prob为tc命令行设置的概率(如:0.02),如果qmax和qmin相等时,将max_P设置为零。否则,依据以上公式取得Plog的值。

int tc_red_eval_P(unsigned int qmin, unsigned int qmax, double prob)
{   
    int i = qmax - qmin;
    
    if (!i) return 0;
    if (i < 0) return -1;

    prob /= i;
    
    for (i = 0; i < 32; i++) {
        if (prob > 1.0)
            break;
        prob *= 2;
    }
    if (i >= 32) return -1;
    return i;

3 Scell_log参数

接下来,是对参数Scell_log的设置,其表示一个cell的log形式的大小,用于查询以下的Stab表项,方便计算报文到达空闲队列时的avg。详情参见:RED队列算法 - 实现中的介绍。

static int red_parse_opt(struct qdisc_util *qu, int argc, char **argv,...)
{
    ...
    if ((parm = tc_red_eval_idle_damping(opt.Wlog, avpkt, rate, sbuf)) < 0) {
        fprintf(stderr, "RED: failed to calculate idle damping table.\n");
        return -1;
    }
    opt.Scell_log = parm;

Stab表由以下公式计算而得,共有256个表项(256个cell),其中索引为0和255的表项的值分别为0和31,表项值最大不超过31。

S t a b [ t ≫ S c e l l _ l o g ] = − l o g ( 1 − W ) ∗ t x m i t _ t i m e Stab\left [ t\gg Scell\_log \right ]=-log\left ( 1-W \right )*\frac{t}{xmit\_time} Stab[tScell_log]=log(1W)xmit_timet

以下为函数tc_red_eval_idle_damping的实现。由于计算空闲队列qavg的复杂性,公式为:(m = idletime / (average_pkt_size / bandwidth), v->qavg *= (1-W)m),数组Stab用于保存事先计算好的log((1-W)m)的结果值(以2为底),这样在计算qavg时,只需进行右移位操作即可(注意Stab保存的为一个log倒数(负值))。

l o g ( ( 1 − W ) m ) = m ∗ l o g ( 1 − W ) = t x m i t _ t i m e ∗ l o g ( 1 − 1 1 − W l o g ) log((1-W)^m) = m*log(1-W) = \frac{t}{xmit\_time} * log(1 - \frac{1}{1-Wlog}) log((1W)m)=mlog(1W)=xmit_timetlog(11Wlog1)

此数组Stab的索引值为:(idle_time >> Scell_log),即将(1 << Scell_log)时间段的空闲时长,都近似为一个值,Stab数组共保存256个时间段。

在函数中,首先确定一个cell时间段的大小,变量clog为cell的log形式,clog最大不超过32(即2**32)。cell的数量不超过256个,即maxtime/(1<<clog) < 512,取其中的最小cell值,cell越小精度越高。

由于cell的数量固定(256),队列空闲时长的最大值maxtime,对应于最大的单个cell的大小,即Scell_log为31时的值,反过来,由于单个时间对应的值为: -log(1.0 - wq))/xmit_time,即可由31/lw计算得到maxtime的值。由此可见,RED仅对(255 << Scell_log)时长的空闲时间计算avg,参见函数red_set_parms中对Scell_max的赋值。

需要注意的是以下lw的计算,使用的log为自然对数。

int tc_red_eval_idle_damping(int Wlog, unsigned int avpkt, unsigned int bps, __u8 *sbuf)
{       
    double xmit_time = tc_calc_xmittime(bps, avpkt);
    double lW = -log(1.0 - 1.0/(1<<Wlog))/xmit_time;
    double maxtime = 31/lW;
    int clog; 
    int i;
        
    for (clog = 0; clog < 32; clog++) {
        if (maxtime/(1<<clog) < 512)
            break;
    }
    if (clog >= 32) return -1;

数组sbuf保存每个时间段对应的右移位值,共有256个时间段。

    sbuf[0] = 0; 
    for (i = 1; i < 255; i++) {
        sbuf[i] = (i<<clog)*lW;
        if (sbuf[i] > 31)
            sbuf[i] = 31;
    }
    sbuf[255] = 31;
    return clog;

4 max_P参数

最后,看一下,max_P参数的设置,将tc命令参数probability的值扩大了2^32倍。这样在RED计算随机值时可使用内核提供的prandom_u32等类型的函数,这样max_P和随机可进行等效的比较。

static int red_parse_opt(struct qdisc_util *qu, int argc, char **argv,...)
{
    ...
    tail = addattr_nest(n, 1024, TCA_OPTIONS);
    addattr_l(n, 1024, TCA_RED_PARMS, &opt, sizeof(opt));
    addattr_l(n, 1024, TCA_RED_STAB, sbuf, 256);
    max_P = probability * pow(2, 32);
    addattr_l(n, 1024, TCA_RED_MAX_P, &max_P, sizeof(max_P));
    addattr_nest_end(n, tail);
    return 0;

在显示的时候,tc命令进行了还原。

static int red_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
{
    __u32 max_P = 0;

    if (tb[TCA_RED_MAX_P] &&
        RTA_PAYLOAD(tb[TCA_RED_MAX_P]) >= sizeof(__u32))
        max_P = rta_getattr_u32(tb[TCA_RED_MAX_P]);

    if (show_details) {
        print_uint(PRINT_ANY, "ewma", "ewma %u ", qopt->Wlog);
        if (max_P)
            print_float(PRINT_ANY, "probability",
                    "probability %lg ", max_P / pow(2, 32));
        else
            print_uint(PRINT_ANY, "Plog", "Plog %u ", qopt->Plog);
        print_uint(PRINT_ANY, "Scell_log", "Scell_log %u",
               qopt->Scell_log);
    }

5 内核RED参数处理

内核中red_change负责RED参数处理,首先由red_check_params检查minth,maxth和Wlog参数的合法性。

static int red_change(struct Qdisc *sch, struct nlattr *opt, struct netlink_ext_ack *extack)
{
    struct Qdisc *old_child = NULL, *child = NULL;
    struct red_sched_data *q = qdisc_priv(sch);
    struct nlattr *tb[TCA_RED_MAX + 1];
    struct tc_red_qopt *ctl;

    if (opt == NULL) return -EINVAL;

    err = nla_parse_nested(tb, TCA_RED_MAX, opt, red_policy, NULL);
    if (err < 0) return err;

    if (tb[TCA_RED_PARMS] == NULL || tb[TCA_RED_STAB] == NULL)
        return -EINVAL;

    max_P = tb[TCA_RED_MAX_P] ? nla_get_u32(tb[TCA_RED_MAX_P]) : 0;

    ctl = nla_data(tb[TCA_RED_PARMS]);
    if (!red_check_params(ctl->qth_min, ctl->qth_max, ctl->Wlog))
        return -EINVAL;

之后,根据limit参数创建FIFO队列。

    if (ctl->limit > 0) {
        child = fifo_create_dflt(sch, &bfifo_qdisc_ops, ctl->limit, extack);
        if (IS_ERR(child)) return PTR_ERR(child);

        /* child is fifo, no need to check for noop_qdisc */
        qdisc_hash_add(child, true);
    }

    sch_tree_lock(sch);
    q->flags = ctl->flags;
    q->limit = ctl->limit;

接下来,调用函数red_set_parms设置RED参数选项。

    red_set_parms(&q->parms, ctl->qth_min, ctl->qth_max, ctl->Wlog,
              ctl->Plog, ctl->Scell_log,
              nla_data(tb[TCA_RED_STAB]),
              max_P);
    red_set_vars(&q->vars);

最后,如果设置了adaptive参数,开启adapt_timer定时器,时长为500毫秒。队列长度为零的话,更新队列的空闲时间戳。

    del_timer(&q->adapt_timer);
    if (ctl->flags & TC_RED_ADAPTATIVE)
        mod_timer(&q->adapt_timer, jiffies + HZ/2);

    if (!q->qdisc->q.qlen)
        red_start_of_idle_period(&q->vars);

以下为RED参数设置函数red_set_parms。变量qth_min和qth_max保存的为右移Wlog位的参数,在red_check_params函数中,确认了此操作不会超出32位的长度范围。

static inline void red_set_parms(struct red_parms *p,
                 u32 qth_min, u32 qth_max, u8 Wlog, u8 Plog,
                 u8 Scell_log, u8 *stab, u32 max_P)
{             
    int delta = qth_max - qth_min;
    u32 max_p_delta;

    p->qth_min  = qth_min << Wlog;
    p->qth_max  = qth_max << Wlog;
    p->Wlog     = Wlog;
    p->Plog     = Plog;
    if (delta <= 0) delta = 1;
    p->qth_delta    = delta;
    if (!max_P) {
        max_P = red_maxp(Plog);
        max_P *= delta; /* max_P = (qth_max - qth_min)/2^Plog */
    }
    p->max_P = max_P;
    max_p_delta = max_P / delta;
    max_p_delta = max(max_p_delta, 1U);
    p->max_P_reciprocal  = reciprocal_value(max_p_delta);

以下是对Adaptive-RED相关参数的初始化,以及空闲队列avg计算相关参数。

    /* RED Adaptative target :
     * [min_th + 0.4*(min_th - max_th),
     *  min_th + 0.6*(min_th - max_th)].
     */
    delta /= 5;
    p->target_min = qth_min + 2*delta;
    p->target_max = qth_min + 3*delta;

    p->Scell_log    = Scell_log;
    p->Scell_max    = (255 << Scell_log);

    if (stab) memcpy(p->Stab, stab, sizeof(p->Stab));

iproute2: 4.19.0
内核版本 5.0

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