日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

關(guān)于TCP的接收緩存以及通告窗口,一般而言懂TCP的都能說出個大概,但是涉及到細節(jié)的話可能理解就不那么深入了。

問題:
明明在接收端有8192字節(jié)的接收緩存,為什么收了不到8000字節(jié)的數(shù)據(jù)就ZeroWindow了呢?

0.network buffer & Application buffer

深入接收緩存管理機制的過程中,你可能會在代碼的注釋中看到這樣的分割,將接收緩存分割成了所謂的network buffer和application buffer,具體參見__tcp_grow_window的注釋:

/* 2. Tuning advertised window (window_clamp, rcv_ssthresh)
*
* All tcp_full_space() is split to two parts: "network" buffer, allocated
* forward and advertised in receiver window (tp->rcv_wnd) and
* "application buffer", required to isolate scheduling/application
* latencies from network.
* window_clamp is maximal advertised window. It can be less than
* tcp_full_space(), in this case tcp_full_space() - window_clamp
* is reserved for "application" buffer. The less window_clamp is
* the smoother our behaviour from viewpoint of network, but the lower
* throughput and the higher sensitivity of the connection to losses. 8)
*
* rcv_ssthresh is more strict window_clamp used at "slow start"
* phase to predict further behaviour of this connection.
* It is used for two goals:
* - to enforce header prediction at sender, even when application
* requires some significant "application buffer". It is check #1.
* - to prevent pruning of receive queue because of misprediction
* of receiver window. Check #2.
*
* The scheme does not work when sender sends good segments opening
* window and then starts to feed us spagetti. But it should work
* in common situations. Otherwise, we have to rely on queue collapsing.
*/
然后,幾乎所有的分析接收緩存的文章都采用了這種說法,誠然,說法并不重要,關(guān)鍵是要便于人們?nèi)ダ斫?。因此我嘗試用一種不同的說法去解釋它,其實本質(zhì)上是相同的,只是更加啰嗦一些。
和我一向的觀點一樣,本文不會去大段大段分析源碼,也就是說不會去做給源碼加注釋的工作,而是希望能繪制一個關(guān)于這個話題的藍圖,就像之前分析OpenVPN以及Netfilter的時候那樣。

1.通告窗口與接收緩存

在TCP的配置中,有一個接收緩存的概念,另外在TCP滑動窗口機制中,還有一個接收窗口的概念,毋庸置疑,接收窗口所使用的內(nèi)存必須分配自接收緩存,因此二者是包容的關(guān)系。
但這不是重點,重點是: 接收窗口無法完全占完接收緩存的內(nèi)存,即接收緩存的內(nèi)存并不能完全用于接收窗口!Why?
這是因為接收窗口是TCP層的概念,僅僅描述TCP載荷,然而這個載荷之所以可以收到,必須使用一個叫做數(shù)據(jù)包的載體,在linux中就是skb,另外為了讓協(xié)議運行,必須為載荷封裝TCP頭,IP頭,以太頭...等等。
我用下圖來解釋接收緩存以及其和TCP數(shù)據(jù)包的關(guān)系:

關(guān)于Linux TCP接收緩存以及接收窗口的一個細節(jié)解析

 

【注意,當我說“TCP數(shù)據(jù)包”的時候,我的意思是這是一個帶有以太頭的完整數(shù)據(jù)包,當我說“TCP數(shù)據(jù)段”的時候,我想表達的則是我并不關(guān)系IP層及以下的東西?!?br />圖示的最后,我特意標紅了一個“極力要避免”的警示,確實,如果直接把可用的窗口都通告出去了,且發(fā)送端并不按照滿MSS發(fā)送的話,是存在溢出風險的,這要怎么解決呢?


附:如何確定通告窗口可以使用的接收緩存

在代碼中,我們注意一個函數(shù)tcp_fixup_rcvbuf:

static void tcp_fixup_rcvbuf(struct sock *sk)
{
    u32 mss = tcp_sk(sk)->advmss;
    u32 icwnd = TCP_DEFAULT_INIT_RCVWND;
    int rcvmem;

    /* Limit to 10 segments if mss <= 1460,
     * or 14600/mss segments, with a minimum of two segments.
     */
    if (mss > 1460)
        icwnd = max_t(u32, (1460 * TCP_DEFAULT_INIT_RCVWND) / mss, 2);

    rcvmem = SKB_TRUESIZE(mss + MAX_TCP_HEADER);
    // 將rcvbuf按比例縮放到其(n-1)/n可以完全容納TCP純載荷的程度,n由系統(tǒng)參數(shù)net.ipv4.tcp_adv_win_scale來確定。
    while (tcp_win_from_space(rcvmem) < mss)
        rcvmem += 128;

    rcvmem *= icwnd;

    if (sk->sk_rcvbuf < rcvmem)
        sk->sk_rcvbuf = min(rcvmem, sysctl_tcp_rmem[2]);
}

以上函數(shù)確定了接收緩存,其中有3個要點:
1).初始通告窗口的大小
默認是10個MSS滿1460字節(jié)的段,這個數(shù)值10來自google的測試,與擁塞窗口的初始值一致,然而由于MSS各不同,其會按照1460/mss的比例進行縮放來適配經(jīng)驗值10。
2).TCP載體開銷的最小值128
展開宏SKB_TRUESIZE會發(fā)現(xiàn)其最小值就是128,這對通告窗口慢啟動過程定義了一個安全下界,載荷小于128字節(jié)的TCP數(shù)據(jù)段將不會增加通告的上限大小。
3).參數(shù)tcp_adv_win_scale的含義
對比我上面的圖示,上述代碼的注釋,我們知道tcp_adv_win_scale就是控制“載荷/載體”比例的,我們看一下其Kernel DOC

tcp_adv_win_scale - INTEGER Count buffering overhead as bytes/2^tcp_adv_win_scale
    (if tcp_adv_win_scale > 0) or bytes-bytes/2^(-tcp_adv_win_scale),
    if it is <= 0.
    Possible values are [-31, 31], inclusive.
    Default: 1

這個參數(shù)曾經(jīng)的default值是2而不是1,這意味著以往TCP的載荷占比由3/4變成了1/2,好像是開銷更大了,這是為什么呢?以下是該Change的patch描述:

From: Eric Dumazet <edum...@google.com>
[ Upstream commit b49960a05e32121d29316cfdf653894b88ac9190 ]
tcp_adv_win_scale default value is 2, meaning we expect a good citizen
skb to have skb->len / skb->truesize ratio of 75% (3/4)
In 2.6 kernels we (mis)accounted for typical MSS=1460 frame :
1536 + 64 + 256 = 1856 'estimated truesize', and 1856 * 3/4 = 1392.
So these skbs were considered as not bloated.
With recent truesize fixes, a typical MSS=1460 frame truesize is now the
more precise :
2048 + 256 = 2304. But 2304 * 3/4 = 1728.
So these skb are not good citizen anymore, because 1460 < 1728
(GRO can escape this problem because it build skbs with a too low
truesize.)
This also means tcp advertises a too optimistic window for a given
allocated rcvspace : When receiving frames, sk_rmem_alloc can hit
sk_rcvbuf limit and we call tcp_prune_queue()/tcp_collapse() too often,
especially when application is slow to drain its receive queue or in
case of losses (netperf is fast, scp is slow). This is a major latency
source.
We should adjust the len/truesize ratio to 50% instead of 75%
This patch :
1) changes tcp_adv_win_scale default to 1 instead of 2
2) increase tcp_rmem[2] limit from 4MB to 6MB to take into account
better truesize tracking and to allow autotuning tcp receive window to
reach same value than before. Note that same amount of kernel memory is

consumed compared to 2.6 kernels.

單純從TCP載荷比來講,開銷的增加意味著效率的降低,然而注意到這部分開銷的增加并非網(wǎng)絡(luò)協(xié)議頭所為,而是skb_shared_info結(jié)構(gòu)體被計入開銷以及skb結(jié)構(gòu)體等系統(tǒng)載體的膨脹所導(dǎo)致:
我們分別來看一下2.6.32和3.10兩個版本的sk_buff的大小,怎么看呢?不要想著寫一個模塊然后打印sizeof,直接用slabtop去看即可,里面信息很足。
a).2.6.32版本的sk_buff大小
slabtop的結(jié)果是:
skbuff_head_cache 550 615 256 15 1 : tunables 120 60 8 : slabdata 41 41 0
我們看到其大小是256字節(jié)。
b).3.10版本的sk_buff大小
slabtop的結(jié)果是:
skbuff_head_cache 3675 3675 320 25 2 : tunables 0 0 0 : slabdata 147 147 0
我們看到其大小是320字節(jié)。
差別并不是太大!這不是主要因素,但確實會有所影響。
除了skb的膨脹之外,系統(tǒng)中還有別的膨脹,比如為了效率的“對齊開銷”,但更大的開銷增加是skb_shared_info結(jié)構(gòu)體的計入(個人認為以前開銷中不計入skb_shared_info結(jié)構(gòu)體是錯誤的)等,最終導(dǎo)致新版本(以3.10+為例)的內(nèi)核計算TRUESIZE的方法改變:
packet_size = mss + MAX_TCP_HEADER + SKB_DATA_ALIGN(sizeof(struct sk_buff)) + SKB_DATA_ALIGN(sizeof(struct skb_shared_info)))
然而以往的老內(nèi)核(以2.6.32為例),其開銷的計算是非常魯莽的,少了很多東西:
packet_size = mss + MAX_TCP_HEADER + 16 + sizeof(struct sk_buff);
雖然這種開銷的膨脹在TCP層面幾乎看不到什么收益(反而付出了代價,你不得不配置更大的rcvbuf...),然而skb等并不單單服務(wù)于TCP,這種膨脹的收益可能被調(diào)度,中斷,IP路由,負載均衡等機制獲取了,記住兩點即可:首先,Linux內(nèi)核各個子系統(tǒng)是一個整體,其次,內(nèi)存越來越便宜而時間一去不復(fù)返,空間換時間,劃得來!

2.如何規(guī)避接收緩存溢出的風險

在談如何規(guī)避溢出風險之前,我必須先說一下這個風險并不是常在的,如果應(yīng)用程序非常迅速的讀取TCP數(shù)據(jù)并釋放skb,那么幾乎不會有什么風險,問題在于應(yīng)用程序并不受TCP層的控制,所以我說的“溢出風險”指的是一種合理但很極端的情況,那就是應(yīng)用程序在TCP層收滿一窗數(shù)據(jù)前都不會去讀取數(shù)據(jù),這雖然很極端,但是符合TCP滑動窗口的規(guī)范:通告給發(fā)送端的窗口表示發(fā)送端可以一次性發(fā)送這么多的數(shù)據(jù),至于應(yīng)用程序什么時候來讀取,滑動窗口機制并不控制。
在闡明了風險的來源后,我們就可以暢談何以規(guī)避風險了。
我們知道,TCP擁塞控制通過慢啟動來規(guī)避突發(fā)造成的網(wǎng)絡(luò)緩存溢出的的風險,事實上擁塞控制也是一種流量控制,作為標準的方案,慢啟動幾乎是規(guī)避溢出的標配方案!這很好理解,慢啟動的含義是“快速地從起點試探到穩(wěn)態(tài)”,并非其字面含義所說的“慢慢地啟動”,之所以有“慢”字是因為與進入穩(wěn)定狀態(tài)后相比,它的起點是低的。這和開車是一樣的道理,靜止的汽車從踩下油門開始一直到勻速,是一個快速加速的過程,達到100km/h的時間也是一個重要的指標,當然,很多情況下是越小越好!
所以說,通告窗口也是采用慢啟動方式逐步張開的。

2.0、收到極小載荷的TCP數(shù)據(jù)包時的慢啟動

比如說收到了一個只包含1個字節(jié)載荷的數(shù)據(jù)包時,此時僅僅skb,協(xié)議頭等開銷就會超過幾百字節(jié),通告窗口增加是非常危險的。Linux TCP實現(xiàn)中,將128字節(jié)定為下限,凡是收到小于128字節(jié)載荷的數(shù)據(jù)包,接收一大窗的數(shù)據(jù)非常有可能造成緩存溢出,因此不執(zhí)行慢啟動。

2.1、收到滿MSS的TCP數(shù)據(jù)包時的慢啟動

如果能保證發(fā)送端一直發(fā)送滿MSS長度的TCP數(shù)據(jù)包,那么接收緩存是不會溢出的,因為整個通告窗口可以使用的內(nèi)存就是通過這個滿MSS長度和接收緩存按照比例縮放而生成的,但是誰也不能保證發(fā)送端會一直發(fā)送滿MSS長度的TCP數(shù)據(jù)包,所以就不能允許發(fā)送端一下子發(fā)送所有可用的窗口緩存那么大的數(shù)據(jù)量,因此慢啟動是必須的。
收到滿MSS長度的數(shù)據(jù)或者大于MSS長度的數(shù)據(jù),窗口可以毫無壓力地增加2個MSS大小。

2.2、收到非滿MSS

這里的情況比較復(fù)雜了。雖然收到數(shù)據(jù)長度比MSS小的TCP數(shù)據(jù)包有緩存溢出的風險,但是受限于當前的通告窗口上限(由于慢啟動的功勞)小于整個可用的通告窗口內(nèi)存,這種情況下即便是發(fā)送一整窗的數(shù)據(jù),也不會造成整個接收緩存的溢出。這就是說某些時候,當當前的接收窗口上限未達到整個可用的窗口緩存時,長度小于MSS的TCP數(shù)據(jù)包的額外高于 (n-1)/n比例的開銷可以暫時“借用”剩余的窗口可用的緩存,只要不會造成溢出,管它是不是借用,都是可以接受的。
如此復(fù)雜的情況,我畫了一個稍微復(fù)雜點的圖來展示,以節(jié)省文字篇幅:

關(guān)于Linux TCP接收緩存以及接收窗口的一個細節(jié)解析

 

看懂了上圖之后,我來補充一個動態(tài)過程,如果持續(xù)收到小包的情況下,會怎樣?
如果持續(xù)收到小于MSS的小包,假設(shè)長度都相等,那么從慢啟動開始,通告窗口的最大值,即rcv_ssthresh將會在每收到一個數(shù)據(jù)包后從初始值開始按照2倍數(shù)據(jù)段長度的增量持續(xù)增長,直到其達到小于所有可用通告窗口內(nèi)存的某個值停止再增長,增長到該值的位置時,一整窗的數(shù)據(jù)連同其開銷將會完全占滿整個rcvbuf。

3.一個差異:通告窗口大小與通告窗口上限

為什么擁塞窗口的慢啟動是直接增加的擁塞窗口的值,通告窗口的慢啟動并不直接增加通告窗口而是增加的通告窗口的上限呢?
這是因為通告窗口的實際值并非單單由接收緩存溢出檢測這么一個因素控制,這個因素事實上反而不是主導(dǎo)因素,主導(dǎo)因素是應(yīng)用程序是不是即時騰出了接收緩存。我們從代碼中如何確定通告窗口的邏輯中可以看出:

u32 __tcp_select_window(struct sock *sk)
{
    struct inet_connection_sock *icsk = inet_csk(sk);
    struct tcp_sock *tp = tcp_sk(sk);
    /* MSS for the peer's data.  Previous verions used mss_clamp
     * here.  I don't know if the value based on our guesses
     * of peer's MSS is better for the performance.  It's more correct
     * but may be worse for the performance because of rcv_mss
     * fluctuations.  --SAW  1998/11/1
     */
    int mss = icsk->icsk_ack.rcv_mss;
    // free_space就是應(yīng)用程序和TCP層合力確定的通告窗口基準值,它簡單來講就是(rcvbuf - sk_rmem_alloc)中的純數(shù)據(jù)部分,縮放比例就是本文開始提到的(n-1)/n。
    int free_space = tcp_space(sk);
    int full_space = min_t(int, tp->window_clamp, tcp_full_space(sk));
    int window;
    
    if (mss > full_space)
        mss = full_space;
    
    // 這里是為了防止接收緩存溢出的最后防線,當free_space小于全部rcvbuf按純數(shù)據(jù)比例縮放后的大小的一半時,就要小心了!
    if (free_space < (full_space >> 1)) {
        icsk->icsk_ack.quick = 0;

        if (sk_under_memory_pressure(sk))
            tp->rcv_ssthresh = min(tp->rcv_ssthresh,
                           4U * tp->advmss);

        // 我們要多大程度上信任mss,取決于發(fā)送端mss的波動情況,如注釋中所提到的“It's more correct but may be worse for the performance because of rcv_mss fluctuations.”
        if (free_space < mss)
            return 0;
    }    
    
    // 這里的核心是,雖然應(yīng)用程序為TCP接收緩存騰出了free_space這么大小的空間,但是并不能全部通告給發(fā)送端,需要一點點通告并增加通告的大小,這就是慢啟動了。
    // 注意這里,free_space不能超過ssthresh,這便是通告窗口上限慢啟動的根本了。    
    if (free_space > tp->rcv_ssthresh)
        free_space = tp->rcv_ssthresh;

    ...// 這里的窗口計算詳細過程反而不是本文關(guān)注的,可以參見其它源碼分析的文章和書籍
    ...// 最終free_space要落實到window,為了便于理解核心,可以認為free_space就是window。

    return window;
}

最后,總結(jié)一幅圖,將上面談到的所有這些概念與Linux內(nèi)核協(xié)議棧TCP實現(xiàn)關(guān)聯(lián)起來:

關(guān)于Linux TCP接收緩存以及接收窗口的一個細節(jié)解析

分享到:
標簽:Linux TCP
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運動步數(shù)有氧達人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定