TCP中RTT的測量和RTO的計算 以及 接收緩存大小的動態調整


RTT測量

 

在發送端有兩種RTT的測量方法,但是因為TCP流控制是在接收端進行的,所以接收端也需要

有測量RTT的方法。

/* Receiver "autotuning" code.
 *
 * The algorithm for RTT estimation w/o timestamps is based on
 * Dynamic Right-Sizing (DRS) by Wu Feng and Mike Fisk of LANL.
 * <http://public.lanl.gov/radiant/pubs.html#DRS>
 *
 * More detail on this code can be found at
 * <http://staff.psc.edu/jheffner/>,
 * though this reference is out of date.  A new paper
 * is pending.
 
 不管是沒有使用時間戳選項的RTT采樣,還是使用時間戳選項的RTT采樣,都是獲得一個RTT樣本。
 
 之后還需要對獲得的RTT樣本進行處理,以得到最終的RTT。
 對於沒有使用時間戳選項的RTT測量方法,不進行微調。因為用此種方法獲得的RTT采樣值已經偏高而且收斂

很慢。直接選擇最小RTT樣本作為最終的RTT測量值。

對於使用時間戳選項的RTT測量方法,進行微調,新樣本占最終RTT的1/8,即rtt = 7/8 old + 1/8 new。
 
 */
static void tcp_rcv_rtt_update(struct tcp_sock *tp, u32 sample, int win_dep)
{
    u32 new_sample = tp->rcv_rtt_est.rtt_us;
    long m = sample;

    if (m == 0)
        m = 1;/* 時延最小為1ms*、*/

    if (new_sample != 0) { /* 不是第一次獲得樣本*/
        /* If we sample in larger samples in the non-timestamp
         * case, we could grossly overestimate the RTT especially
         * with chatty applications or bulk transfer apps which
         * are stalled on filesystem I/O.
         *
         * Also, since we are only going for a minimum in the
         * non-timestamp case, we do not smooth things out
         * else with timestamps disabled convergence takes too
         * long. /* 對RTT采樣進行微調,新的RTT樣本只占最終RTT的1/8 *
         */
        if (!win_dep) {//需要對采樣進行微調
            m -= (new_sample >> 3);
            new_sample += m;
        } else { /* 不對RTT采樣進行微調,直接取最小值,原因可見上面那段注釋*/
            m <<= 3; 
            if (m < new_sample)
                new_sample = m;
        }
    } else {
        /* No previous measure. 第一次獲得樣本*//注意,Linux內核為了避免浮點運算,RTT采樣都是按8倍存儲的*/
        new_sample = m << 3;//注意,Linux內核為了避免浮點運算,RTT采樣都是按8倍存儲的
    }

    tp->rcv_rtt_est.rtt_us = new_sample;/* 更新RTT*/
}
/*
此函數的原理:我們知道發送端不可能在一個RTT期間發送大於一個通告窗口的數據量。
那么接收端可以把接收一個確認窗口的數據量(rcv_wnd)所用的時間作為RTT。接收端收到一個數據段,
然后發送確認(確認號為rcv_nxt,通告窗口為rcv_wnd),開始計時,RTT就是收到序號為rcv_nxt + rcv_wnd的數據段所用的時間。
很顯然,這種假設並不准確,測量所得的RTT會偏大一些。所以這種方法只有當沒有采用時間戳選項時才使用,
而內核默認是采用時間戳選項的(tcp_timestamps為1)。
下面是一段對此方法的評價:
If the sender is being throttled by the network, this estimate will be valid. However, if the sending application did nothave any data to send, 
the measured time could be much larger than the actual round-trip time. Thus this measurementacts only as an upper-bound on the round-trip time.
————————————————


*/
static inline void tcp_rcv_rtt_measure(struct tcp_sock *tp)
{
    u32 delta_us;
    /* 第一次接收到數據時,需要對相關變量初始化*/

    if (tp->rcv_rtt_est.time.v64 == 0)
        goto new_measure;
     /* 收到指定的序列號后,才能獲取一個RTT測量樣本*/
    if (before(tp->rcv_nxt, tp->rcv_rtt_est.seq))
        return;
     /* RTT的樣本:jiffies - tp->rcv_rtt_est.time */
    delta_us = skb_mstamp_us_delta(&tp->tcp_mstamp, &tp->rcv_rtt_est.time);
    tcp_rcv_rtt_update(tp, delta_us, 1);

new_measure:
    tp->rcv_rtt_est.seq = tp->rcv_nxt + tp->rcv_wnd;
    tp->rcv_rtt_est.time = tp->tcp_mstamp;
}
/*但是在流量小的時候,通過時間戳采樣得到的RTT的值會偏大,此時就會采用

沒有時間戳時的RTT測量方法。*/
static inline void tcp_rcv_rtt_measure_ts(struct sock *sk,
                      const struct sk_buff *skb)
{
    struct tcp_sock *tp = tcp_sk(sk);
    if (tp->rx_opt.rcv_tsecr &&/* 啟用了Timestamps選項,並且流量穩定*/
        (TCP_SKB_CB(skb)->end_seq -
         TCP_SKB_CB(skb)->seq >= inet_csk(sk)->icsk_ack.rcv_mss))
            /* RTT = 當前時間 - 回顯時間*/
        tcp_rcv_rtt_update(tp,
                   jiffies_to_usecs(tcp_time_stamp -
                            tp->rx_opt.rcv_tsecr),
                   0);
}

 

調整接收緩存

數據從TCP接收緩存復制到用戶空間之后,會調用tcp_rcv_space_adjust()來調整TCP接收緩存和接收窗口上限的大小

/*
 * This function should be called every time data is copied to user space.
 * It calculates the appropriate TCP receive buffer space.
 tp->rcvq_space.space表示當前接收緩存的大小(只包括應用層數據,單位為字節)。
 sk->sk_rcvbuf表示當前接收緩存的大小(包括應用層數據、TCP協議頭、sk_buff和skb_shared_info結構,

tcp_adv_win_scale微調,單位為字節)
 */
void tcp_rcv_space_adjust(struct sock *sk)
{
    struct tcp_sock *tp = tcp_sk(sk);
    int time;
    int copied;
    /*計算上次調整到現在的時間*/ 

    time = skb_mstamp_us_delta(&tp->tcp_mstamp, &tp->rcvq_space.time);
    /* 調整至少每隔一個RTT才進行一次,RTT的作用在這里?
    //注意,Linux內核為了避免浮點運算,RTT采樣都是按8倍存儲的
    或者沒喲計算出接收方rtt?/
    if (time < (tp->rcv_rtt_est.rtt_us >> 3) || tp->rcv_rtt_est.rtt_us == 0)
        return;

    /* Number of bytes copied to user in last RTT */
    /* 一個RTT內接收方應用程序接收並復制到用戶空間的數據量*/  
    copied = tp->copied_seq - tp->rcvq_space.seq;
    if (copied <= tp->rcvq_space.space) /* 如果這次的space比上次的 小*/  
        goto new_measure;

    /* A bit of theory :
     * copied = bytes received in previous RTT, our base window
     * To cope with packet losses, we need a 2x factor
     * To cope with slow start, and sender growing its cwin by 100 %
     * every RTT, we need a 4x factor, because the ACK we are sending
     * now is for the next RTT, not the current one :
     * <prev RTT . ><current RTT .. ><next RTT .... >
     */
        /* 如果這次的space比上次的 大   */
    /* 啟用自動調節接收緩沖區大小,並且接收緩沖區沒有上鎖*/

    if (sysctl_tcp_moderate_rcvbuf &&
        !(sk->sk_userlocks & SOCK_RCVBUF_LOCK)) {
        int rcvwin, rcvmem, rcvbuf;

        /* minimal window to cope with packet losses, assuming
         * steady state. Add some cushion because of small variations.
         */
        rcvwin = (copied << 1) + 16 * tp->advmss;

        /* If rate increased by 25%,
         *    assume slow start, rcvwin = 3 * copied
         * If rate increased by 50%,
         *    assume sender can use 2x growth, rcvwin = 4 * copied
         */
        if (copied >=
            tp->rcvq_space.space + (tp->rcvq_space.space >> 2)) {
            if (copied >=
                tp->rcvq_space.space + (tp->rcvq_space.space >> 1))
                rcvwin <<= 1;
            else
                rcvwin += (rcvwin >> 1);
        }
        /* 一個數據包耗費的總內存包括: 
                       * 應用層數據:tp->advmss, 
                       * 協議頭:MAX_TCP_HEADER, 
                       * sk_buff結構, 
                       * skb_shared_info結構。 
                       */  

        rcvmem = SKB_TRUESIZE(tp->advmss + MAX_TCP_HEADER);
        while (tcp_win_from_space(rcvmem) < tp->advmss)
            rcvmem += 128; // 為啥微調是128
        /*不能超過允許的最大接收緩沖區大小*/

        rcvbuf = min(rcvwin / tp->advmss * rcvmem, sysctl_tcp_rmem[2]);
        if (rcvbuf > sk->sk_rcvbuf) {
            sk->sk_rcvbuf = rcvbuf;/* 調整接收緩沖區的大小*/

            /* Make the window clamp follow along.  */
            tp->window_clamp = rcvwin;/*調整接收窗口的上限*/
        }
    }
    tp->rcvq_space.space = copied;//更新接收方窗口上限

new_measure:
    tp->rcvq_space.seq = tp->copied_seq;
    tp->rcvq_space.time = tp->tcp_mstamp;
}

 

sk->sk_rcvbuf:分配給連接的接收使用的buf大小(size of receive buffer in bytes)。通常將數據拷貝給用戶后會根據歷史接收情況重新計算sk_rcvbuf(具體參見tcp_rcv_space_adjust)。
sk->sk_rmem_alloc:接收已經使用的buf大小。(接收到報文會相應增加,將數據交給用戶后會相應減少)
tp->window_clamp:連接窗口的上限(Maximal window to advertise)。通常是通過歷史接收情況估算出當前需要通告的最優窗口(具體參見tcp_rcv_space_adjust)。rcv_ssthresh動態調整最終會趨向於這個值。
tp->rcv_ssthresh:當前允許通告的最大窗口(Current window clamp)。窗口的選擇大多時候都由rcv_ssthresh決定,而rcv_ssthresh是會動態調整的。而rcv_ssthresh調整的上限就是tp->window_clamp


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM