1. 程式人生 > >Linux 核心網路協議棧 ------ 擁塞避免處理函式 tcp_reno_cong_avoid

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;  // 清空
 
         tp->snd_cwnd_cnt += cnt;
         while (tp->snd_cwnd_cnt >= tp->snd_cwnd) {  // 這裡snd_cwnd_cnt是snd_cwnd的幾倍,擁塞視窗就增加幾。
                 tp->snd_cwnd_cnt -= tp->snd_cwnd;   // ok
                 if (tp->snd_cwnd < tp->snd_cwnd_clamp)  // 判斷視窗大小
                         tp->snd_cwnd++;  // + +
         }
}

最後看一下這個函式:tcp_is_cwnd_limited,基本的意思就是判斷需不需要增大擁塞視窗。

關於gso:主要功能就是儘量的延遲資料包的傳輸,以便與在最恰當的時機傳輸資料包。如果支援gso,就有可能是tso 延遲了資料包,因此這裡會進行幾個相關的判斷,來看需不需要增加擁塞視窗。

關於burst:主要用來控制網路流量的突發性增大,也就是說當left資料(還能傳送的資料段數)大於burst值的時候,我們需要暫時停止增加視窗,因為此時有可能我們這邊資料傳送過快。其實就是一個平衡權值。

int tcp_is_cwnd_limited(const struct sock *sk, u32 in_flight)  // 第二個引數是正在網路中傳輸,還沒有收到確認的報數量
{
         const struct tcp_sock *tp = tcp_sk(sk);
         u32 left;
 
         if (in_flight >= tp->snd_cwnd)  // 比較傳送未確認和傳送擁塞視窗的大小
                 return 1;               // 如果未確認的大,那麼需要增大擁塞視窗
 
         if (!sk_can_gso(sk))  // 如果沒有gso延時處理所有包,不需要增大視窗
                 return 0;
 
         left = tp->snd_cwnd - in_flight;   // 得到還能傳送的資料包的數量
         if (sysctl_tcp_tso_win_divisor)    
                 return left * sysctl_tcp_tso_win_divisor < tp->snd_cwnd;
         else
                 return left <= tcp_max_burst(tp); // 如果還可以傳送的數量>burst,說明發送太快,不需要增大視窗。
}