1. 程式人生 > >TCP接收視窗的調整演算法(中)

TCP接收視窗的調整演算法(中)

本文內容:分析TCP接收視窗的調整演算法,主要是接收視窗當前閾值的調整演算法。

核心版本:3.2.12

作者:zhangskd @ csdn blog

接收視窗當前閾值的調整演算法

我們知道,在擁塞控制中,有個慢啟動閾值,控制著擁塞視窗的增長。在流控制中,也有個接收視窗的

當前閾值,控制著接收視窗的增長。可見TCP的擁塞控制和流控制,在某些地方有異曲同工之處。

接收視窗當前閾值tp->rcv_ssthresh的主要功能:

On reception of data segment from the sender, this value is recalculated based on the size of the

segment, and later on this value is used as upper limit on the receive window to be advertised.

可見,接收視窗當前閾值對接收視窗的大小有著重要的影響。

接收視窗當前閾值調整演算法的基本思想:

When we receive a data segment, we need to calculate a receive window that needs to be

advertised to the sender, depending on the segment size received.

The idea is to avoid filling the receive buffer with too many small segments when an application

is reading very slowly and packets are transmitted at a very high rate.

在接收視窗當前閾值的調整演算法中,收到資料報的負荷是個關鍵因素,至於它怎麼影響接收視窗當前

閾值的增長,來看下程式碼吧。

當接收到一個報文段時,呼叫處理函式:

static void tcp_event_data_recv (struct sock *sk, struct sk_buff *skb)
{
    struct tcp_sock *tp = tcp_sk(sk);
    struct inet_connection_sock *icsk = inet_csk(sk);
    u32 now;
    ...
    /* 當報文段的負荷不小於128位元組時,考慮增大接收視窗當前閾值rcv_ssthresh */
    if (skb->len >= 128)
        tcp_grow_window(sk, skb);
}

下面這個函式決定是否增長rcv_ssthresh,以及增長多少。

static void tcp_grow_window (struct sock *sk, const struct sk_buff *skb)
{
    struct tcp_sock *tp = tcp_sk(sk);
 
    /* Check #1,關於這三個判斷條件的含義可見下文分析 */
    if (tp->rcv_ssthresh < tp->window_clamp && 
         (int) tp->rcv_ssthresh < tcp_space(sk) && ! tcp_memory_pressure) {
        int incr;
        
        /* Check #2. Increase window, if skb with such overhead will fit to rcvbuf in future. 
         * 如果應用層資料佔這個skb總共消耗記憶體的75%以上,則說明這個資料報是大的資料報,
          * 記憶體的額外開銷較小。這樣一來我們可以放心的增長rcv_ssthresh了。
          */
        if (tcp_win_from_space(skb->truesize) <= skb->len)
            incr = 2 * tp->advmss; /* 增加兩個本端最大接收MSS */
        else
            /* 可能增大rcv_ssthresh,也可能不增大,具體視額外記憶體開銷和剩餘快取而定*/
            incr = __tcp_grow_window(sk, skb);

        if (incr) {
            /* 增加後不能超過window_clamp */
            tp->rcv_ssthresh = min(tp->rcv_ssthresh + incr, tp->window_clamp);
            inet_csk(sk)->icsk_ack.quick |= 1; /* 允許快速ACK */
        }
    }
}
 
/* Slow part of check#2. */
static int __tcp_grow_window (const struct sock *sk, const struct sk_buff *skb)
{
    struct tcp_sock *tp = tcp_sk(sk);
    /* Optimize this! */
    int truesize = tcp_win_from_space(skb->truesize) >> 1;
    int window = tcp_win_from_space(sysctl_tcp_rmem[2]) >> 1; /* 接收緩衝區長度上限的一半*/

    /* rcv_ssthresh不超過一半的接收緩衝區上限才有可能*/
    while (tp->rcv_ssthresh <= window) {
        if (truesize <= skb->len)
            return 2 * inet_csk(sk)->icsk_ack.rcv_mss; /* 增加兩個對端傳送MSS的估計值*/
        
        truesize >>= 1;
        window >>= 1;
    }

    return 0;/*不增長*/
}

這個演算法可能不太好理解,我們來分析一下。

只有當資料段長度大於128位元組時才會考慮增長rcv_ssthresh,並且有以下大前提(就是check #1):

a. 接收視窗當前閾值不能超過接收視窗的上限。

b. 接收視窗當前閾值不能超過剩餘接收快取的3/4,即network buffer。

c.  沒有記憶體壓力。TCP socket系統總共使用的記憶體過大。

check#2是根據額外開銷的記憶體佔的比重,來判斷是否允許增長。額外的記憶體開銷(overhead)指的是:

sk_buff、skb_shared_info結構體,以及協議頭。有效的記憶體開銷指的是資料段的長度。

(1) 額外開銷小於25%,則rcv_ssthresh增長兩個本端最大接收MSS。

(2)額外開銷大於25%,分為兩種情況。

演算法如下:

把3/4的剩餘接收快取,即剩餘network buffer均分為2^n塊。把額外開銷均分為2^n份。

如果均分後每塊快取的大小大於rcv_ssthresh,且均分後的每份開銷小於資料段的長度,則:

允許rcv_ssthresh增大2個對端傳送MSS的估計值。

否則,不允許增大rcv_ssthresh。

我們注意到在(1)和(2)中,rcv_ssthresh的增長幅度是不同的。在(1)中,由於收到大的資料段,額外

開銷較低,所以增長幅度較大(2 * tp->advmss)。在(2)中,由於收到中等資料段,額外開銷較高,所以

增長幅度較小(2 * icsk->icsk_ack.rcv_mss)。這樣做是為了防止額外開銷過高,而耗盡接收視窗。

rcv_ssthresh增長演算法的基本思想:

This algorithm works on the basis that we do not want to increase the advertised window if we

receive lots of small segments (i.e. interactive data flow), as the per-segment overhead (headers

and the buffer control block) is very high.

額外開銷大小,取決於資料段的大小。我們從這個角度來分析下當接收到一個數據報時,rcv_ssthresh

的增長情況:

(1)Small segment (len < 128)

如果接收到的資料段很小,這時不允許增大rcv_ssthresh,防止額外記憶體開銷過大。

(2)Medium segment (128 <= len <= 647)

如果接收到中等長度的資料段,符合條件時,rcv_ssthresh += 2 * rcv_mss。

(3)Large segment (len > 647)

如果接收到資料段長度較大的報文,符合條件時(rcv_ssthresh不超過window_clamp和3/4剩餘接收快取等),

rcv_ssthresh += 2 * advmss。這是比較常見的情況,這時接收視窗閾值一般增加2 * 1460 = 2920位元組。

這個值還可能有細微波動,這是由於對齊視窗擴大因子的關係。