Linux路由緩存的前世此生

route cache

3.6版本必定算得上是Linux網絡子系統中一個特別的版本, 這個版本(補丁patch)移除了查找FIB以前的緩存查找。本文就來談談路由緩存的前世此生。linux

幾個基本概念

爲了讓本文的閱讀曲線更加平緩我決定仍是將本文涉及的一些術語做個說明。git

路由:將skb按照規則送到該去的地方,這個地方多是本機,也多是局域網中的其餘主機,或者更遠的主機。從這個角度來講,它一個動詞。那麼路由發生在哪一個時候呢? 咱們知道路由是網絡層(L3)的概念,接收方向,它須要決定收到的skb是應該上送本機仍是轉發,發送方向,它須要決定skb從哪一個網絡接口發出。下圖本來是描述Netfilter在內核中的鉤子位置的,但我以爲用來講明路由的位置也是比較合適的。數據庫

forward

與此同時,路由也能夠特指上面所說的規則,這是名詞的用法。路由從哪來? 通常來講有三個來源:1. 用戶主動配置;2.內核生成; 3. 其餘一些路由協議進程(OSPFBGP)生成。普通主機上可能沒有最後一種,因此,爲了理解方便,你能夠將路由就理解爲你用route命令看到的內容。緩存

[root@tristan]# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.99.0    0.0.0.0         255.255.255.0   U     0      0        0 eth0
192.168.98.42   192.168.99.1    255.255.255.255 UGH   0      0        0 eth0
127.0.0.0       0.0.0.0         255.0.0.0       U     0      0        0 lo
0.0.0.0         192.168.99.254  0.0.0.0         UG    0      0        0 eth0

FIB:全稱是(Forwarding Information Base),翻譯過來就是轉發信息表FIB是內核skb路由過程的數據庫,或者說內核會將路由翻譯成FIB中的表項。咱們習慣說的查詢路由,對於內核來講,應該叫查詢FIB網絡

3.6版本之前的路由緩存

緩存無處不在。現代計算機系統中,CacheCPU與內存間存在一種容量較小但速度很高的存儲器,用來存放CPU剛使用過或最近使用的數據。路由緩存就是基於這種思想的軟件實現。內核查詢FIB前,固定先查詢cache中的記錄,若是cache命中(hit),那就直接用就行了,沒必要查詢FIB。若是沒有命中(miss), 就回過頭來查詢FIB,最終將結果保存到cache,以便下次再也不須要須要查詢FIB性能

緩存是精確匹配的, 每一條緩存表項記錄了匹配的源地址和目的地址、接收發送的dev,以及與內核鄰居系統(L2層)的聯繫(negghbour)
FIB中存儲的也就是路由信息,它經常是範圍匹配的,好比像ip route 1.2.3.0/24 dev eth0這樣的網段路由。spa

下圖是3.6版本之前的本機發送skb的路由過程.....net

fiblookup

看上去的確可能能提升性能! 只要cache命中率足夠高。要得到高的cache命中率有如下兩個途徑:1. 存儲更多的表項; 2.存儲更容易命中的表項翻譯

緩存中存放的表項越多,那麼目標報文與表項匹配的可能性越大。可是cache又不能無限制地增大,cache自己佔用內存是一回事,更重要的是越多的表項會致使查詢cache自己變慢。使用cache的目的是爲了加速,若是不能加速,那要這勞什子有有什麼用呢?code

前面說了,cache的特色決定了它只能作精確匹配。也就是說,只有目標數據報文與cache中的表項徹底一致,纔算匹配成功。最簡單的cache查找過程應該是下面這樣:遍歷cache中的全部表項,直到遇到匹配的表項跳出循環。

foreach entry in cache:
then
    if entry match skb
    then
        /* 條件匹配,將緩存表項中記錄的結果設置到skb上 */
        skb->dst <= entry->dst
        return
    endif
end

顯然,cache表項的數目越多,那麼查找的過程就越長! 固然,內核不會這麼蠢地將全部cache拉成一個線,而是使用hash桶,看上去應該是這麼一個結構。

cachebucket

內核首先根據目標報文的一些特徵計算hash,找到對應的hash衝突鏈表。在鏈表上一個一個地進行比較遍歷。

爲了不cache表項過多,內核還會在必定時機下清除過時的表項。有兩個這樣的時機,其一是添加新的表項時,若是衝突鏈的表項過多,就刪除一條已有的表項;其二是內核會啓動一個專門的定時器週期性地老化一些表項.

得到更高的cache命中率的第二個途徑是存儲更容易命中的表項,什麼是更容易命中的呢? 那就是真正有效的報文。遺憾的是,內核一點也不聰明:只要輸入路由系統的報文不來離譜,它就會生成新的緩存表項。壞人正好能夠利用這一點,不停地向主機發送垃圾報文,內核所以會不停地刷新cache。這樣每一個skb都會先在cache表中進行搜索,再查詢FIB表,最後再建立新的cache表項,插入到cache表。這個過程當中還會涉及爲每個新建立的cache表項綁定鄰居,這又要查詢一次ARP表。

要知道,一臺主機上的路由表項可能有不少,特別是對於網絡交換設備,由OSPF*BGP等路由協議動態下發的表項有上萬條是很正常的事。而鄰居節點卻不可能達到這個數量。對於轉發或者本機發送的skb來講,路由系統能幫它們找到下一跳鄰居*就足夠了。

總結起來就是,3.6版本之前的這種路由緩存在skb地址穩定時的確可能提升性能。但這種根據skb內容決定的性能倒是不可預測和不穩定的。

3.6版本之後的下一跳緩存

正如前面所說,3.6版本移除了FIB查找前的路由緩存。這意味着每個接收發送的skb如今都必需要進行FIB查找了。這樣的好處是如今查找路由的代價變得穩定(consistent)了。

路由緩存徹底消失了嗎? 並無!在3.6之後的版本, 你還能夠在內核代碼中看到dst_entry。這是由於,3.6版本其實是將FIB查找緩存到了下一跳(fib_nh)結構上,也就是下一跳緩存

爲何須要緩存下一跳呢? 咱們能夠先來看下沒有下一跳緩存的狀況。以轉發過程爲例,相關的僞代碼以下:

FORWARD:

fib_result = fib_lookup(skb)
dst_entry  = alloc_dst_entry(fib_result)
skb->dst = dst_entry;

skb->dst.output(skb)   
nexthop = rt_nexthop(skb->dst, ip_hdr(skb)->daddr)
neigh = ipv4_neigh_lookup(dev, nexthop)
dst_neigh_output(neigh,skb)
release_dst_entry(skb->dst)

內核利用FIB查詢的結果申請dst_entry, 並設置到skb上,而後在發送過程當中找到下一跳地址,繼而查找到鄰居結構(查詢ARP),而後鄰居系統將報文發送出去,最後釋放dst_entry

下一跳緩存的做用就是儘可能減小最初和最後的申請釋放dst_entry,它將dst_entry緩存在下一跳結構(fib_nh)上。這和以前的路由緩存有什麼區別嗎? 很大的區別!以前的路由緩存是以源IP和目的IP爲KEY,有千萬種可能性,而如今是和下一跳綁定在一塊兒,一臺設備沒有那麼多下一跳的可能。這就是下一跳緩存的意義!

early demux

early demux是在skb接收方向的加速方案。如前面所說,在取消了FIB查詢前的路由緩存後,每一個skb應該都須要查詢FIB。而early demux是基於一種思想:若是一個skb是本機某個應用程序的套接字須要的,那麼咱們能夠將路由的結果緩存在內核套接字結構上,這樣下次一樣的報文(四元組)到達後,咱們能夠在FIB查詢前就將報文提交給上層,也就是提早分流(early demux)

圖 early demux

總結

3.6版本將FIB查詢以前的路由緩存移除了,取而代之的是下一跳緩存。

REF

Route cache removed
IPV4 route cache removed from >= 3.6 linux kernel
remove routing cache
Linux3.5內核之後的路由下一跳緩存

相關文章
相關標籤/搜索