概述
如下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正常工作的情况下,队列长度不会达到此值。
min 和 max 分别对应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+1−avpktqmin<W1−(1−W)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 %un",
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 =logleft ( frac{prob}{qmax-qmin} right ) Plog=log(qmax−qminprob)
其中,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 Stableft [ tgg Scell_log right ]=-logleft ( 1-W right )*frac{t}{xmit_time} Stab[t≫Scell_log]=−log(1−W)∗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((1−W)m)=m∗log(1−W)=xmit_timet∗log(1−1−Wlog1)
此数组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
最后
以上就是想人陪早晨为你收集整理的RED队列tc设置的全部内容,希望文章能够帮你解决RED队列tc设置所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复