本文主要是介绍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 <- RRTTVAR <- 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
这篇关于TCP的RTO值估算的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!