TCP Hybla算法的改進是對於RTT較長的連接(例如衛星和無線網絡)可獲得與參考TCP連接(如,有線網絡)相同的瞬時發送速率B(t)。TCP的發送速率計算如下(W(t)表示t時刻的發送窗口值):
對於傳統TCP,發送窗口計算如下:
可見,發送窗口W(t)和發送速率B(t)都是與RTT成比例的,如果RTT較長的連接要達到與參考連接相同的效果,需要滿足兩個條件,第一,根據以上公式2,窗口W(t)要擺脫與RTT的關係;第二,根據公式1,要達到相同的發送速率B(t),需要對較長的RTT連接進行補償,消除RTT的影響。對於TCP Hybla算法,其引入了變量ρ,其值定義如下:
其中RTT0爲參考連接的RTT值,爲了使Hybla連接達到與參考連接相同的性能,將窗口增長函數(公式2)修改如下。首先,將窗口增長時間乘以rho,對於SlowStart階段,時間爲t; 對於擁塞避免階段,時長爲t-tγ,其中tγ爲窗口達到ssthresh時的時間值,γ爲ssthresh。這樣,窗口值W(t)將取決於選定的RTT0,不再依賴於RTT時長。再一步,繼續將窗口值W(t)乘以rho,這樣,在計算髮送速率B(t)時,消除了RTT的影響。
根據第二步的修改,原本的慢啓動閾值γ,將變爲ργ,對於所有的連接,不論RTT的值爲多少,到達閾值γ的時間都相同:
由公式4,可得到Hybla的報文發送速率如下,可見其與實際的RTT沒有關聯。
將以上公式4,轉換爲標準TCP的實現,即每個ACK報文對應的窗口增長值,得到以下公式:
TCP-Hybla將rho的最小值定義爲1,此時,窗口增長值與標準TCP相同。
Hybla預設值
內核中將參考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)");
rho值計算
由於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窗口增長
函數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<<int,
* while we will use hybla_slowstart_fraction_increment() to
* calculate 2^fract in a <<7 value.
*/
is_slowstart = 1;
increment = ((1 << min(ca->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擁塞狀態
如下函數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