zoukankan      html  css  js  c++  java
  • TCP拥塞控制算法内核实现剖析(十)

    内核版本:3.2.12

    主要源文件:linux-3.2.12/ net/ ipv4/ tcp_veno.c

    主要内容:Veno的原理和实现

    Author:zhangskd @ csdn blog

    概要

    Veno结合了Vegas和Reno,故得此名。

    Veno的主要目的在于区分随机丢包和无线丢包。

    Vegas能够测量网络瓶颈路由器中属于此连接的数据包个数,Veno正是利用这一变量来区分随机丢包和

    拥塞丢包,并采取不同的措施。

    Veno也改进了窗口增长函数,当网络瓶颈路由器中属于此连接的数据包个数超过一定值时,放缓窗口增长速度。

    原理

    我们通过3个问题来了解Veno的原理。

    (1)如何区分随机丢包和拥塞丢包?

    Vegas能够测量网络瓶颈路由器中属于此连接的数据包的个数diff,并以此来进行主动的拥塞控制,避免数据包的丢失。

    Veno并不进行主动的拥塞控制,而是利用diff来区分随机丢包和拥塞丢包。

    当检测到丢包时:

    (1) diff < 3 ,说明此时路由器缓存的数据包不多,路由器很可能没有拥塞,所以判定此丢包为随机丢包。

          由于没有发生拥塞,所以拥塞窗口和阈值的减小幅度不宜太大。

          拥塞窗口和阈值设置为:0.8 * snd_cwnd。

    (2) diff >= 3,说明此时路由器缓存的数据包较多,路由器很可能发生拥塞,所以判定此丢包为拥塞丢包。

          由于发生了拥塞,所以拥塞窗口和阈值的减小幅度需要适当加大。

          拥塞窗口和阈值设置为:0.5 * snd_cwnd。

    (2)拥塞窗口增长函数的改进

    (1) diff < 3,说明此时路由器缓存的数据包不多,采用和Reno相同的窗口增长函数,即每个RTT后cwnd增加一个数据包。

    (2) diff >= 3,说明此时路由器缓存的数据包较多,不久之后可能发生拥塞,所以应该放缓窗口增长速度,

          这样就可以延迟拥塞事件的到来,使cwnd保持在接近网络容量处较长时间,从而达到减少拥塞丢包

          和增加网络带宽利用率的效果。

         在此期间,每两个RTT才使cwnd增加一个数据包,拥塞窗口增长速度减半。

    (3)如何测量网络瓶颈路由器中属于此连接的数据包个数diff?

    diiff的测量方法和Vegas中的相同,这里再简单描述下(具体可见前面blog):

    期望吞吐量:Expected = cwnd / baseRTT

    实际吞吐量:Actual = cwnd / minRTT

    吞吐量差值:Diff = Expected - Actual

    其中,baseRTT代表当路由器缓存中没有数据包时的RTT值。minRTT为上个RTT的测量值,由于此时路由器中已有数据包,

    需要排队,故minRTT > baseRTT。

    网络瓶颈路由器中缓存此连接的数据包个数:

    diff = Diff * baseRTT = (Expected - Actual) * baseRTT

    参数和变量

    #define V_PARAM_SHIFT 1
    static const int beta = 3 << V_PARAM_SHIFT;

    beta是一个阈值,表示网络瓶颈路由器中缓存的属于此连接的数据包的拥塞临界值。

    /* Veno variables */
    
    struct veno {
            /* 表示是否使用Veno,只有在Open态才能使用。*/
            u8 doing_veno_now; 
    
            /* 在Vegas中是表示上个RTT内得到的RTT样本的个数,但是在Veno中却没用到,
              * 形同虚设。
              */
            u16 cntrtt;
    
            /* 在Vegas中表示上个RTT内得到的RTT样本中的最小值,用于过滤delayed ACK,
              * 每个RTT才重置一次。而Veno却每个ACK重置一次,所以代表即时rtt。
              */
            u32 minrtt;
    
            /* 连接的最小RTT,代表propagation delay。只有在连接刚建立或者idle后才重置,
              * 在其它情况下实时更新。
              */
            u32 basertt;
    
            /* decide whether to increase cwnd,在拥塞避免阶段使用,当diff > 3时,在0和1转换,
              * 使cwnd两个RTT才加一。
              */
            u32 inc; 
    
            /* calculate the diff rate,计算网络瓶颈路由器中缓存的属于此连接的数据包的个数,
              * 是衡量网络是否处于拥塞状态的标准,从而判断丢包是随机丢包还是无线丢包,
              * 判断拥塞避免阶段是否应该放缓拥塞窗口增长速度。
              */
            u32 diff;
    }

    函数

    /* There are several situations when we must "re-start" Veno:
     * (1) when a connection is establish
     * (2) after an RTO
     * (3) after fast recovery
     * (4) when we send a packet and there is no outstanding unacknowledged
     *       data (restarting an idle connection)
     */
    static inline void veno_enable (struct sock *sk)
    {
        struct veno *veno = inet_csk_ca(sk);
    
        /* turn on Veno */
        veno->doing_veno_now = 1;
        veno->minrtt = 0x7fffffff;
    }
    
    static inline void veno_disable (struct sock *sk)
    {
        struct veno *veno = inet_csk_ca(sk);
    
        /* turn off Veno */
        veno->doing_veno_now = 0;
    }
    
    static void tcp_veno_init (struct sock *sk)
    {
        struct veno *veno = inet_csk_ca(sk);
    
        veno->basertt = 0x7fffffff;
        veno->inc = 1;
        veno_enable(sk);
    }
    
    static void tcp_veno_state(struct sock *sk, u8 ca state)
    {
        if (ca_state == TCP_CA_Open)
            veno_enable(sk);
        else
            veno_disable(sk);
    }
    
    static void tcp_veno_cwnd_event (struct sock *sk, enum tcp_ca_event event)
    {
        if (event == CA_EVENT_CWND_RESTART || event == CA_EVENT_TX_START)
            tcp_veno_init(sk);
    }
    

    我们来看一下以上各个状态的转换:

    (1)当连接刚建立时,触发CA_EVENT_TX_START事件,调用

              tcp_veno_cwnd_event(sk, CA_EVENT_TX_START),其中又调用

              tcp_veno_init(sk),做了以下事情:

              basertt = 0x7fffffff; /* 重置basertt */

              minrtt = 0x7fffffff; /* 重置minrtt */

              inc = 1;

              doing_veno_now = 1;

    (2)从其它拥塞状态进入Open时,调用tcp_veno_state(sk, TCP_CA_Open),

             又调用veno_enable(sk),做了以下事情:

              doing_veno_now = 1; /* 表示使用veno,在非Open态置为0*/

              minrtt = 0x7fffffff; /* 重置minrtt */

    (3)从Open态进入其它拥塞状态时,调用tcp_veno_state(sk, ca_state),

              ca_state = TCP_CA_Recovery | TCP_CA_Loss,调用veno_disable(sk),

              doing_veno_now = 0; 

              表示在Recovery或Loss等其它非Open状态不能使用Veno。

    (4)闲置的连接重新开始传输数据,restarting an idle connection。

              触发了CA_EVENT_CWND_RESTART事件,调用

             tcp_veno_cwnd_event(sk, CA_EVENT_CWND_RESTART),其中又调用

             tcp_veno_init(sk),做的事情和(1)连接刚建立一样。 

    tcp_veno_pkts_acked()在每收到ACK时调用,用于更新basertt和minrtt。

    /* Do rtt sampling needed for Veno. */
    static void tcp_veno_pkts_acked (struct sock *sk, u32 cnt, s32 rtt_us)
    {
        struct veno *veno = inet_csk_ca(sk);
        u32 vrtt;
    
        if (rtt_us < 0)
            return;
    
        /* Never allow zero rtt or baseRTT */
        vrtt = rtt_us + 1;
    
        /* Filter to find propagation delay : */
        if (vrtt < veno->basertt)
            veno->basertt = vrtt;
    
        /* 和vegas的不一样,minrtt并不是上个RTT内的最小采样值,而是即时的rtt,
          * 因为每处理一个ACK后,minrtt都置为0x7fffffff。
          */
        veno->minrtt = min(veno->minrtt, vrtt);
    
        /* 这个cntrtt形同虚设,因为它根本没清零重置过。*/
        veno->cntrtt ++;
    }

    tcp_veno_cong_avoid()为核心函数,主要用于计算diff、根据窗口增长函数调整cwnd。

    static void tcp_veno_cong_avoid (struct sock *sk, u32 ack, u32 in_flight)
    {
        struct tcp_sock *tp = tcp_sk(sk);
        struct veno *veno = inet_csk_ca(sk);
    
        /* 只允许在Open态使用veno,计算diff和调整cwnd。*/
        if (! veno->doing_veno_now) {
            tcp_reno_cong_avoid(sk, ack, in_flight);
            return;
        }
    
        /* limited by applications */
        if (! tcp_is_cwnd_limited(sk, in_flight))
            return;
    
        /* 既然cntrtt没清零过,这个判断式毫无意义。*/
        if (veno->cntrtt <= 2) {
            tcp_reno_cong_avoid(sk, ack, in_flight);
    
        } else {
            u64 target_cwnd;
            u32 rtt;
    
            rtt = veno->minrtt;
    
            target_cwnd = tp->snd_cwnd * veno->basertt;
            target_cwnd <<= V_PARAM_SHIFT;
            do_div(target_cwnd, rtt);
    
            /* 跟Vegas中diff的计算一样,只是这里乘2 */
            veno->diff = (tp->snd_cwnd << V_PARAM_SHIFT) - target_cwnd;
    
            /* 处于慢启动阶段*/
            if (tp->snd_cwnd <= tp->snd_ssthresh) {
                tcp_slow_start(tp);
    
            } else { /* 处于拥塞避免阶段 */
                /* In the non-congestive state, increase cwnd every rtt。判断网络不处于拥塞,
                   * 则cwnd的增长和Reno一样。
                   */
                if (veno->diff < beta) {
                    tcp_cong_avoid_ai(tp, tp->snd_cwnd);
    
                } else {
                    /* In the congestive state, increasse cwnd every other rtt.
                     * 判断网络拥塞,则每两个RTT才使cwnd增加一个。
                        */
                    if (tp->snd_cwnd_cnt >= tp->snd_cwnd) {
                        if (veno->inc && tp->snd_cwnd < tp->snd_cwnd_clamp) {
                            tp->snd_cwnd++;
                            veno->inc = 0;
                        } else
                            veno->inc = 1;
                        tp->snd_cwnd_cnt = 0;
                    } else
                        tp->snd_cwnd_cnt++;
                }
            }
            if (tp->snd_cwnd < 2)
                tp->snd_cwnd = 2;
            else if (tp->snd_cwnd > tp->snd_cwnd_clamp)
                tp->snd_cwnd = tp->snd_cwnd_clamp;
        }
    
        /* 重置minrtt,所以minrtt是每个ACK的RTT测量值*/
        veno->minrtt = 0x7fffffff;
    }

    丢包后重新设置慢启动阈值,这就是区分随机丢包和拥塞丢包的函数。

    static u32 tcp_veno_ssthresh (struct sock *sk)
    {
        const struct tcp_sock *tp = tcp_sk(sk);
        struct veno *veno = inet_csk_ca(sk);
    
        /* in non-congestive state, cut cwnd by 1/5,如果判断为随机丢包,那么慢启动
         * 阈值的减小幅度为0.2。
         */
        if (veno->diff < beta)
            return max(tp->snd_cwnd * 4 / 5, 2U);
        else
            /* in congestive state, cut cwnd by 1/2,如果判断为拥塞丢包,那么慢启动阈值
              * 的减小幅度为0.5。
              */
             return max(tp->snd_cwnd >> 1U, 2U);
    }

    笔者评价

    在Vegas中,diff是每个RTT后才计算一次的,因为其中的minrtt的取值是上个RTT内的最小采样值。

    这样做得好处是:由于minrtt是多个样本中的最小值,所以可以避免一些异常的RTT样本,比如

    delayed ACK。minrtt能代表一段时间内的网络瓶颈路由器的情况,计算得到的diff会更准确一些。

    在Veno中,diff是每收到一个ACK计算一次的,因为其中的minrtt的取值是本次ACK的RTT测量值。

    这样一来,diff的值可能会有偏差。

    这两种方法的区别在于对diff准确性和检测及时性的权衡。

    Vegas注重于diff的准确性,所以用一个RTT的时间来测量。

    Veno注重于检测的及时性,对每个ACK都做diff计算,为了更及时的检测到网络拥塞。

    因为以上原因,Veno可能会出现误判,比如一个ACK的RTT值偏大,却不是由于路由器拥塞,

    但是计算得到的diff可能会判定路由器拥塞,这样一些随机丢包可能会被判定成拥塞丢包。

    veno在随机丢包率较高的无线网络中有较好的表现。

    veno能够区分随机丢包和拥塞丢包,因此避免了随机丢包时不必要的降低吞吐量。

    veno改进了窗口增长函数,试图使cwnd停留在接近网络容量处较长时间,延迟丢包事件的到来,

    这样不仅能减少丢包,还能提高吞吐量。但实际上这也会导致veno的带宽竞争力变弱。

    veno的不足:它对拥塞丢包的判断有着较高的准确率,但是对随机丢包的判断准确率不高,经常

    把随机丢包判成拥塞丢包。所以它对随机丢包判断的准确率还有待提高。

    此外,beta设为3纯粹是一个经验值,很多场合并不适用。

  • 相关阅读:
    PTA(Advanced Level)1037.Magic Coupon
    PTA(Advanced Level)1033.To Fill or Not to Fill
    PTA(Basic Level)1020.月饼
    PTA(Advanced Level)1048.Find Coins
    PTA(Advanced Level)1050.String Subtraction
    PTA(Advanced Level)1041.Be Unique
    PTA(Basci Level)1043.输出PATest
    PTA(Basic Level)1039.到底买不买
    PTA(Basic Level)1033.旧键盘打字
    PTA(Advanced Level)1083.List Grades
  • 原文地址:https://www.cnblogs.com/aiwz/p/6333326.html
Copyright © 2011-2022 走看看