免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 5388 | 回复: 5
打印 上一主题 下一主题

[网络管理] Linux内核中的随机早期检测算法(RED) [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2008-12-03 16:50 |只看该作者 |倒序浏览
好久都没有来发过贴子了,由于需要,看了一下内核中RED的实现。发上来共享之:
内核版本2.6.12,当时对比过2.6.22,22版的几本上一样,就是多了些封装函数。12要简洁些,故分析这个版本。其中有些地方还没有完全吃透,等分析明白了再补上。发先上来大家讨论:

算法描述:
http://www.zlunwen.com/computer/application/27228.htm

说得很详细了。
其中关键环节在于:

当有分组到达时:
If( 队列空)
{
    =f(time-q_time);
   Avq= (1-w)m Avq;
}
else
   Avq<—(1-w) Avq+wq;

If (MINth<= Avq <=MAXth)
{
   Count=Count+1;
   Pb=maxp((Avq- MINth)/( MAXth- MINth));

    Pa=Pb/ (1-count*P);
}
else if(Avq>= MAXth)
   丢弃分组;
else
  count=-1;


算法之关键,在于求得一个“平均队列长度(average length queue)”,然后跟两个预设阀值比较(MAXth,MINth)比较,分别处理
< MINth
[MIN,MAXth]
> MAXth
三种情况。
这其中还要注意两点:
1、而在计算平均队列长度的时候,需要对队列是否为空进行区别处理。
2、在[MINth,MAXth]中进行随机丢包时,需要按公式计算出一个随机丢包的概率。

实现分析:

struct red_sched_data
{
/* Parameters */
        u32                limit;                /* 队列长度上限        */
        u32                qth_min;                        /* 算法需要的两个两个门限值*/
        u32                qth_max;       
        u32                Rmask;
        u32                Scell_max;                /* 最大空闲时间 */
        unsigned char        flags;
        char                Wlog;                /* log(W)                */
        char                Plog;                /* random number bits        */
        char                Scell_log;
        u8                Stab[256];

/* Variables */
        unsigned long        qave;                /* 这个东东太重要了,平均队列长度 */
        int                qcount;                /* 上次丢弃分组后收到的分组个数*/
        u32                qR;                /* Cached random number */

        psched_time_t        qidlestart;        /* 队列空间开始时间                */
        struct tc_red_xstats st;
};

struct red_sched_data是RED的私有数据结构,包含了其所有需要的重要参数。

如前所述,算法中需要处理队列是否为空的情况,上述数据结构中,使用qidlestart成员来描述队列是否空:
  1. 如果为空,则记录空闲开始时间,通过宏PSCHED_GET_TIME(q->qidlestart);来完成;
  2. 如果不为空,则置为0,         通过宏PSCHED_SET_PASTPERFECT(q->qidlestart);来完成;
复制代码


例如,在出队函数中:
  1. static struct sk_buff *
  2. red_dequeue(struct Qdisc* sch)
  3. {
  4.         struct sk_buff *skb;
  5.         struct red_sched_data *q = qdisc_priv(sch);

  6.         skb = __skb_dequeue(&sch->q);
  7.         if (skb) {
  8. ……
  9.         }
  10.         [color=Red]PSCHED_GET_TIME(q->qidlestart);[/color]
  11.         return NULL;
  12. }
复制代码

如果从队列中再也取不到数据包(即队列为空,空闲了),则调用PSCHED_GET_TIME(q->qidlestart)宏将空闲开始时间设为当前时间。

同理,在数据包重入队函数中:
  1. static int
  2. red_requeue(struct sk_buff *skb, struct Qdisc* sch)
  3. {
  4.         struct red_sched_data *q = qdisc_priv(sch);

  5.         PSCHED_SET_PASTPERFECT(q->qidlestart);
复制代码

因为有数据包进入队列,队列不在空闲,则将qidlestart置为0。

因为这个东东在程序中反复用到,所以先将其分析了。

一、注册
首先,模块向QoS子系统注册自身,这一块实现跟QoS模块关系紧密,以后再来单独分析:
  1. static int __init red_module_init(void)
  2. {
  3.         return register_qdisc(&red_qdisc_ops);
  4. }
复制代码


简单地说,每一个QoS算法,都被封装成一个对像 struct Qdisc_ops:
  1. static struct Qdisc_ops red_qdisc_ops = {
  2.         .next                =        NULL,
  3.         .cl_ops                =        NULL,
  4.         .id                =        "red",
  5.         .priv_size        =        sizeof(struct red_sched_data),
  6.         .enqueue        =        red_enqueue,
  7.         .dequeue        =        red_dequeue,
  8.         .requeue        =        red_requeue,
  9.         .drop                =        red_drop,
  10.         .init                =        red_init,
  11.         .reset                =        red_reset,
  12.         .change                =        red_change,
  13.         .dump                =        red_dump,
  14.         .dump_stats        =        red_dump_stats,
  15.         .owner                =        THIS_MODULE,
  16. };
复制代码


它封装了算法的一些属性和操作。

二、初始化
初始化函数是red_init:
  1. static int red_init(struct Qdisc* sch, struct rtattr *opt)
  2. {
  3.         return red_change(sch, opt);
  4. }
复制代码


red_change用来设置RED的属性值:
  1. static int red_change(struct Qdisc *sch, struct rtattr *opt)
  2. {
  3.         struct red_sched_data *q = qdisc_priv(sch);
  4.         struct rtattr *tb[TCA_RED_STAB];
  5.         struct tc_red_qopt *ctl;

  6.         if (opt == NULL ||
  7.             rtattr_parse_nested(tb, TCA_RED_STAB, opt) ||
  8.             tb[TCA_RED_PARMS-1] == 0 || tb[TCA_RED_STAB-1] == 0 ||
  9.             RTA_PAYLOAD(tb[TCA_RED_PARMS-1]) < sizeof(*ctl) ||
  10.             RTA_PAYLOAD(tb[TCA_RED_STAB-1]) < 256)
  11.                 return -EINVAL;

  12.         ctl = RTA_DATA(tb[TCA_RED_PARMS-1]);

  13.         sch_tree_lock(sch);
  14.         q->flags = ctl->flags;
  15.         q->Wlog = ctl->Wlog;
  16.         q->Plog = ctl->Plog;
  17.         q->Rmask = ctl->Plog < 32 ? ((1<<ctl->Plog) - 1) : ~0UL;
  18.         q->Scell_log = ctl->Scell_log;
  19.         q->Scell_max = (255<<q->Scell_log);
  20.         q->qth_min = ctl->qth_min<<ctl->Wlog;
  21.         q->qth_max = ctl->qth_max<<ctl->Wlog;
  22.         q->limit = ctl->limit;
  23.         memcpy(q->Stab, RTA_DATA(tb[TCA_RED_STAB-1]), 256);

  24.         q->qcount = -1;
  25.         if (skb_queue_len(&sch->q) == 0)
  26.                 PSCHED_SET_PASTPERFECT(q->qidlestart);
  27.         sch_tree_unlock(sch);
  28.         return 0;
  29. }
复制代码

没有仔细去看过iproute2设置RED的代码,如果一对照,应该很好理解相关的参数。

二、入队函数
前面前戏整了一大串,现在写到重点了,整个算法的实现,是在入队函数中完成的,函数有点长,一步步地来。

  1.         struct red_sched_data *q = qdisc_priv(sch);

  2.         psched_time_t now;

  3.         if (!PSCHED_IS_PASTPERFECT(q->qidlestart)) {
  4.                 long us_idle;
  5.                 int  shift;

  6.                 PSCHED_GET_TIME(now);
  7.                 us_idle = PSCHED_TDIFF_SAFE(now, q->qidlestart, q->Scell_max);
  8.                 PSCHED_SET_PASTPERFECT(q->qidlestart);

  9. /*
  10.    The problem: ideally, average length queue recalcultion should
  11.    be done over constant clock intervals. This is too expensive, so that
  12.    the calculation is driven by outgoing packets.
  13.    When the queue is idle we have to model this clock by hand.

  14.    SF+VJ proposed to "generate" m = idletime/(average_pkt_size/bandwidth)
  15.    dummy packets as a burst after idle time, i.e.

  16.           q->qave *= (1-W)^m

  17.    This is an apparently overcomplicated solution (f.e. we have to precompute
  18.    a table to make this calculation in reasonable time)
  19.    I believe that a simpler model may be used here,
  20.    but it is field for experiments.
  21. */
  22.                 shift = q->Stab[us_idle>>q->Scell_log];

  23.                 if (shift) {
  24.                         q->qave >>= shift;
  25.                 } else {
  26.                         /* Approximate initial part of exponent
  27.                            with linear function:
  28.                            (1-W)^m ~= 1-mW + ...

  29.                            Seems, it is the best solution to
  30.                            problem of too coarce exponent tabulation.
  31.                          */

  32.                         us_idle = (q->qave * us_idle)>>q->Scell_log;
  33.                         if (us_idle < q->qave/2)
  34.                                 q->qave -= us_idle;
  35.                         else
  36.                                 q->qave >>= 1;
  37.                 }
  38.         }
复制代码

首先根据qidlestart判断队列如果是空间的话,则取得当前时间,并计算空闲时间总长度,然后置队列非空闲标记(因为有数据进入了)
                PSCHED_GET_TIME(now);
                us_idle = PSCHED_TDIFF_SAFE(now, q->qidlestart, q->Scell_max);
                PSCHED_SET_PASTPERFECT(q->qidlestart);
如前所述,队列空间时,平均队列长度的计算公式为:
q->qave *= (1-W)^m

shift的目的是为了计算m:
shift = q->Stab[us_idle>>q->Scell_log];
当shift求出后,就可以根据公式,计算平均队列长度了:
  1.                 if (shift) {
  2.                         q->qave >>= shift;
  3.                 } else {
  4.                         /* Approximate initial part of exponent
  5.                            with linear function:
  6.                            (1-W)^m ~= 1-mW + ...

  7.                            Seems, it is the best solution to
  8.                            problem of too coarce exponent tabulation.
  9.                          */

  10.                         us_idle = (q->qave * us_idle)>>q->Scell_log;
  11.                         if (us_idle < q->qave/2)
  12.                                 q->qave -= us_idle;
  13.                         else
  14.                                 q->qave >>= 1;
  15.                 }
复制代码


如果队列非空,则计算公式为:
avg = (1-W)*avg + W*current_queue_len
也就是
qave = qave*(1-W) + sch->qstats.backlog*W;
也就是
q->qave += sch->qstats.backlog - (q->qave >> q->Wlog);


接下来比较平均队列跟阀值的关系,如果小于MINth的话:
  1.         if (q->qave < q->qth_min) {
  2.                 q->qcount = -1;
  3. enqueue:
  4.                 if (sch->qstats.backlog + skb->len <= q->limit) {
  5.                         __skb_queue_tail(&sch->q, skb);
  6.                         sch->qstats.backlog += skb->len;
  7.                         sch->bstats.bytes += skb->len;
  8.                         sch->bstats.packets++;
  9.                         return NET_XMIT_SUCCESS;
  10.                 } else {
  11.                         q->st.pdrop++;
  12.                 }
  13.                 kfree_skb(skb);
  14.                 sch->qstats.drops++;
  15.                 return NET_XMIT_DROP;
  16.         }
复制代码

如果队列还没有超限,则入队,重新计算队列长度,计算统计信息。否则,丢弃该包,并做相应统计。

如果大于或等于MAXth:
  1.         if (q->qave >= q->qth_max) {
  2.                 q->qcount = -1;
  3.                 sch->qstats.overlimits++;     //计算溢出标志
  4. mark:
  5.                 //丢包,判断是否有ECN拥塞控制标志
  6.                                 if  (!(q->flags&TC_RED_ECN) || !red_ecn_mark(skb)) {
  7.                         q->st.early++;     //标注及丢弃数据包
  8.                         goto drop;
  9.                 }
  10.                 q->st.marked++;
  11.                 goto enqueue;
  12.         }
复制代码



如果在两个门限值之间:
  1.         if (++q->qcount) {
  2.                 /* The formula used below causes questions.

  3.                    OK. qR is random number in the interval 0..Rmask
  4.                    i.e. 0..(2^Plog). If we used floating point
  5.                    arithmetics, it would be: (2^Plog)*rnd_num,
  6.                    where rnd_num is less 1.

  7.                    Taking into account, that qave have fixed
  8.                    point at Wlog, and Plog is related to max_P by
  9.                    max_P = (qth_max-qth_min)/2^Plog; two lines
  10.                    below have the following floating point equivalent:
  11.                   
  12.                    max_P*(qave - qth_min)/(qth_max-qth_min) < rnd/qcount

  13.                    Any questions? --ANK (980924)
  14.                  */
  15.                 if (((q->qave - q->qth_min)>>q->Wlog)*q->qcount < q->qR)
  16.                         goto enqueue;
  17.                 q->qcount = 0;
  18.                 q->qR = net_random()&q->Rmask;
  19.                 sch->qstats.overlimits++;
  20.                 goto mark;
  21.         }
复制代码

开始随机丢包,未丢弃的包通过goto enqueue入队,其它的就丢弃。

这段代码的关键是
  1. if (((q->qave - q->qth_min)>>q->Wlog)*q->qcount < q->qR)
复制代码

换言之,就是根据Pb(随机丢包概率)和Pa(一个临时根据)的关系来决定。其中:
Pb = max_P*(qave - qth_min)/(qth_max-qth_min)
Pa = rnd/qcount

因为
max_P = (qth_max-qth_min)/2^Plog;
所以可得:
Pb = ((q->qave - q->qth_min)>>q->Wlog)

上式就可以简化成为:
Pb * qcount < rnd
其中,qcount即为q->qcount,rnd即为q->qR。
而q->qR的计算公式为:
q->qR = net_random()&q->Rmask;
其中掩码位的计算,在初始化中已经进行:
q->Rmask = ctl->Plog < 32 ? ((1<<ctl->Plog) - 1) : ~0UL;

丢包,是通过goto mark跳转来执行的,不过它并不是直接丢弃,而是要先判断数据包是否有没用ECN拥塞标志,如果没有,则直接丢弃。

当平均队列长度在两个阀值之间,而count又为-1时,程序会执行到这一步,重新计算qR,并将数据包入队(这一步还没有怎么看明白^o^):
        q->qR = net_random()&q->Rmask;
        goto enqueue;

丢包,直接丢弃,统计字段累加。
drop:
        kfree_skb(skb);
        sch->qstats.drops++;
        return NET_XMIT_CN;

基本上就是这些了。

[ 本帖最后由 独孤九贱 于 2008-12-3 16:54 编辑 ]

论坛徽章:
0
2 [报告]
发表于 2008-12-03 20:18 |只看该作者
收藏下,慢慢看

论坛徽章:
0
3 [报告]
发表于 2008-12-03 21:33 |只看该作者

回复 #1 独孤九贱 的帖子

这个应该放到内核源码里面,什么时候需要定制内核,什么时候好好的研究研究

论坛徽章:
0
4 [报告]
发表于 2008-12-03 23:06 |只看该作者
呵呵,我也在做qos,前段时间一直在研究htb源码。
这个red早晚也得用,收藏之,谢谢lz

论坛徽章:
49
15-16赛季CBA联赛之福建
日期:2016-06-22 16:22:002015年亚洲杯之中国
日期:2015-01-23 16:25:12丑牛
日期:2015-01-20 09:39:23未羊
日期:2015-01-14 23:55:57巳蛇
日期:2015-01-06 18:21:36双鱼座
日期:2015-01-02 22:04:33午马
日期:2014-11-25 09:58:35辰龙
日期:2014-11-18 10:40:07寅虎
日期:2014-11-13 22:47:15申猴
日期:2014-10-22 15:29:50摩羯座
日期:2014-08-27 10:49:43辰龙
日期:2014-08-21 10:47:58
5 [报告]
发表于 2008-12-26 15:21 |只看该作者
:wink:
兄弟,我收藏到本期杂志中去了,不介意吧?

论坛徽章:
0
6 [报告]
发表于 2010-03-16 00:56 |只看该作者
好东西啊,学习了
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP