Linux 核心網路協議棧 ------ 擁塞避免處理函式 tcp_reno_cong_avoid
阿新 • • 發佈:2018-12-22
慢啟動和快速重傳擁塞避免演算法,函式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,說明發送太快,不需要增大視窗。 }