天天看點

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位元組。

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

轉載于:https://www.cnblogs.com/aiwz/archive/2013/02/22/6333355.html

繼續閱讀