TCP Hybla算法的改进是对于RTT较长的连接(例如卫星和无线网络)可获得与参考TCP连接(如,有线网络)相同的瞬时发送速率B(t)。TCP的发送速率计算如下(W(t)表示t时刻的发送窗口值):
B ( t ) = W ( t ) / R T T (1) \tag{1} B(t)=W(t)/RTT B(t)=W(t)/RTT(1)
对于传统TCP,发送窗口计算如下:
W ( t ) = { 2 t / R T T if 0 ≤ t < t γ , S S t − t γ R T T + γ if t > t γ , C A (2) \tag{2} W(t)=\begin{cases} 2^{t/RTT} & \text{ if } 0\leq t< t_{\gamma }, SS \\ \frac{t-t_{\gamma } }{RTT}+\gamma & \text{ if } t> t_{\gamma }, CA \end{cases} W(t)={2t/RTTRTTt−tγ+γ if 0≤t<tγ,SS if t>tγ,CA(2)
可见,发送窗口W(t)和发送速率B(t)都是与RTT成比例的,如果RTT较长的连接要达到与参考连接相同的效果,需要满足两个条件,第一,根据以上公式2,窗口W(t)要摆脱与RTT的关系;第二,根据公式1,要达到相同的发送速率B(t),需要对较长的RTT连接进行补偿,消除RTT的影响。对于TCP Hybla算法,其引入了变量ρ,其值定义如下:
ρ = R T T / R T T 0 (3) \tag{3} \rho = RTT/RTT_{0} ρ=RTT/RTT0(3)
其中RTT0为参考连接的RTT值,为了使Hybla连接达到与参考连接相同的性能,将窗口增长函数(公式2)修改如下。首先,将窗口增长时间乘以rho,对于SlowStart阶段,时间为t; 对于拥塞避免阶段,时长为t-tγ,其中tγ为窗口达到ssthresh时的时间值,γ为ssthresh。这样,窗口值W(t)将取决于选定的RTT0,不再依赖于RTT时长。再一步,继续将窗口值W(t)乘以rho,这样,在计算发送速率B(t)时,消除了RTT的影响。
W H ( t ) = { ρ 2 ρ t / R T T if 0 ≤ t < t γ , S S ρ [ ρ t − t γ R T T + γ ] if t > t γ , C A (4) \tag{4} W^{H}(t)=\begin{cases} \rho 2^{\rho t/RTT} & \text{ if } 0\leq t< t_{\gamma },SS \\ \rho \left [ \rho \frac{t-t_{\gamma }}{RTT}+\gamma \right ] & \text{ if } t> t_{\gamma },CA \end{cases} WH(t)={ρ2ρt/RTTρ[ρRTTt−tγ+γ] if 0≤t<tγ,SS if t>tγ,CA(4)
根据第二步的修改,原本的慢启动阈值γ,将变为ργ,对于所有的连接,不论RTT的值为多少,到达阈值γ的时间都相同:
t γ = R T T 0 log 2 γ (5) \tag{5}t_{\gamma }=RTT_{0}\log_2\gamma tγ=RTT0log2γ(5)
由公式4,可得到Hybla的报文发送速率如下,可见其与实际的RTT没有关联。
B H ( t ) = { 2 t / R T T 0 R T T 0 if 0 ≤ t < t γ , S S 1 R T T 0 [ t − t γ R T T 0 + γ ] if t > t γ , C A (6) \tag{6} B^{H}(t)=\begin{cases} \frac{2^{t/RTT_{0}}}{RTT_{0}} & \text{ if } 0\leq t< t_{\gamma },SS \\ \frac{1}{RTT_{0}}\left [ \frac{t-t_{\gamma }}{RTT_{0}}+\gamma \right ] & \text{ if } t> t_{\gamma },CA \end{cases} BH(t)={RTT02t/RTT0RTT01[RTT0t−tγ+γ] if 0≤t<tγ,SS if t>tγ,CA(6)
将以上公式4,转换为标准TCP的实现,即每个ACK报文对应的窗口增长值,得到以下公式:
W i + 1 H = { W i H + 2 ρ − 1 SS W i H + ρ 2 / W i H CA (7) \tag{7} W_{i+1}^{H}=\begin{cases} W_{i}^{H}+2^{\rho }-1 & \text{ SS } \\ W_{i}^{H}+ \rho^{2} / W_{i}^{H} & \text{ CA } \end{cases} Wi+1H={WiH+2ρ−1WiH+ρ2/WiH SS CA (7)
TCP-Hybla将rho的最小值定义为1,此时,窗口增长值与标准TCP相同。
内核中将参考RTT时长(rtt0),默认设置为25毫秒,可通过模块参数在加载时进行修改。
/* Hybla reference round trip time (default= 1/40 sec = 25 ms), in ms */
static int rtt0 = 25;
module_param(rtt0, int, 0644);
MODULE_PARM_DESC(rtt0, "reference rout trip time (ms)");
由于srtt_us中保存的为真实的SRTT值左移3位的值,所以,rho_3ls中保存的rho值也为真实的RHO值左移3位的结果,rho_3ls的值最小为8,即真实的rho值最小为1(8 >> 3)。除了计算出rho值为,如下也计算了rho值的平方值。
/* This is called to refresh values for hybla parameters */
static inline void hybla_recalc_param (struct sock *sk)
{
struct hybla *ca = inet_csk_ca(sk);
ca->rho_3ls = max_t(u32,
tcp_sk(sk)->srtt_us / (rtt0 * USEC_PER_MSEC),
8U);
ca->rho = ca->rho_3ls >> 3;
ca->rho2_7ls = (ca->rho_3ls * ca->rho_3ls) << 1;
ca->rho2 = ca->rho2_7ls >> 7;
}
函数hybla_cong_avoid在ACK报文处理流程中被调用,首先,如果当前的SRTT(Smoothed RTT)小于Hybla记录的最小RTT值,重新计算rho,并且更新Hybla的最小RTT值。之后,判断当前套接口的发送是否受到拥塞窗口的限制,如果没有,不进行窗口调整。其次,如果Hybla没有使能,调用TCP-Reno算法处理窗口增长。
/* TCP Hybla main routine.
* This is the algorithm behavior:
* o Recalc Hybla parameters if min_rtt has changed
* o Give cwnd a new value based on the model proposed
* o remember increments <1
*/
static void hybla_cong_avoid(struct sock *sk, u32 ack, u32 acked)
{
struct tcp_sock *tp = tcp_sk(sk);
struct hybla *ca = inet_csk_ca(sk);
u32 increment, odd, rho_fractions;
int is_slowstart = 0;
/* Recalculate rho only if this srtt is the lowest */
if (tp->srtt_us < ca->minrtt_us) {
hybla_recalc_param(sk);
ca->minrtt_us = tp->srtt_us;
}
if (!tcp_is_cwnd_limited(sk))
return;
if (!ca->hybla_en) {
tcp_reno_cong_avoid(sk, ack, acked);
return;
}
如果rho值为零,重新计算其值,参见函数hybla_recalc_param的介绍。
if (ca->rho == 0)
hybla_recalc_param(sk);
按照以上公式(),在慢启动阶段,窗口的增长值为2^RHO - 1,以下计算将RHO拆分为两个部分:整数部分和分数部分。其中整数部分为rho,在计算窗口增长值时,这部分通过左移计算。对于分数部分rho_fractions(左移3位后的值),由函数hybla_fraction计算幂值。
注意一下计算的increment为真实增长值左移7位的结果,计算公式2^RHO - 1最后的减1,变为减128(1 << 7);
rho_fractions = ca->rho_3ls - (ca->rho << 3);
if (tcp_in_slow_start(tp)) {
/*
* slow start
* INC = 2^RHO - 1
* This is done by splitting the rho parameter
* into 2 parts: an integer part and a fraction part.
* Inrement<<7 is estimated by doing:
* [2^(int+fract)]<<7
* that is equal to:
* (2^int) * [(2^fract) <<7]
* 2^int is straightly computed as 1<rho, 16U)) *
hybla_fraction(rho_fractions)) - 128;
} else {
对于拥塞避免阶段,窗口增加值的计算公式为RHO^2 / W,使用变量rho2_7ls除以snd_cwnd,得到的即为左移7位的窗口增长值increment,如果得到的值小于128,即小于1(128 >> 7),将snd_cwnd_cnt递增一。
/*
* congestion avoidance
* INC = RHO^2 / W
* as long as increment is estimated as (rho<<7)/window
* it already is <<7 and we can easily count its fractions.
*/
increment = ca->rho2_7ls / tp->snd_cwnd;
if (increment < 128)
tp->snd_cwnd_cnt++;
}
将拥塞窗口snd_cwnd加上以上计算的增加值(increment >> 7),对于增加值中小于1的部分,将其增加到变量snd_cwnd_cents中,当其中的值大于1时,将拥塞窗口snd_cwnd增加一。
odd = increment % 128;
tp->snd_cwnd += increment >> 7;
ca->snd_cwnd_cents += odd;
/* check when fractions goes >=128 and increase cwnd by 1. */
while (ca->snd_cwnd_cents >= 128) {
tp->snd_cwnd++;
ca->snd_cwnd_cents -= 128;
tp->snd_cwnd_cnt = 0;
}
最后,如果以上操作没有增加snd_cwnd窗口值,但是,snd_cwnd_cnt计数已经大于等于snd_cwnd,将拥塞窗口递增一。确保慢启动阶段的窗口值小于阈值ssthresh,以及窗口值不能大于窗口钳制值。
/* check when cwnd has not been incremented for a while */
if (increment == 0 && odd == 0 && tp->snd_cwnd_cnt >= tp->snd_cwnd) {
tp->snd_cwnd++;
tp->snd_cwnd_cnt = 0;
}
/* clamp down slowstart cwnd to ssthresh value. */
if (is_slowstart)
tp->snd_cwnd = min(tp->snd_cwnd, tp->snd_ssthresh);
tp->snd_cwnd = min_t(u32, tp->snd_cwnd, tp->snd_cwnd_clamp);
}
如下函数hybla_fraction,由于odds值的计算为:(ca->rho_3ls - (ca->rho << 3)),可见其仅占用了3位,odds的值最大为7。根据公式(2^fract <<7)计算,其中fract为[0, 0.1, … 0.7],得到的结果为:[128, 137, 147, 158, 169, 181, 194, 208],与以下函数中的值不相同,暂不知何故?
static inline u32 hybla_fraction(u32 odds)
{
static const u32 fractions[] = {
128, 139, 152, 165, 181, 197, 215, 234,
};
return (odds < ARRAY_SIZE(fractions)) ? fractions[odds] : 128;
}
如下函数hybla_state,仅在TCP_CA_Open状态开启Hybla算法。
static void hybla_state(struct sock *sk, u8 ca_state)
{
struct hybla *ca = inet_csk_ca(sk);
ca->hybla_en = (ca_state == TCP_CA_Open);
}
内核版本 5.0