TCP的RTO值估算

大致介绍一下Linux内核实现的RTO值计算方式,以及与RFC6298的不同之处。

RTT估算

static void tcp_rtt_estimator(struct sock *sk, long mrtt_us)
{
    struct tcp_sock *tp = tcp_sk(sk);
    long m = mrtt_us; /* RTT */
    u32 srtt = tp->srtt_us;

    /*  The following amusing code comes from Jacobson's article in SIGCOMM '88.
     *  Note that rtt and mdev are scaled versions of rtt and mean deviation.
     *  This is designed to be as fast as possible  m stands for "measurement".
     *
     *  On a 1990 paper the rto value is changed to:
     *  RTO = rtt + 4 * mdev
     *
     * Funny. This algorithm seems to be very broken.
     * These formulae increase RTO, when it should be decreased, increase too slowly,
     * when it should be increased quickly, decrease too quickly etc. 
  * I guess in BSD RTO takes ONE value, so that it is absolutely does not matter 
  * how to _calculate_ it. Seems, it was trap that VJ failed to avoid. 8)
     */

如果SRTT之前已经有值,新的SRTT的值等于7/8倍的原有SRTT值与1/8倍的新测量的RTT值之和,即新测量值在SRTT中比重为八分之一。 由于m为测量值,为原值,而srtt为SRTT值的8倍,以下代码计算新的srtt值, 其等于: srtt + m - srrt1/8,即srtt7/8 + m,最终结果为8倍的SRTT,即新的srtt的值。RFC6298定义的SRTT计算公式如下,其中alpha为1/8,R’为RTT测量值。

	SRTT <- (1 - alpha) * SRTT + alpha * R’

之后,计算MDEV(Mean DEViation)的值,其表示RTT的变化情况,注意RFC6298中并没有定义MDEV值,而是使用RTTVAR值。RTTVAR的计算公式如下,其中beta取值为1/4。其中SRTT为上次计算的值,而不是更新之后的值。

    RTTVAR <- (1 - beta) * RTTVAR + beta * |SRTT - R’|

内核中MDEV值的计算类似于RFC6298中的RTTVAR(Round-Trip Time VARiation)计算公式,不同的是,如果mdev测量值快速减小,幅度超过记录的MDEV(变量mdev_us表示4倍的MDEV)值时,内核将减低mdev测量值在MDEV值中的比重(再减低1/8),防止因RTT急剧减小导致RTO值增加(参见RTO值的计算),此情况下mdev测量值在MDEV新值中的比重为:(alphabeta = 1/81/4 = 1/32)。

如下计算公式,其中mdev_m表示本次的mdev测量值,mdev_us为最终的MDEV值。

    mdev_us + (mdev_m - mdev_us/4)/8
	= mdev_us + mdev_m/8 - mdev_us/32 
    = mdev_us *31/32 + mdev_m/8
	
等同于以下,注意mdev_us中保存的为4倍的MDEV值。

	MDEV * 31/32 + mdev_m/32

否则,如果本次测量的mdev值没有急剧下降,或者其值大于等于零,使用RFC中定义的计算公式获取MDEV值(得到的mdev_us为4倍MDEV): mdev_us + (mdev_m - mdev_us/4) = mdev_us *3 /4 + mdev_m。

    if (srtt != 0) {
        m -= (srtt >> 3);   /* m is now error in rtt est */
        srtt += m;      /* rtt = 7/8 rtt + 1/8 new */
        if (m < 0) {
            m = -m;     /* m is now abs(error) */
            m -= (tp->mdev_us >> 2);   /* similar update on mdev */
            /* This is similar to one of Eifel findings.
             * Eifel blocks mdev updates when rtt decreases.
             * This solution is a bit different: we use finer gain
             * for mdev in this case (alpha*beta).
             * Like Eifel it also prevents growth of rto,
             * but also it limits too fast rto decreases,
             * happening in pure Eifel.
             */
            if (m > 0)
                m >>= 3;
        } else {
            m -= (tp->mdev_us >> 2);   /* similar update on mdev */
        }
        tp->mdev_us += m;       /* mdev = 3/4 mdev + 1/4 new */

不同于RFC6298,以下使用MDEV值更新rttvar的值,只要得到的新MDEV值大于记录的最大值,更新MDEV最大值,并且在大于之前RTTVAR时,同步更新RTTVAR值,即RTTVAR值为MDEV最大值,每次测量都由可能更新RTTVAR值。这样可以避免内核在大的窗口下对过多的报文进行测量,而RTT变化很小,导致的RTO值减少的问题。

如果当前未确认的数据序号SND.UNA位于记录的rtt_seq序号之后,说明进入了下一个发送窗口期(新的RTT),更新rtt_seq为新的SND.NXT值,并且,如果mdev_max_us小于rttvar_us的值,将rttvar_us的值更新为3/4倍的rttvar_us值加上1/4倍的mdev_max_us的值。

rttvar - (rttvar - mdev_max)/4
= rttvar - rttvar *1 /4 + mdev_max/4
= rttvar * 3/4 + mdev_max/4

以上可见,内核仅允许RTTVAR值在一个RTT周期内减少一次,这样可以避免在窗口过大的情况下,进行的测量次数过多,而RTT变化很小的情况下,RTO减小的风险,造成不必要的重传。

        if (tp->mdev_us > tp->mdev_max_us) {
            tp->mdev_max_us = tp->mdev_us;
            if (tp->mdev_max_us > tp->rttvar_us)
                tp->rttvar_us = tp->mdev_max_us;
        }
        if (after(tp->snd_una, tp->rtt_seq)) {
            if (tp->mdev_max_us < tp->rttvar_us)
                tp->rttvar_us -= (tp->rttvar_us - tp->mdev_max_us) >> 2;
            tp->rtt_seq = tp->snd_nxt;
            tp->mdev_max_us = tcp_rto_min_us(sk);
        }

如果SRTT没有值,表明这是第一次测量,RFC6298规定SRTT和RTTVAR的初始值如下,R为首次RTT测量值。

    SRTT <- R
    RTTVAR <- R/2

内核中与RFC基本相同,将srtt值设置为本次测量的RTT时间值,初始偏差值mdev_us(Mean DEViation)等于SRRT值的一倍;而rttvar_us值等于MDEV与TCP_RTO_MIN(tcp_rto_min_us默认200ms)两者之间的较大值。最后记录下RTT计算时的SND.NXT值。与RFC6298不同,Linux内核使用的RTO最小值为200ms,而RFC中定义为1秒。

    } else {
        /* no previous measure. */
        srtt = m << 3;      /* take the measured time to be rtt */
        tp->mdev_us = m << 1;   /* make sure rto = 3*rtt */
        tp->rttvar_us = max(tp->mdev_us, tcp_rto_min_us(sk));
        tp->mdev_max_us = tp->rttvar_us;
        tp->rtt_seq = tp->snd_nxt;
    }
    tp->srtt_us = max(1U, srtt);
}

RTO值计算

以下RTO计算函数,其等于SRTT加上4倍的RTTVAR值。RFC6298中的计算公式为:RTO <- SRTT + max (G, K*RTTVAR),其中G为时钟精度,K等于4。

static inline u32 __tcp_set_rto(const struct tcp_sock *tp)
{
    return usecs_to_jiffies((tp->srtt_us >> 3) + tp->rttvar_us);
}

在没有退避的情况下,内核中使用函数tcp_set_rto设置RTO值。RTTVAR不太可能小于50毫秒,其中solaris和freebsd不稳定的ACK回复方式使其不可能发生,但是这与延时ACK无关,因为在拥塞窗口大于2时,不会等到延时ACK的定时器超时的时候发送ACK,3个报文完全可以触发接收端的ACK回复。实际上,Linux-2.4在一些情况下也会产生不稳定的ACK。

所以RTO计算中的4倍RTTVAR至少为200毫秒,RTO值不会小于TCP_RTO_MIN(HZ/5)。函数tcp_bound_rto设置RTO上限值。

static void tcp_set_rto(struct sock *sk)
{   
    const struct tcp_sock *tp = tcp_sk(sk);
    /* Old crap is replaced with new one. 8)
     *
     * More seriously:
     * 1. If rtt variance happened to be less 50msec, it is hallucination.
     *    It cannot be less due to utterly erratic ACK generation made
     *    at least by solaris and freebsd. "Erratic ACKs" has _nothing_
     *    to do with delayed acks, because at cwnd>2 true delack timeout
     *    is invisible. Actually, Linux-2.4 also generates erratic
     *    ACKs in some circumstances.
     */
    inet_csk(sk)->icsk_rto = __tcp_set_rto(tp);
    
    /* 2. Fixups made earlier cannot be right.
     *    If we do not estimate RTO correctly without them,
     *    all the algo is pure shit and should be replaced
     *    with correct one. It is exactly, which we pretend to do.
     */
    
    /* NOTE: clamping at TCP_RTO_MIN is not required, current algo
     * guarantees that rto is higher.
     */
    tcp_bound_rto(sk);

如下函数tcp_bound_rto将RTO值的上限设置在120秒。

static inline void tcp_bound_rto(const struct sock *sk)
{   
    if (inet_csk(sk)->icsk_rto > TCP_RTO_MAX)
        inet_csk(sk)->icsk_rto = TCP_RTO_MAX;
}
#define TCP_RTO_MAX ((unsigned)(120*HZ))

内核版本 5.0

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页