天天看點

網絡sk_buff緩沖區[2]sk_buff的clone及限額

  上一篇文章中介紹了sk_buff的基本結構,對sk_buff的基本組織結構以及對資料的處理有了一定的了解,接下來我們繼續來介紹sk_buff中另外兩個比較重要的概念。

4.sk_buff的clone 首先,為什麼要對skb進行clone呢? 我們知道對于面向連接配接的協定,資料傳到下層并不是真正的完成了發送過程,隻有在接到對方的确認後才能丢棄資料。clone的意義就在于這,儲存發送的資料,在發送逾時時可以對這段資料進行重傳。在我們熟知的協定中,最好的例子當然是TCP了。 下面我們再來看核心中是如何實作的clone,還是先圖解:  

網絡sk_buff緩沖區[2]sk_buff的clone及限額

(圖1) 關于這副圖的幾點說明: * 兩個skb在一片連續的記憶體中,且在這片記憶體的末尾隐藏了一個計數器,用來給這片記憶體做引用計數 * 原始skb的fclone位為1,克隆skb的fclone位為2 * 克隆前兩個skb的cloned位均為0,克隆後兩個skb的cloned位均為1 這種skb是使用alloc_skb_fclone()申請的,它從sk_buff的一個專門的cache中配置設定。alloc_skb_fclone()傳回第一個skb的位址。 但如果像這樣兩個skb共同引用一個header緩沖區,會帶來嚴重的競争問題,是以其中的一個必須要放棄對header緩沖的控制。因為原始skb不需要操作header(因為它的作用隻是儲存緩沖區中的資料),是以,就應該由原始skb來放棄對header的控制權。該操作分兩個部分:首先要給自己的nohdr位置0,表示該skb不包括header;然後就要操作以前提到的dataref了。1094604 之前我們提到了dataref分成了高16位和低16位,低16位是對整個資料緩沖區的引用計數,高16位是對header沒有引用的計數。那麼在clone的情況下,原始skb隻引用了payload,沒有引用header,是以,它要給高16位和低16位都加1。而克隆skb引用了整個資料區,是以,它要給低16位加1。這樣一來,就完成了對整個資料區,header和payload三個部分的計數。至于為什麼要把高16位定義的那樣怪,應該是想盡量減少atomic操作。   5.sk_buff的限額及緩沖區控制 首先,限額的功能隻是針對于STREAM類型的協定,因為隻有這種協定才需要保留已的發送緩沖區,因而也就需要有記憶體使用限制。 另外,這隻是socket子系統提供的一套機制,具體的響應還是要由協定來決定,隻能是起到一個“建議”的作用。 還有,限額不是由skb本身提供的一個機制,它實際是由sk和proto以及一系列接口組成的。但它是控制sk_buff緩沖區生長的一個重要機制,是以權且寫在這裡。下面先從字段來看。 proto相關字段釋義: 限額包含了兩個層面的記憶體申請限制:協定層面和單個socket層面。使用該機制的協定必須提供三個數組,即proto中的三個sysctl_開頭的字段。 sysctl_mem: 協定的總記憶體限額(在此處稱之為協定記憶體限額)。機關是SK_MEM_QUANTUM(即PAGE_SIZE)。 和memory_allocated協同使用,表示目前TCP對記憶體的使用情況。 sysctl_wmem: 單個寫隊列的記憶體限額(在此處稱之為sock寫隊列記憶體限額)。機關是位元組。 sysctl_rmem: 單個讀隊列的記憶體限額(在此處稱之為sock讀隊列記憶體限額)。機關是位元組 這三個數組的長度都為3,每個數都表示了使用空間的一個門檻值。 memory_pressure:空間使用進入壓力狀态的标志,表示協定現在是否使用了過量的空間。需要指派才能啟用。 memory_allocated:協定已配置設定記憶體的數量,機關是SK_MEM_QUANTUM。 和協定記憶體限額配合使用,當超過sysctl_mem[1]時就進入pressure狀态,對memory_pressure賦1。 sk相關字段釋義: sk_rmem_alloc 接收隊列已使用空間。 sk_wmem_alloc

發送隊列已使用空間。在STREAM類型協定中由sk_wmem_queued成員所替代。 sk_omem_alloc 目前尚不明。 sk_sndbuf: 發送緩沖區大小。最大不超過sock寫隊列記憶體限額的最大值。 sk_rcvbuf: 接收緩沖區大小。最大不超過sock讀隊列記憶體限額的最大值。 sk_forward_alloc:發送隊列中可以直接使用的空間大小,是指已經配置設定了但尚未使用的空間。 sk_wmem_queued: 發送隊列所占空間,僅用于STREAM類型協定,替代sk_wmem_alloc。最大不能超過發送緩沖區大小。   接口: sk_wmem_schedule() 發送隊列記憶體申請。從協定記憶體限額中配置設定,配置設定的空間在sk_forward_alloc中。 sk_rmem_schedule() 接收隊列記憶體申請。從協定記憶體限額中配置設定,配置設定的空間在sk_forward_alloc中。 sk_stream_memory_free() 檢查目前是否已經将發送緩沖區填滿。 sk_stream_moderate_sndbuf()修改發送緩沖區(在申請記憶體失敗的情況下),修改後的sndbuf小于目前發送隊列所占空間,大于SOCK_MIN_SNDBUF。 sk_stream_wait_memory() 在申請記憶體失敗的情況下等待發送緩沖區釋放空間。   當發送緩沖區填滿(或申請不到skb,page,或達到協定記憶體限額上限)時,若在阻塞條件下,系統會等待記憶體空閑,發送調用阻塞。在這種情況下,隻有等接收對方相應,重新調整發送緩沖區大小,才能喚醒發送隊列。并且發送隊列的喚醒是有條件的,發送緩沖區的空閑空間要占總空間的1/3以上,才能将寫隊列喚醒,繼續接收應用程式輸入請求。另外對于發送緩沖區的調整,是根協定相關的,這個以後看到再說吧。 當接收緩沖區填滿(或達到協定記憶體限額上限)時,當有新的skb到來時,對于TCP來說就直接将該包丢棄,此時不會對接收緩沖區進行調整。但接收緩沖區的大小也并非是不會改變,當收到應用程式輸出請求時,會根據協定來調整接收緩沖區大小。 發送緩沖區和接收緩沖區都是在單個socket層面上的控制,核心網絡子系統還提供了以協定為機關的記憶體限額功能。它主要使用了上面提到的memory_allocated和sysctl_mem兩個字段。在TCP協定中,協定記憶體限額是根據系統記憶體數量計算出來的,是以在記憶體很少的情況下可能會影響TCP的性能。   socket記憶體管理架構大概就是這樣,以後看到相關的再補充。但是在繼續研究TCP建立連接配接的過程之前,還需要先了解核心對TCP滑動視窗協定的實作,下面先來研究一下這個問題吧。    

轉載于:https://blog.51cto.com/957554/466950

繼續閱讀