TCP-BIC Binary Increase Congestion算法

TCPIP协议 专栏收录该内容
105 篇文章 6 订阅

BIC主要包括两个部分: binary search increase 和 additive increase,这两个部分又统称为binary increase,即BIC算法名称的由来。另外,BIC还包括一个Slow-Start和Fast-Convergence阶段。

Binary search increase阶段: BIC以丢包作为拥塞窗口是否合适的判断依据,将发送丢包时的CWND设置为最大窗口值,并且将当前窗口值降低β倍(0.2),作为最小窗口值,BIC在两者之间执行二分查找获得目标窗口值。如果在新的目标窗口没有发生丢包,将其设置为新的最小窗口值,再次执行二分查找。一直到最小窗口值与最大窗口值之间的差值小于最小阈值(minimum increment - Smin)时,拥塞窗口恢复到最大窗口值。

Additive Increase阶段:在Binary search increase阶段,如果二分查找选取的中间窗口值大于最大增长值(maximum increment - Smax),为防止在一个RTT内给网络带来太大的压力,拥塞窗口增长Smax的量,直到选取的中间值小于Smax后,执行以上的Binary search increase阶段的算法。

BIC Slow Start阶段:当拥塞窗口超过当前的最大窗口值时,新的最大窗口变得未知,BIC执行Slow-Start探测最大窗口,一直到探测窗口增加值到Smax之后,BIC执行Additive Increase算法,停止Slow-Start,每个RTT周期拥塞窗口增长Smax的量。在Slow-Start阶段,每个RTT周期,拥塞窗口变化为:cwnd+1, cwnd+2, cwnd+4,…, cwnd+Smax。

Fast convergence阶段:在binary search increase阶段,发生丢包时,如果当前拥塞窗口小于上一次丢包时设置的最大拥塞窗口,表明窗口处于下降阶段,为实现快速收敛,BIC并不是将当前拥塞窗口设置为最大窗口,而是设置为:(max_win-min_win)/2),进一步降低窗口,让出网络带宽。

具体算法参见文档:Binary Increase Congestion Control for Fast,Long Distance Networks。

BIC预设参数

fast_convergence - 是否启用快速收敛。启用的话将会修正选择的最大拥塞窗口,尽快达到合理的窗口值。
low_window - 只有当拥塞窗口大于此值时,BIC算法才开始生效。默认值为14,拥塞窗口小于此值时,采用传统的Reno TCP拥塞算法控制窗口。
max_increment - 在二分查找增长拥塞窗口时,可增长的最大限值。防止拥塞窗口过快的增长给网络带来的压力,默认值为16。
beta - 窗口减少因子(Multiplicative window decrease factor),在发生丢包时,用于选择最小窗口,即最小窗口等于当前拥塞窗口*beta/BICTCP_BETA_SCALE,beta默认值为819。
initial_ssthresh - 初始的慢启动阈值ssthresh。
smooth_part - 定义在丢包发生点执行的RTT周期数量。

宏定义:

BICTCP_B - 用于寻找最大窗口和最小窗口之间的值,算法为: (max+min)/BICTCP_B。

BICTCP_BETA_SCALE - 默认值为1024。 beta/BICTCP_BETA_SCALE的结果等于(1-β)。

#define BICTCP_BETA_SCALE    1024   /* Scale factor beta calculation
                                     * max_cwnd = snd_cwnd * beta */
#define BICTCP_B        4    /* * In binary search, go to point (max+min)/N */

static int beta = 819;      /* = 819/1024 (BICTCP_BETA_SCALE) */
static int smooth_part = 20;

BIC初始化

如果指定了初始ssthresh,使用其作为初始慢启动阈值,而不是系统默认的TCP_INFINITE_SSTHRESH(0x7fffffff)。在bictcp_init初始化函数中,将其赋值给套接口的发送ssthresh变量(snd_ssthresh)。

默认情况下,延迟ACK的速率设置为2,变量delayed_ack中保存的为估算的Packets/ACKs的16倍值(ACK_RATIO_SHIFT)。

static inline void bictcp_reset(struct bictcp *ca)
{
    ca->cnt = 0;
    ca->last_max_cwnd = 0;
    ca->last_cwnd = 0;
    ca->last_time = 0;
    ca->epoch_start = 0;
    ca->delayed_ack = 2 << ACK_RATIO_SHIFT;
}
static void bictcp_init(struct sock *sk)
{
    struct bictcp *ca = inet_csk_ca(sk);

    bictcp_reset(ca);

    if (initial_ssthresh)
        tcp_sk(sk)->snd_ssthresh = initial_ssthresh;

BIC拥塞窗口更新

如下函数bictcp_cong_avoid,其在ACK报文处理的最后被调用。首先如果拥塞窗口足够使用,例如应用层发送数据不足;或者在慢启动阶段,发送窗口大于翻倍之后的发送速率,无需进行拥塞窗口调整。否则,如果处于慢启动阶段,由tcp_slow_start函数处理拥塞窗口。

以上情况都不去成立的话,由BIC算法函数bictcp_update处理拥塞窗口。

static void bictcp_cong_avoid(struct sock *sk, u32 ack, u32 acked)
{
    struct tcp_sock *tp = tcp_sk(sk);
    struct bictcp *ca = inet_csk_ca(sk);

    if (!tcp_is_cwnd_limited(sk))
        return;

    if (tcp_in_slow_start(tp))
        tcp_slow_start(tp, acked);
    else {
        bictcp_update(ca, tp->snd_cwnd);
        tcp_cong_avoid_ai(tp, ca->cnt, 1);

如果当前拥塞窗口等于上一次调整后的拥塞窗口,并且调整时间小于1/32秒(大约31毫秒),不做调整。初始情况下epoch_start等于零,表示一个BIC周期的开始,记录下开始时间戳。如果当前拥塞窗口小于预设的low_window(默认为14),不做处理,仅更新计数cnt为当前拥塞窗口。在稍后介绍的函数tcp_cong_avoid_ai中,将会看到拥塞窗口的增加值等于ack数量与cnt的比值,例如,当接收到一个ack报文时,拥塞窗口的增加值为1/cnt,在拥塞窗口小于low_window时,窗口增加值等于1/cwnd,即每个RTT周期拥塞窗口增加1,负荷RENO-TCP的定义。

static inline void bictcp_update(struct bictcp *ca, u32 cwnd)
{
    if (ca->last_cwnd == cwnd &&
        (s32)(tcp_jiffies32 - ca->last_time) <= HZ / 32)
        return;

    ca->last_cwnd = cwnd;
    ca->last_time = tcp_jiffies32;

    if (ca->epoch_start == 0) /* record the beginning of an epoch */
        ca->epoch_start = tcp_jiffies32;

    /* start off normal */
    if (cwnd <= low_window) {
        ca->cnt = cwnd;
        return;
    }

线性增长阶段。如果当前拥塞窗口CWND小于最大拥塞窗口(发生丢包时的窗口值),计算二者之差的四分之一(BICTCP_B默认为4),即拥塞窗口要增加的目标值(变量dist)。如果dist大于max_increment(默认为16),BIC算法认为窗口增加值过大,将其钳制在max_increment值(默认16)。变量cnt使用cwnd / max_increment的值,即在下一个RTT周期,拥塞窗口值增加max_increment的量。

否则,进入二分查找增长阶段。如果dist小于等于1,变量cnt赋值为(cwnd * smooth_part) / BICTCP_B,默认情况下smooth_part为20个RTT,即在拥塞窗口增加到接近上次最大窗口值时(<=1),在此位置持续运行20个RTT周期,此阶段内窗口将仅增加BICTCP_B(4)的量,增加算法的稳定性,因为此处这是上次发生丢包的位置。

最后,以上两个条件都不成立的话,变量cnt赋值为cwnd / dist,即使用二分查找(内核实现为1/4)结果作为拥塞窗口增长值。

    /* binary increase */
    if (cwnd < ca->last_max_cwnd) {
        __u32   dist = (ca->last_max_cwnd - cwnd) / BICTCP_B;

        if (dist > max_increment)
            /* linear increase */
            ca->cnt = cwnd / max_increment;
        else if (dist <= 1U)
            /* binary search increase */
            ca->cnt = (cwnd * smooth_part) / BICTCP_B;
        else
            /* binary search increase */
            ca->cnt = cwnd / dist;

如果当前拥塞窗口CWND大于等于最大拥塞窗口,并且,小于最大拥塞窗口与BICTCP_B(4)之和,进入慢启动阶段,变量cnt的值为:(cwnd * smooth_part) / BICTCP_B,与上所述类似,为保持稳定性,在此处运行20个RTT周期(smooth_part),以观察是否发生丢包。否则,如果CWND值位于还没有超出最大窗口值与max_increment*(BICTCP_B-1)之和阶段,变量cnt的值为:(cwnd * (BICTCP_B-1))/ (cwnd - ca->last_max_cwnd),即在此3个RTT周期(BICTCP_B-1=3),拥塞窗口的涨幅为:(cwnd - ca->last_max_cwnd)。

最后,以上两个条件都不成立,进入线性增长阶段,变量cnt赋值为cwnd / max_increment,之后每个RTT周期拥塞窗口以max_increment为步长增加。

    } else {
        /* slow start AMD linear increase */
        if (cwnd < ca->last_max_cwnd + BICTCP_B)
            /* slow start */
            ca->cnt = (cwnd * smooth_part) / BICTCP_B;
        else if (cwnd < ca->last_max_cwnd + max_increment*(BICTCP_B-1))
            /* slow start */
            ca->cnt = (cwnd * (BICTCP_B-1))
                / (cwnd - ca->last_max_cwnd);
        else
            /* linear increase */
            ca->cnt = cwnd / max_increment;
    }

如果处于BIC慢启动阶段,或者链路使用率非常低,不宜过多的增加拥塞窗口,这里将在每个RTT增加5%窗口。

    /* if in slow start or link utilization is very low */
    if (ca->last_max_cwnd == 0) {
        if (ca->cnt > 20) /* increase cwnd 5% per RTT */
            ca->cnt = 20;
    }

以下考虑接收端Delayed-ACK的影响,接收到的ACK报文必然减小,这里将cnt转换为对应的装换之后的ACK报文数量值。例如,初始情况下delayed_ack比值为2,如果算法计算得到的cnt值为20,那这里最终的cnt值为20/2等于10,即只要接收到10个ACK报文即可满足算法要求。

    ca->cnt = (ca->cnt << ACK_RATIO_SHIFT) / ca->delayed_ack;
    if (ca->cnt == 0)           /* cannot be zero */
        ca->cnt = 1;
}

最后,在函数bictcp_cong_avoid中调用tcp_cong_avoid_ai计算拥塞窗口,参数w为以上计算的变量cnt的值,参数acked固定为1,即每次进入此函数,将拥塞窗口计数变量(snd_cwnd_cnt)递增一。如果一开始snd_cwnd_cnt大于等于w,清空snd_cwnd_cnt,将拥塞窗口加一。

如果snd_cwnd_cnt递增一之后,大于等于w,拥塞窗口CWND增加tp->snd_cwnd_cnt / w 的整数值。

/* In theory this is tp->snd_cwnd += 1 / tp->snd_cwnd (or alternative w),
 * for every packet that was ACKed.
 */
void tcp_cong_avoid_ai(struct tcp_sock *tp, u32 w, u32 acked)
{   
    /* If credits accumulated at a higher w, apply them gently now. */
    if (tp->snd_cwnd_cnt >= w) {
        tp->snd_cwnd_cnt = 0;
        tp->snd_cwnd++;
    }
    
    tp->snd_cwnd_cnt += acked;
    if (tp->snd_cwnd_cnt >= w) {
        u32 delta = tp->snd_cwnd_cnt / w;
        
        tp->snd_cwnd_cnt -= delta * w;
        tp->snd_cwnd += delta;
    }   
    tp->snd_cwnd = min(tp->snd_cwnd, tp->snd_cwnd_clamp);
}   

ssthresh计算

套接口在进入TCP_CA_CWR、TCP_CA_Recovery或者TCP_CA_Loss状态时,调用bictcp_recalc_ssthresh函数计算慢启动阈值。首先,进入以上的三种拥塞状态,意味着拥塞的发生,结束BIC增长周期,epoch_start设置为零。

如果当前拥塞窗口CWND(当前的最大拥塞窗口),小于之前记录的最大拥塞窗口,表明链路可用带宽在减少,例如网络中增加了新的流,如果启用了快速收敛功能,将让出更多的空间给新增流,最大拥塞窗口降低:

(BICTCP_BETA_SCALE - beta)                             (BICTCP_BETA_SCALE + beta)
—————————————————————————— 倍,即得到的最大CWND的值为: ——————————————————————————— * snd_cwnd。
(2 * BICTCP_BETA_SCALE)                                  (2 * BICTCP_BETA_SCALE)

反之,如果CWND大于等于之前的最大拥塞窗口,或者没有启动快速收敛,最大CWND设置为当前窗口值。

static u32 bictcp_recalc_ssthresh(struct sock *sk)
{
    const struct tcp_sock *tp = tcp_sk(sk);
    struct bictcp *ca = inet_csk_ca(sk);

    ca->epoch_start = 0;    /* end of epoch */

    /* Wmax and fast convergence */
    if (tp->snd_cwnd < ca->last_max_cwnd && fast_convergence)
        ca->last_max_cwnd = (tp->snd_cwnd * (BICTCP_BETA_SCALE + beta))
            / (2 * BICTCP_BETA_SCALE);
    else
        ca->last_max_cwnd = tp->snd_cwnd;

如果当前拥塞窗口CWND小于预设的最低窗口,安装RENO算法,将CWND减低一半。否则,CWND减低:

BICTCP_BETA_SCALE - beta                                 beta
————————————————————————— 倍, 即减低之后的CWND等于:  ———————————————————— * snd_cwnd 
   BICTCP_BETA_SCALE                                  BICTCP_BETA_SCALE

默认情况下beta等于819,等式为:819/1024 * snd_cwnd,约等于0.8*snd_cwnd。

    if (tp->snd_cwnd <= low_window)
        return max(tp->snd_cwnd >> 1U, 2U);
    else
        return max((tp->snd_cwnd * beta) / BICTCP_BETA_SCALE, 2U);

BIC拥塞状态处理

如下函数bictcp_state,仅处理TCP_CA_Loss拥塞状态,此状态下复位BIC相关变量,开始新的BIC增长周期。

static void bictcp_state(struct sock *sk, u8 new_state)
{
    if (new_state == TCP_CA_Loss)
        bictcp_reset(inet_csk_ca(sk));
}

数据报文与ACK比率

函数bictcp_acked中,新的数据报文数量与ACK报文数量的比值,等于旧值与新值的加权和,其中旧的比值占15/16,而新确认数据报文量占比为1/16。注意仅在TCP_CA_Open拥塞状态更新delayed_ack值。

采样变量pkts_acked表示当前ACK确认的数据报文数量,即当前的数据报文数量与相应ACK报文量的比值为pkts_acked/1,此值在最终值中占比1/16。

/* Track delayed acknowledgment ratio using sliding window
 * ratio = (15*ratio + sample) / 16
 */
static void bictcp_acked(struct sock *sk, const struct ack_sample *sample)
{
    const struct inet_connection_sock *icsk = inet_csk(sk);

    if (icsk->icsk_ca_state == TCP_CA_Open) {
        struct bictcp *ca = inet_csk_ca(sk);

        ca->delayed_ack += sample->pkts_acked -
            (ca->delayed_ack >> ACK_RATIO_SHIFT);

内核版本 5.0

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值