日期:2014-05-16  浏览次数:20869 次

Linux 内核网络协议栈 ------ 拥塞避免处理函数 tcp_reno_cong_avoid


慢启动和快速重传拥塞避免算法,函数tcp_reno_cong_avoid

在“慢开始”阶段,每收到一个ACK,cwnd++一次,那么一个RTT之后,cwnd就会加倍

拥塞避免阶段,其实就是在一个RTT时间内将cwnd++一次( 注意在不丢包的情况下 )


/*
 * TCP Reno congestion control
 * This is special case used for fallback as well.
 */
/* This is Jacobson's slow start and congestion avoidance.
 * SIGCOMM '88, p. 328.
 */
void tcp_reno_cong_avoid(struct sock *sk, u32 ack, u32 in_flight)
{
         struct tcp_sock *tp = tcp_sk(sk);  // 获取tcp_sock
         // 函数返回1说明拥塞窗口被限制,我们需要增加拥塞窗口,否则的话,就不需要增加拥塞窗口。
         if (!tcp_is_cwnd_limited(sk, in_flight))  // 是否已经达到拥塞窗口的限制值(1)
                 return;

         /* In "safe" area, increase. */
         if (tp->snd_cwnd <= tp->snd_ssthresh)   // 如果发送窗口大小还 比 慢开始门限小,那么还是慢开始处理
                 tcp_slow_start(tp);   // 下面进入慢开始处理 (2)
         /* In dangerous area, increase slowly. */
         else if (sysctl_tcp_abc) {   // 否则进入拥塞避免阶段!!每个RTT时间就加1
                 /* RFC3465: Appropriate Byte Count
                  * increase once for each full cwnd acked   // 基本思想就是:经过一个RTT时间就将snd_cwnd增加一个单位!
                  */                                         // 一个RTT时间可以认为是当前拥塞窗口发送出去的数据的所有ACK都被接收到
                 if (tp->bytes_acked >= tp->snd_cwnd*tp->mss_cache) {   // 当前的拥塞窗口的所有段都被ack了,窗口才被允许增加。
                         tp->bytes_acked -= tp->snd_cwnd*tp->mss_cache; // ACK处理过的及删除去了
                         if (tp->snd_cwnd < tp->snd_cwnd_clamp)   // 不允许发送窗口大小超过snd_cwnd_clamp值
                                 tp->snd_cwnd++;
                 }
         } else {   // 每接收到一个ACK,窗口增大(1/snd_cwnd),使用cnt计数
                 /* In theory this is tp->snd_cwnd += 1 / tp->snd_cwnd */
                 if (tp->snd_cwnd_cnt >= tp->snd_cwnd) {        // 线性增长计数器 >= 阈值 
                         if (tp->snd_cwnd < tp->snd_cwnd_clamp) // 如果窗口还没有达到阈值
                                 tp->snd_cwnd++;                // 那么++增大窗口
                         tp->snd_cwnd_cnt = 0;
                 } else
                         tp->snd_cwnd_cnt++;   // 否则仅仅是增大线性递增计数器
         }
}


下面看一下“慢开始”算法:

void tcp_slow_start(struct tcp_sock *tp)   // 每到达一个ACK,snd_cwnd就加1。这意味着每个RTT,拥塞窗口就会翻倍。
{
         int cnt; /* increase in packets */
 
         /* RFC3465: ABC Slow start
          * Increase only after a full MSS of bytes is acked
          *
          * TCP sender SHOULD increase cwnd by the number of
          * previously unacknowledged bytes ACKed by each incoming
          * acknowledgment, provided the increase is not more than L
          */
         if (sysctl_tcp_abc && tp->bytes_acked < tp->mss_cache)    // 如果ack确认的数据少于一个MSS大小,不需要增大窗口
                 return;
         // 限制cnt的值
         if (sysctl_tcp_max_ssthresh > 0 && tp->snd_cwnd > sysctl_tcp_max_ssthresh) // 发送窗口超过最大门限值
                 cnt = sysctl_tcp_max_ssthresh >> 1;     /* limited slow start */   // 窗口减半~~~~~
         else
                 cnt = tp->snd_cwnd;                     /* exponential increase */ // 否则还是原来的窗口
 
         /* RFC3465: ABC
          * We MAY increase by 2 if discovered delayed ack
          */
         if (sysctl_tcp_abc > 1 && tp->bytes_acked >= 2*tp->mss_cache) // 如果启动了延迟确认,那么当接收到的ACK大于等于两个MSS的时候才加倍窗口大小
                 cnt <<= 1;
         tp->bytes_acked = 0;  // 清空