Linux内核中流量控制(7)

本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: [email protected]
来源:http://yfydz.cublog.cn

5.7 RED(Random Early Detection queue)

RED算法由Sally Floyd和Van Jacobson提出, 论文为"Random Early Detection Gateways  for Congestion Avoidance", 1993, IEEE/ACM Transactions on Networking.

基本算法:
对新数据包计算平均队列长度:
平均长度avg=(1-W) * avg + W*当前队列长度
 W是参数, 取值为1/(2^Wlog), Wlog可配置, W越小, 平滑能力越强.
 算法中两个阈值: th_min和th_max, 这两个参数可配置
当avg > th_max时,  该新包被丢弃;
当avg < th_min时,  该新包允许通过;
当th_min <= avg <= th_max时, 计算概率:
 Pb = max_P * (avg - th_min)/(th_max-th_min)
然后按此概率丢包, max_P为一小数, 通常为0.01, 0.02等, 一般在算法中通过右移操作来实现:
 max_P = (qth_max-qth_min)>>Plog
Plog为可配置参数

5.7.1 RED操作结构定义

// RED算法参数
struct red_parms
{
 /* Parameters */
// 最小队列长度
 u32  qth_min; /* Min avg length threshold: A scaled */
// 最大队列长度
 u32  qth_max; /* Max avg length threshold: A scaled */
// 最大休眠时间
 u32  Scell_max;
// 保存随机掩码
 u32  Rmask;  /* Cached random mask, see red_rmask */
//
 u8  Scell_log;
// Wlog, Plog参数含义如上所示
 u8  Wlog;  /* log(W)  */
 u8  Plog;  /* random number bits */
// 256个元素
 u8  Stab[RED_STAB_SIZE];
 /* Variables */
// 以下的参数是在处理过程中会改变的参数
// 从上次随机数产生时的处理的数据包数
 int  qcount;  /* Number of packets since last random
        number generation */
// 缓存的随机数
 u32  qR;  /* Cached random number */
// 平均队列长度
 unsigned long qavg;  /* Average queue length: A scaled */
// 当前休眠起始时间
 psched_time_t qidlestart; /* Start of current idle period */
};

// RED私有数据
struct red_sched_data
{
// 最大队列长度, 这是硬限制
 u32   limit;  /* HARD maximal queue length */
// 标志
 unsigned char  flags;
// RED算法参数
 struct red_parms parms;
// RED统计值
 struct red_stats stats;
 struct Qdisc  *qdisc;
};

// RED流控操作结构
static struct Qdisc_ops red_qdisc_ops = {
 .id  = "red",
 .priv_size = sizeof(struct red_sched_data),
 .cl_ops  = &red_class_ops,
 .enqueue = red_enqueue,
 .dequeue = red_dequeue,
 .requeue = red_requeue,
 .drop  = red_drop,
 .init  = red_init,
 .reset  = red_reset,
 .destroy = red_destroy,
 .change  = red_change,
 .dump  = red_dump,
 .dump_stats = red_dump_stats,
 .owner  = THIS_MODULE,
};

// RED类别操作结构
static struct Qdisc_class_ops red_class_ops = {
 .graft  = red_graft,
 .leaf  = red_leaf,
 .get  = red_get,
 .put  = red_put,
 .change  = red_change_class,
 .delete  = red_delete,
 .walk  = red_walk,
 .tcf_chain = red_find_tcf,
 .dump  = red_dump_class,
};
 
5.7.2 RED一些基本操作函数

在include/net/red.h中定义

// 返回Plog对应RED掩码, 和网络掩码不同,RED掩码值是从低位开始算的
// 掩码值位2^Plog-1, , Plog超过31后就和31相同
static inline u32 red_rmask(u8 Plog)
{
 return Plog < 32 ? ((1 << Plog) - 1) : ~0UL;
}

// 设置RED参数, 平均长度阈值的最大最小值等
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)
{
 /* Reset average queue length, the value is strictly bound
  * to the parameters below, reseting hurts a bit but leaving
  * it might result in an unreasonable qavg for a while. --TGR
  */
 p->qavg  = 0;
// 队列元素统计
 p->qcount = -1;
// 内部平均长度阈值的最大最小值为设置值的2^Wlog倍
 p->qth_min = qth_min << Wlog;
 p->qth_max = qth_max << Wlog;
 p->Wlog  = Wlog;
 p->Plog  = Plog;
// 随机掩码
 p->Rmask = red_rmask(Plog);
 p->Scell_log = Scell_log;
// 最大休眠时间
 p->Scell_max = (255 << Scell_log);
// stab
 memcpy(p->Stab, stab, sizeof(p->Stab));
}

// 算法是否在休眠状态, 也就是看qidlestart是否为0, qidlestart非0表示正在休眠
static inline int red_is_idling(struct red_parms *p)
{
 return !PSCHED_IS_PASTPERFECT(p->qidlestart);
}

// RED休眠, 将p->qidlestart设置为当前时间
static inline void red_start_of_idle_period(struct red_parms *p)
{
 PSCHED_GET_TIME(p->qidlestart);
}
// RED停止休眠, 将p->qidlestart设置清零
static inline void red_end_of_idle_period(struct red_parms *p)
{
 PSCHED_SET_PASTPERFECT(p->qidlestart);
}

// RED算法重新启动
static inline void red_restart(struct red_parms *p)
{
// RED数值清零,
 red_end_of_idle_period(p);
 p->qavg = 0;
 p->qcount = -1;
}

// 从休眠恢复后重新计算队列平均值
static inline unsigned long red_calc_qavg_from_idle_time(struct red_parms *p)
{
 psched_time_t now;
 long us_idle;
 int  shift;
// 获取当前时间
 PSCHED_GET_TIME(now);
// 计算当前时间与休眠时的时间差, 也就是计算休眠了多少时间
// p->Scell_max是休眠时间上限
 us_idle = PSCHED_TDIFF_SAFE(now, p->qidlestart, p->Scell_max);
 /*
  * The problem: ideally, average length queue recalcultion should
  * be done over constant clock intervals. This is too expensive, so
  * that the calculation is driven by outgoing packets.
  * When the queue is idle we have to model this clock by hand.
  *
  * SF+VJ proposed to "generate":
  *
  * m = idletime / (average_pkt_size / bandwidth)
  *
  * dummy packets as a burst after idle time, i.e.
  *
  *  p->qavg *= (1-W)^m
  *
  * This is an apparently overcomplicated solution (f.e. we have to
  * precompute a table to make this calculation in reasonable time)
  * I believe that a simpler model may be used here,
  * but it is field for experiments.
  */
// 根据休眠数和Scell_log计算索引值获取stab数组中的偏移值
 shift = p->Stab[(us_idle >> p->Scell_log) & RED_STAB_MASK];
 if (shift)
// 偏移值非0, 当前平均队列值左移后返回
  return p->qavg >> shift;
 else {
  /* Approximate initial part of exponent with linear function:
   *
   *  (1-W)^m ~= 1-mW + ...
   *
   * Seems, it is the best solution to
   * problem of too coarse exponent tabulation.
   */
// 重新计算休眠时间
  us_idle = (p->qavg * (u64)us_idle) >> p->Scell_log;
// 如果休眠时间数值小于队列长度一半
  if (us_idle < (p->qavg >> 1))
// 返回队列长度减休眠时间
   return p->qavg - us_idle;
  else
// 否则返回队列长度的一半
   return p->qavg >> 1;
 }
}

// 非休眠情况下计算队列平均值
static inline unsigned long red_calc_qavg_no_idle_time(struct red_parms *p,
             unsigned int backlog)
{
 /*
  * NOTE: p->qavg is fixed point number with point at Wlog.
  * The formula below is equvalent to floating point
  * version:
  *
  *  qavg = qavg*(1-W) + backlog*W;
  *
  * --ANK (980924)
  */
 return p->qavg + (backlog - (p->qavg >> p->Wlog));
}

// RED计算滑动队列平均值
static inline unsigned long red_calc_qavg(struct red_parms *p,
       unsigned int backlog)
{
// 分活动状态和非活动状态, 分别计算
 if (!red_is_idling(p))
  return red_calc_qavg_no_idle_time(p, backlog);
 else
  return red_calc_qavg_from_idle_time(p);
}

// 生成RED随机数
static inline u32 red_random(struct red_parms *p)
{
 return net_random() & p->Rmask;
}

// 概率随机决定是否丢包
static inline int red_mark_probability(struct red_parms *p, unsigned long qavg)
{
 /* The formula used below causes questions.
    OK. qR is random number in the interval 0..Rmask
    i.e. 0..(2^Plog). If we used floating point
    arithmetics, it would be: (2^Plog)*rnd_num,
    where rnd_num is less 1.
    Taking into account, that qavg have fixed
    point at Wlog, and Plog is related to max_P by
    max_P = (qth_max-qth_min)/2^Plog; two lines
    below have the following floating point equivalent:
    max_P*(qavg - qth_min)/(qth_max-qth_min) < rnd/qcount
    Any questions? --ANK (980924)
  */
// 根据当前队列平均值计算出一个值是否小于随机数qR来实现概率丢包
 return !(((qavg - p->qth_min) >> p->Wlog) * p->qcount < p->qR);
}
enum {
 RED_BELOW_MIN_THRESH,
 RED_BETWEEN_TRESH,
 RED_ABOVE_MAX_TRESH,
};

// 将当前队列平均值与阈值进行比较
static inline int red_cmp_thresh(struct red_parms *p, unsigned long qavg)
{
// 小于低阈值
 if (qavg < p->qth_min)
  return RED_BELOW_MIN_THRESH;
// 不小于高阈值
 else if (qavg >= p->qth_max)
  return RED_ABOVE_MAX_TRESH;
 else
// 高低阈值之间
  return RED_BETWEEN_TRESH;
}
enum {
// 放行
 RED_DONT_MARK,
// 根据概率标记
 RED_PROB_MARK,
// 标记
 RED_HARD_MARK,
};

// RED动作判定
static inline int red_action(struct red_parms *p, unsigned long qavg)
{
// 将当前平均队列值与阈值进行比较
 switch (red_cmp_thresh(p, qavg)) {
  case RED_BELOW_MIN_THRESH:
// 低于低阈值, 放行
   p->qcount = -1;
   return RED_DONT_MARK;
  case RED_BETWEEN_TRESH:
// 高低之间, 按概率标记
   if (++p->qcount) {
// 是否根据概率标记
    if (red_mark_probability(p, qavg)) {
// 标记
     p->qcount = 0;
     p->qR = red_random(p);
     return RED_PROB_MARK;
    }
   } else
    p->qR = red_random(p);
// 不标记
   return RED_DONT_MARK;
  case RED_ABOVE_MAX_TRESH:
// 超过高阈值, 直接标记
   p->qcount = -1;
   return RED_HARD_MARK;
 }
 BUG();
 return RED_DONT_MARK;
}

5.7.3 初始化

static int red_init(struct Qdisc* sch, struct rtattr *opt)
{
// RED私有数据
 struct red_sched_data *q = qdisc_priv(sch);
// 内部Qdisc初始化为noop_qdisc
 q->qdisc = &noop_qdisc;
// 调用change函数进行初始化
 return red_change(sch, opt);
}

5.7.4 参数修改

static int red_change(struct Qdisc *sch, struct rtattr *opt)
{
 struct red_sched_data *q = qdisc_priv(sch);
 struct rtattr *tb[TCA_RED_MAX];
 struct tc_red_qopt *ctl;
 struct Qdisc *child = NULL;
// 检查数据范围是否合法
 if (opt == NULL || rtattr_parse_nested(tb, TCA_RED_MAX, opt))
  return -EINVAL;
 if (tb[TCA_RED_PARMS-1] == NULL ||
     RTA_PAYLOAD(tb[TCA_RED_PARMS-1]) < sizeof(*ctl) ||
     tb[TCA_RED_STAB-1] == NULL ||
     RTA_PAYLOAD(tb[TCA_RED_STAB-1]) < RED_STAB_SIZE)
  return -EINVAL;
 ctl = RTA_DATA(tb[TCA_RED_PARMS-1]);
// 如果流量限制值有效, 建立RED流控的内部缺省流控结构, 为一BFIFO流控结构
 if (ctl->limit > 0) {
  child = red_create_dflt(sch->dev, ctl->limit);
  if (child == NULL)
   return -ENOMEM;
 }
 sch_tree_lock(sch);
// 设置RED流控结构标志和流量限制参数
 q->flags = ctl->flags;
 q->limit = ctl->limit;
// 对内部流控赋值
 if (child)
  qdisc_destroy(xchg(&q->qdisc, child));
// 设置RED流控算法参数
 red_set_parms(&q->parms, ctl->qth_min, ctl->qth_max, ctl->Wlog,
     ctl->Plog, ctl->Scell_log,
     RTA_DATA(tb[TCA_RED_STAB-1]));
// 如果队列空, RED进入休眠状态
 if (skb_queue_empty(&sch->q))
  red_end_of_idle_period(&q->parms);
 sch_tree_unlock(sch);
 return 0;
}

// 建立缺省的RED内部Qdisc
static struct Qdisc *red_create_dflt(struct net_device *dev, u32 limit)
{
// 内部Qdisc使用的是bfifo, 按字节数限制
 struct Qdisc *q = qdisc_create_dflt(dev, &bfifo_qdisc_ops);
 struct rtattr *rta;
 int ret;
 if (q) {
  rta = kmalloc(RTA_LENGTH(sizeof(struct tc_fifo_qopt)),
                GFP_KERNEL);
  if (rta) {
// 填写rtattr结构, 实际有效数据就是流量限制值limit
   rta->rta_type = RTM_NEWQDISC;
   rta->rta_len = RTA_LENGTH(sizeof(struct tc_fifo_qopt));
   ((struct tc_fifo_qopt *)RTA_DATA(rta))->limit = limit;
// 调用bfifo的修改函数设置bfifo流控结构的limit参数
   ret = q->ops->change(q, rta);
   kfree(rta);
   if (ret == 0)
    return q;
  }
  qdisc_destroy(q);
 }
 return NULL;
}

5.7.5 入队

static int red_enqueue(struct sk_buff *skb, struct Qdisc* sch)
{
// RED私有数据
 struct red_sched_data *q = qdisc_priv(sch);
// RED内部流控节点: bfifo
 struct Qdisc *child = q->qdisc;
 int ret;
// 计算队列滑动平均值
 q->parms.qavg = red_calc_qavg(&q->parms, child->qstats.backlog);
// 如果在休眠, 停止休眠
 if (red_is_idling(&q->parms))
  red_end_of_idle_period(&q->parms);

// 根据队列平均值获取判定结果
 switch (red_action(&q->parms, q->parms.qavg)) {
// 通过
  case RED_DONT_MARK:
   break;
// 概率标记
  case RED_PROB_MARK:
   sch->qstats.overlimits++;
// 如果没用ECN拥塞标志, 丢包
   if (!red_use_ecn(q) || !INET_ECN_set_ce(skb)) {
    q->stats.prob_drop++;
    goto congestion_drop;
   }
// 概率标记增加,允许入队
   q->stats.prob_mark++;
   break;
// 直接标记
  case RED_HARD_MARK:
// overlimits增加
   sch->qstats.overlimits++;
// 如果RED设置HARDDROP标志或没使用ECN, 丢包
   if (red_use_harddrop(q) || !red_use_ecn(q) ||
       !INET_ECN_set_ce(skb)) {
    q->stats.forced_drop++;
    goto congestion_drop;
   }
// forced_mask增加, 允许入队
   q->stats.forced_mark++;
   break;
 }
// 调度内部流控的入队函数
 ret = child->enqueue(skb, child);
// 根据入队是否成功更新相关统计
 if (likely(ret == NET_XMIT_SUCCESS)) {
  sch->bstats.bytes += skb->len;
  sch->bstats.packets++;
  sch->q.qlen++;
 } else {
  q->stats.pdrop++;
  sch->qstats.drops++;
 }
 return ret;
congestion_drop:
// 拥塞丢包处理
 qdisc_drop(skb, sch);
 return NET_XMIT_CN;
}

5.7.6 重入队

static int red_requeue(struct sk_buff *skb, struct Qdisc* sch)
{
// RED私有数据
 struct red_sched_data *q = qdisc_priv(sch);
// 内部流控
 struct Qdisc *child = q->qdisc;
 int ret;
// 如果在休眠, 停止休眠
 if (red_is_idling(&q->parms))
  red_end_of_idle_period(&q->parms);
// 使用内部流控的重入队函数
 ret = child->ops->requeue(skb, child);
// 成功的话更新统计数据
 if (likely(ret == NET_XMIT_SUCCESS)) {
  sch->qstats.requeues++;
  sch->q.qlen++;
 }
 return ret;
}

5.7.7 出队

static struct sk_buff * red_dequeue(struct Qdisc* sch)
{
 struct sk_buff *skb;
// RED私有数据
 struct red_sched_data *q = qdisc_priv(sch);
// 内部流控
 struct Qdisc *child = q->qdisc;
// 调用内部流控的出队函数
 skb = child->dequeue(child);
// 如果出队成功, RED队列长度减1, 返回数据包
 if (skb)
  sch->q.qlen--;
 else if (!red_is_idling(&q->parms))
// 否则队列空, RED进入休眠状态
  red_start_of_idle_period(&q->parms);
 return skb;
}

5.7.8 丢包

static unsigned int red_drop(struct Qdisc* sch)
{
// RED私有数据
 struct red_sched_data *q = qdisc_priv(sch);
// 内部流控
 struct Qdisc *child = q->qdisc;
 unsigned int len;
// 如果内部流控结构定义的丢包函数, 执行该丢包函数, 更新统计返回
 if (child->ops->drop && (len = child->ops->drop(child)) > 0) {
  q->stats.other++;
  sch->qstats.drops++;
  sch->q.qlen--;
  return len;
 }
// 否则使RED进入休眠状态
 if (!red_is_idling(&q->parms))
  red_start_of_idle_period(&q->parms);
 return 0;
}
 
5.7.9 复位

// 就是内部流控的复位函数, RED队列长度清零
static void red_reset(struct Qdisc* sch)
{
 struct red_sched_data *q = qdisc_priv(sch);
 qdisc_reset(q->qdisc);
 sch->q.qlen = 0;
 red_restart(&q->parms);
}

5.7.10 释放

// 就是内部流控的释放函数
static void red_destroy(struct Qdisc *sch)
{
 struct red_sched_data *q = qdisc_priv(sch);
 qdisc_destroy(q->qdisc);
}

5.7.11 输出参数

static int red_dump(struct Qdisc *sch, struct sk_buff *skb)
{
// RED私有数据
 struct red_sched_data *q = qdisc_priv(sch);
 struct rtattr *opts = NULL;
// 填写RED参数结构
 struct tc_red_qopt opt = {
  .limit  = q->limit,
  .flags  = q->flags,
  .qth_min = q->parms.qth_min >> q->parms.Wlog,
  .qth_max = q->parms.qth_max >> q->parms.Wlog,
  .Wlog  = q->parms.Wlog,
  .Plog  = q->parms.Plog,
  .Scell_log = q->parms.Scell_log,
 };
// 将参数拷贝到skb数据区
 opts = RTA_NEST(skb, TCA_OPTIONS);
 RTA_PUT(skb, TCA_RED_PARMS, sizeof(opt), &opt);
// 发送数据包
 return RTA_NEST_END(skb, opts);
rtattr_failure:
 return RTA_NEST_CANCEL(skb, opts);
}

5.7.12 输出统计值

static int red_dump_stats(struct Qdisc *sch, struct gnet_dump *d)
{
// RED私有数据
 struct red_sched_data *q = qdisc_priv(sch);
// 构造标准TC统计结构, 填写相关参数
 struct tc_red_xstats st = {
// 提早丢包数: 概率丢和强迫丢
  .early = q->stats.prob_drop + q->stats.forced_drop,
// 丢包数
  .pdrop = q->stats.pdrop,
// 其他
  .other = q->stats.other,
// 被标记的包数
  .marked = q->stats.prob_mark + q->stats.forced_mark,
 };
// 返回
 return gnet_stats_copy_app(d, &st, sizeof(st));
}

5.7.13 RED类别操作

// 输出分类
static int red_dump_class(struct Qdisc *sch, unsigned long cl,
     struct sk_buff *skb, struct tcmsg *tcm)
{
 struct red_sched_data *q = qdisc_priv(sch);
 if (cl != 1)
  return -ENOENT;
// 设置tcm参数:
// handle或1
 tcm->tcm_handle |= TC_H_MIN(1);
// 信息为内部流控handle
 tcm->tcm_info = q->qdisc->handle;
 return 0;
}

// 嫁接, 增加叶子qdisc
static int red_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new,
       struct Qdisc **old)
{
// RED私有数据
 struct red_sched_data *q = qdisc_priv(sch);
// 如果没定义新流控, 用noop_qdisc
 if (new == NULL)
  new = &noop_qdisc;
 sch_tree_lock(sch);
// 将当前RED内部流控和新流控结构指针对换
 *old = xchg(&q->qdisc, new);
// 复位老流控结构
 qdisc_reset(*old);
// 流控队列长度清零
 sch->q.qlen = 0;
 sch_tree_unlock(sch);
 return 0;
}

// 获取叶子流控节点
static struct Qdisc *red_leaf(struct Qdisc *sch, unsigned long arg)
{
 struct red_sched_data *q = qdisc_priv(sch);
// 返回RED内部流控: bfifo
 return q->qdisc;
}

// 引用计数
static unsigned long red_get(struct Qdisc *sch, u32 classid)
{
 return 1;
}
// 释放计数,空函数
static void red_put(struct Qdisc *sch, unsigned long arg)
{
 return;
}

// 更改类别, 无定义
static int red_change_class(struct Qdisc *sch, u32 classid, u32 parentid,
       struct rtattr **tca, unsigned long *arg)
{
 return -ENOSYS;
}
// 删除节点, 无定义
static int red_delete(struct Qdisc *sch, unsigned long cl)
{
 return -ENOSYS;
}

// 遍历
static void red_walk(struct Qdisc *sch, struct qdisc_walker *walker)
{
// 其实也说不上遍历, 因为就只执行一次
 if (!walker->stop) {
  if (walker->count >= walker->skip)
   if (walker->fn(sch, 1, walker) < 0) {
    walker->stop = 1;
    return;
   }
  walker->count++;
 }
}
// 查找分类过滤规则, 空函数
static struct tcf_proto **red_find_tcf(struct Qdisc *sch, unsigned long cl)
{
 return NULL;
}

...... 待续 ......

猜你喜欢

转载自cxw06023273.iteye.com/blog/867326
今日推荐