TCP-Hybla拥塞算法

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/RTTRTTttγ+γ if 0t<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ρ[ρRTTttγ+γ] if 0t<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[RTT0ttγ+γ] if 0t<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相同。

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

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页