9.4 ER(Early Retransmit)定時器

9.4.1 Why

        TCP發送的數據如果丟失則快速重傳算法會立即重傳數據而不用等到重傳定時器超時,從而快速地恢復數據。如果發送端接收不到足夠數量(一般來說是3個)的ACK,則快重傳算法無法起作用,這個時候就只能等待RTO超時。ER算法主要是爲了解決這個問題。在下面的條件下,就會導致收不到足夠的ACK:

(1)擁塞窗口比較小

(2)窗口中一個很大數量的段丟失或者在傳輸的結尾處發生了丟包

        如果滿足了上面的兩個條件,那麼就會發生髮送端由於接收不到足夠數量的ACK導致快重傳算法無法生效。比如擁塞窗口是3,然後第一個段丟失了,那麼理論上最多發送段只可能收到2個重複的ACK,此時由於快重傳要求3個重複的ack,那麼發送端將會等待RTO超時,然後重傳第一個段。

        在上面的第二個條件中,有兩種可能性,其中ER算法是爲了解決第一種可能性(也就是當連續的很多段丟失)。而第二種情況則需要TLP(Tail Loss Probe)來解決。

        接下來來描述一下ER的算法。ER可以基於兩種模式,一種是基於字節的,一種是基於段(segment-based)的,Linux中的ER是基於段的。ER算法會在小窗口下(flight count 小於4)減小觸發快重傳的重複ACK的閾值,比如減小到1或者2。而在Linux的實現中爲了防止假超時會加上一個延遲再重傳數據,這個功能就靠ER定時器實現。

9.4.2 When

        TCP在收到ACK時會調用tcp_fastretrans_alert函數判斷是否需要快速重傳:

2745 static void tcp_fastretrans_alert(struct sock *sk, int pkts_acked,
2746                   int prior_sacked, int prior_packets,
2747                   bool is_dupack, int flag)
2748 {
...
2828         if (!tcp_time_to_recover(sk, flag)) {
2829             tcp_try_to_open(sk, flag, newly_acked_sacked);
2830             return;
2831         }
...
 
        tcp_time_to_recover函數決定什麼時候進入Recovery狀態
2076 static bool tcp_time_to_recover(struct sock *sk, int flag)
2077 {
2078     struct tcp_sock *tp = tcp_sk(sk);
2079     __u32 packets_out;
2080 
2081     /* Trick#1: The loss is proven. */
2082     if (tp->lost_out)
2083         return true;
2084 
2085     /* Not-A-Trick#2 : Classic rule... */
2086     if (tcp_dupack_heuristics(tp) > tp->reordering)
2087         return true;
2088 
2089     /* Trick#3 : when we use RFC2988 timer restart, fast
2090      * retransmit can be triggered by timeout of queue head.
2091      */
2092     if (tcp_is_fack(tp) && tcp_head_timedout(sk))
2093         return true;
2094 
2095     /* Trick#4: It is still not OK... But will it be useful to delay
2096      * recovery more?
2097      */
2098     packets_out = tp->packets_out;
2099     if (packets_out <= tp->reordering &&
2100         tp->sacked_out >= max_t(__u32, packets_out/2, sysctl_tcp_reordering) &&
2101         !tcp_may_send_now(sk)) {  //檢查Nagle算法等
2102         /* We have nothing to send. This connection is limited
2103          * either by receiver window or by application.
2104          */
2105         return true;
2106     }
2107 
2108     /* If a thin stream is detected, retransmit after first
2109      * received dupack. Employ only if SACK is supported in order
2110      * to avoid possible corner-case series of spurious retransmissions
2111      * Use only if there are no unsent data.
2112      */
2113     if ((tp->thin_dupack || sysctl_tcp_thin_dupack) &&
2114         tcp_stream_is_thin(tp) && tcp_dupack_heuristics(tp) > 1 &&
2115         tcp_is_sack(tp) && !tcp_send_head(sk))
2116         return true;
2117 
2118     /* Trick#6: TCP early retransmit, per RFC5827.  To avoid spurious
2119      * retransmissions due to small network reorderings, we implement
2120      * Mitigation A.3 in the RFC and delay the retransmission for a short
2121      * interval if appropriate.
2122      */
2123     if (tp->do_early_retrans && !tp->retrans_out && tp->sacked_out &&
2124         (tp->packets_out >= (tp->sacked_out + 1) && tp->packets_out < 4) &&
2125         !tcp_may_send_now(sk))
2126         return !tcp_pause_early_retransmit(sk, flag);
2127 
2128     return false;
2129 } 
        tcp_pause_early_retransmit函數是唯一能夠設置ER定時器的函數:
1947 static bool tcp_pause_early_retransmit(struct sock *sk, int flag)
1948 {
1949     struct tcp_sock *tp = tcp_sk(sk);
1950     unsigned long delay;
1951 
1952     /* Delay early retransmit and entering fast recovery for
1953      * max(RTT/4, 2msec) unless ack has ECE mark, no RTT samples
1954      * available, or RTO is scheduled to fire first.
1955      */
1956     if (sysctl_tcp_early_retrans < 2 || sysctl_tcp_early_retrans > 3 ||
1957         (flag & FLAG_ECE) || !tp->srtt)
1958         return false;
1959 
1960     delay = max_t(unsigned long, (tp->srtt >> 5), msecs_to_jiffies(2));
1961     if (!time_after(inet_csk(sk)->icsk_timeout, (jiffies + delay))) <span><span class="comment">/* 如果超時重傳定時器更早超時*/</span></span>
1962         return false;
1963 
1964     inet_csk_reset_xmit_timer(sk, ICSK_TIME_EARLY_RETRANS, delay,
1965                   TCP_RTO_MAX);
1966     return true;
1967 }
        由代碼可知,Linux爲設置ER定時器設置了重重障礙,現在來仔細數數:

(1)有丟包事件發生

(2)重複的ACK小於等於亂序的閾值

(3)未開啓FACK,或沒有未收到確認的包,或隊列首包已發送但未超時

(4)已發送的數據 > 亂序的閾值,或被SACK段的數量小於閾值,或允許發送skb

(5)thin_dupack功能未開啓,或當前鏈接並不是"thin"的,或重複ACK的數量大於1,或SACK未開啓,或有要發送的數據

(6)do_early_retrans開啓

(7)沒有重傳完畢但沒有確認的報文

(8)有被SACK的報文

(9)在網絡中的報文數量比被SACK的報文數量多至少1個

(10)在網絡中的報文數量少於4

(11)現在不允許發送數據

(12)sysctl_tcp_early_retrans的值是2或3

(13)ACK中沒有ECE標記

(14)tp->srtt(smoothed round trip time)的值大於0

(15)icsk->icsk_retransmit_timer超時時間在延遲時間之後

        以上條件全部滿足後,ER定時器會被設置,其超時時間是一個比重傳定時器更小的值。

        安裝丟失探測定時器、重傳定時器、堅持定時器時ER定時器就會被清除。

9.4.3 What

        ER定時器的超時函數是tcp_resume_early_retransmit:

2961 void tcp_resume_early_retransmit(struct sock *sk)
2962 {
2963     struct tcp_sock *tp = tcp_sk(sk);
2964 
2965     tcp_rearm_rto(sk);  //更新RTO,設置重傳定時器
2966 
2967     /* Stop if ER is disabled after the delayed ER timer is scheduled */
2968     if (!tp->do_early_retrans)  //ER功能被禁用
2969         return;
2970 
2971     tcp_enter_recovery(sk, false);    //進入恢復狀態
2972     tcp_update_scoreboard(sk, 1);    //更新記分板,標記skb丟失
2973     tcp_xmit_retransmit_queue(sk);   //重傳數據
2974 }
        tcp_xmit_retransmit_queue函數執行重傳數據的功能:

2447 void tcp_xmit_retransmit_queue(struct sock *sk)
2448 {
...
2457     if (!tp->packets_out)    //沒有在網絡中的數據
2458         return;
...
2463     if (tp->retransmit_skb_hint) {
2464         skb = tp->retransmit_skb_hint;
2465         last_lost = TCP_SKB_CB(skb)->end_seq;
2466         if (after(last_lost, tp->retransmit_high))
2467             last_lost = tp->retransmit_high;
2468     } else {
2469         skb = tcp_write_queue_head(sk);
2470         last_lost = tp->snd_una;
2471     }
2472
2473     tcp_for_write_queue_from(skb, sk) { //從skb開始遍歷整個發送隊列
2474         __u8 sacked = TCP_SKB_CB(skb)->sacked;
2475 
2476         if (skb == tcp_send_head(sk))
2477             break;
...
2489         if (tcp_packets_in_flight(tp) >= tp->snd_cwnd)  //網絡中的數據超過擁塞窗口
2490             return;
...
2523         if (sacked & (TCPCB_SACKED_ACKED|TCPCB_SACKED_RETRANS)) //skb被SACK過或被重傳過
2524             continue;
2525 
2526         if (tcp_retransmit_skb(sk, skb)) { //重傳skb
2527             NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPRETRANSFAIL);
2528             return;
2529         }
...
2535         if (skb == tcp_write_queue_head(sk))
2536             inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
2537                           inet_csk(sk)->icsk_rto,
2538                           TCP_RTO_MAX);
2539     }
...
        值得注意的是在快速重傳時不會重傳已經被SACK過或被重傳過的skb,這些skb也許能夠被順利收到,在這裏不重傳會減小網絡擁塞。

發佈了79 篇原創文章 · 獲贊 46 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章