Classless Queuing Disciplines (qdiscs)
本文涉及的隊列規則(Qdisc)都可以作為接口上的主qdisc,或作為一個classful qdiscs的葉子類。這些是Linux下使用的基本調度器。默認的調度器為pfifo_fast
。
6.1 FIFO,先進先出(pfifo和bfifo)
注:雖然FIFO是隊列系統中最簡單的元素之一,但pfifo和bfifo都不是Linux接口上的默認qdisc。參見 Section 6.2, “
pfifo_fast
, the default Linux qdisc”了解更多關於默認qdisc(pfifo_fast
)的信息。
6.1.1 pfifo, bfifo算法
FIFO算法是所有Linux網絡接口的默認qdisc(pfifo_fast
)。它不會對報文進行整流和重排,僅在接收到報文並在報文入隊列后盡快將其發送出去。這也是新創建的類使用的qdisc(除非使用其他的qdisc或類替換FIFO)。
FIFO算法維護了一個報文列表,當一個報文入隊列后,會將其插入隊列末尾。當需要將一個報文發送到網絡上時,會將列表首部的報文進行發送。
6.1.2 limit參數
真實的FIFO qdisc必須限制列表的大小(緩沖大小)來防止溢出,這種情況下無法將接收到的報文入隊列。Linux實現了兩個基本的FIFO qdisc,一個基於字節數,另一個基於報文。如果不考慮使用的FIFO類型,隊列的大小由參數limit
決定。對於pfifo(Packet limited First In, First Out queue)
,其單位為報文,對於bfifo(Byte limited First In, First Out queue)
,其單位為字節。
對於pfifo,大小默認等於接口的txqueuelen,可以使用ifconfig或ip查看。該參數的范圍為[0, UINT32_MAX]。
對於bfifo,大小默認等於txqueuelen乘以接口MTU。該參數的范圍為[0, UINT32_MAX]字節。在計算報文長度時會考慮鏈路層首部。
例6. 為一個報文FIFO或字節FIFO指定一個limit
[root@leander]# cat bfifo.tcc
/*
* make a FIFO on eth0 with 10kbyte queue size
*
*/
dev eth0 {
egress {
fifo (limit 10kB );
}
}
[root@leander]# tcc < bfifo.tcc
# ================================ Device eth0 ================================
tc qdisc add dev eth0 handle 1:0 root dsmark indices 1 default_index 0
tc qdisc add dev eth0 handle 2:0 parent 1:0 bfifo limit 10240
[root@leander]# cat pfifo.tcc
/*
* make a FIFO on eth0 with 30 packet queue size
*
*/
dev eth0 {
egress {
fifo (limit 30p );
}
}
[root@leander]# tcc < pfifo.tcc
# ================================ Device eth0 ================================
tc qdisc add dev eth0 handle 1:0 root dsmark indices 1 default_index 0
tc qdisc add dev eth0 handle 2:0 parent 1:0 pfifo limit 30
6.1.3. tc –s qdisc ls
tc -s qdisc ls
的輸出包含limit(報文數或字節數),以及實際發送的字節數和報文數。未發送和丟棄的報文使用括號括起來,並不計算在Sent
之內。
本例中,隊列長度為1000個報文,發送的681個報文的大小為45894 字節。沒有丟包,由於pfifo不會降低報文的速率,因此沒有overlimits
:
$ tc -s qdisc ls dev eth0
qdisc pfifo 8001: dev eth0 limit 100p
Sent 45894 bytes 681 pkts (dropped 0, overlimits 0)
如果出現積壓(overlimits),也會顯示出來。
與所有非默認的qdisc一樣,pfifo和bfifo會維護統計數據。
6.2. pfifo_fast, 默認的Linux qdisc
pfifo_fast (three-band first in, first out queue)
qdisc是Linux上所有接口使用的默認qdisc。當創建一個接口后,會自動使用pfifo_fast qdisc隊列。如果附加了其他qdisc,這些qdisc則會搶占默認的pfifo_fast,當移除現有的qdisc時,pfifo_fast會自動恢復運行。
6.2.1. pfifo_fast
算法
該算法基於傳統的FIFO qdisc
,但同時也提供了一些基於優先級的處理。它使用三個不同的band(獨立的FIFO)來分割流量。具有最高優先級的流量(交互式流量)會進入band 0,總是會被優先處理。類似地,在band 2出隊列之前,band 1中不會存在未處理的報文。
該算法與classful prio qdisc非常類似。pfifo_fast qdisc就像三個並排的pfifo隊列,一個報文可以根據其服務類型(ToS)位進入其中某一個FIFO。三個band並不能同時入隊列(當具有最小值,即優先級高的band包含流量時,具有高數值的,即優先級低的band就不能出隊列)。這樣可以優先處理交互流量,或者對“最低成本”的流量進行懲罰。每個band最多可以容納txqueuelen 大小的報文,可以使用ifconfig
或ip
配置。當接收到額外的報文時,如果特定的band滿了,則會丟棄該報文。
參見下面的6.2.3章節來了解ToS為如何轉換為band。
6.2.2. txqueuelen 參數
三個band的長度取決於接口的txqueuelen
。
6.2.3. Bugs
終端用戶無需對pfifo_fast qdisc進行配置。下面是默認配置。
priomap
決定了報文的優先級,由內核分配並映射到bands。內核會根據報文的八個比特位的ToS進行映射,ToS如下:
四個ToS位的定義如下:
可以使用 tcpdump -v -v 顯示整個報文的ToS字段(不僅僅是四個比特位的內容)。
上面展示了很多數值,第二列包含於ToS比特位相關的數值,后面是其對應的意義。例如15表示期望最小貨幣開銷,最大可靠性,最大吞吐量以及最小延遲。
上述四列的內容給出了Linux是如何解析ToS 比特位的,以及它們被映射到的優先級,如優先級4映射到的band 號為1。允許映射到更高的優先級(>7),但這類優先級與ToS映射無關,表示其他意義。
最后一列給出了默認的優先級映射的結果。在命令行中,默認的優先級映射為:
1, 2, 2, 2, 1, 2, 0, 0 , 1, 1, 1, 1, 1, 1, 1, 1
與其他非標准的qdisc不同,pfifo_fast不會維護信息,且不會展示在tc qdisc ls
命令中。這是因為它是默認的qdisc。
6.3. SFQ, 隨機公平隊列
隨機公平隊列是tc命令使用的用於流量控制的classless qdisc。SFQ不會整流,僅負責根據流來調度傳輸的報文,目的是保證公平,這樣每個流都能夠依次發送數據,防止因為單條流影響了其他流的傳輸速率。SFQ使用一個哈希函數將流分到不同的FIFO中,使用輪詢的方式出隊列。因為在哈希函數的選擇上存在不公平的可能性,該函數會周期性地改變,通過擾動(參數perturb)來設置此周變動期性(參見6.3.3參數)。
因此,SFQ qdisc會在任意多的流中,嘗試給每條流分配相同的機會來發送數據。
6.3.1. SFQ 算法
在進入隊列之后,會基於報文的哈希值給每個報文分配一個哈希桶。該哈希值可能是從外部的流分類器獲取到的,如果沒有配置外部分類器,則使用默認的內部分類器。當使用內部分類器時,sfq會使用:
- 源地址
- 目的地址
- 源和目的端口
SFQ能夠區分ipv4,ipv6,以及UDP,TCP和ESP等。其他協議的報文會基於32位的目的和源進行哈希。一條流大多數對應一個TCP/IP連接。
每個桶都應該表示唯一的一條流。由於多條流可能哈希到同一個桶,sdq的內部哈希算法可能會在可配置的時間間隔內受到干擾,但不公平僅會持續很短的一段時間。然而,擾動可能會在無意中導致發送報文的重排。在Linux 3-3之后,就不會存在報文重排的問題,但可能在重哈希達到上限(流的數目或每條流的報文數)后丟棄報文。
當出隊列時,會以輪詢的方式請求每個哈希桶的數據。
在Linux3-3之前,SFQ 的最大長度為128個報文,即最多可以分布在128個桶(1024個可用桶)上。當發生溢出時,滿的桶會發生尾部丟棄,從而保持公平性。
在Linux3-3之后,SFQ 的最大長度為65535 個報文,除數的極限是65536。當發生溢出時,除非明確要求頭部丟棄,否則滿的桶會發生尾部丟棄,
6.3.2. 命令行使用
tc qdisc ... [divisor hashtablesize] [limit packets] [perturb seconds] [quantum bytes] [flows number] [depth number] [head drop] [redflowlimit bytes] [min bytes] [max bytes] [avpkt bytes] [burst packets] [probability P] [ecn] [harddrop]
6.3.3. 參數
- divisor: 可以用於設置不同的哈希表大小,從2.6.39內核開始可用。指定的除數必須是2的冪,並且不能大於65536。默認為1024。
- limit: SFQ limit的上限。可以用於減少默認的127個報文長度。在linux-3.3之后,可以增加該值。
- depth: 每條流的報文限制(linux-3.3之后)。默認為127,可以降低。
- perturb: 隊列算法干擾的時間間隔(秒)。默認為 0,意味着不會發生干擾。不要設置過低的值,因為每個干擾都可能導致報文的重排或丟失。建議值為60。當使用外部流分類時,該值將不起作用。為了降低哈希沖突,可以增加該建議值。
- quantum: 在輪詢處理期間允許出隊列的字節數。默認為接口的MTU,這也是建議的最小值。
- flows: 在linux-3.3之后,可以修改默認的流的限制,默認為127。
- headdrop: 默認的SFQ行為是尾部丟棄一條流的報文。也可以使用首部丟棄,可以給TCP流提供更好的反饋。
- redflowlimit: 在每個SFQ流上配置可選的RED模塊(用於防止產生bufferbloat問題)。RED(Random Early Detection)的原理是以概率的方式標記或丟棄報文。redflowlimit對每條SFQ流隊列的大小作了硬性限制,單位為字節。(以下參數為RED的參數)
- min: 可以進行標記的平均隊列大小,默認為max的三分之一。
- max: 在此平均隊列大小下,標記的概率最大。默認為 redflowlimit的四分之一。
- probability: 可以用於標記的最大概率,為一個0.0到1.0的浮點數,默認為0.02。
- avpkt: 以字節為單位。與burst一起用於確定平均隊列大小計算的時間常數,默認為1000。
- burst: 用於確定真實隊列大小對平均隊列大小的影響速度,默認為: (2 * min + max) / (3 * avpkt)。
- ecn: RED可以執行"標記"或"丟棄 "。顯式的擁塞通知(Explicit Congestion Notification)允許RED通知遠程主機它們的速率超過了可用帶寬。沒有啟用ECN的主機可能會接收到報文丟棄的通知。如果指定了該參數,那么支持ECN的主機的報文將會被標記,而不會被丟棄(除非隊列滿)。
- harddrop: 如果平均流隊列長度大於max字節數,該參數會強制丟棄報文,而不會執行ecn標記。
SFQ中比較容易混淆的是參數:limit,depth,flows這三個參數。limit用於限制SFQ的隊列數目,depth用於限制每條流的數目,flows用於限制流的數目。SFQ會對報文進行哈希,將哈希結果相同的報文作為同一條流上的報文,然后將這條流單獨放到一個隊列中(受限於哈希算法,有可能存在實際上多個不同的流被哈希成了SFQ中的同一條流,因此引入了perturb)。
6.3.4 例子和用法
附加到ppp0口。
$ tc qdisc add dev ppp0 root sfq
請注意SFQ和其他非整流的qdisc一樣,僅對其擁有的隊列有效。鏈路速度等於實際可用的帶寬時就是這種情況。 適用於常規電話調制解調器,ISDN連接和直接非交換式以太網鏈接。
大多數情況下,有線調制解調器和DSL設備不屬於這一類。 當連接到交換機並嘗試將數據發送到同樣連接到該交換機的擁塞段時,情況也是如此。在這種情況下,有效隊列並不在Linux內,因此不能用於調度。在classful qdisc中嵌入SFQ可以確保它擁有該隊列。
可以在sfq中使用外部分類器,例如基於源/目的IP地址對流量進行哈希:
$ tc filter add ... flow hash keys src,dst perturb 30 divisor 1024
注意給定的divisor應該匹配sfq使用的一個哈希表。如果修改了sfq的divisor的默認值1024,則流哈希過濾器也會使用相同的值。
例7. 帶可選RED模塊的SFQ
[root@leander]# tc qdisc add dev eth0 parent 1:1 handle 10: sfq limit 3000 flows 512 divisor 16384 redflowlimit 100000 min 8000 max 60000 probability 0.20 ecn headdrop
例8. 創建一個SFQ
[root@leander]# cat sfq.tcc
/*
* make an SFQ on eth0 with a 10 second perturbation
*
*/
dev eth0 {
egress {
sfq( perturb 10s );
}
}
[root@leander]# tcc < sfq.tcc
# ================================ Device eth0 ================================
tc qdisc add dev eth0 handle 1:0 root dsmark indices 1 default_index 0
tc qdisc add dev eth0 handle 2:0 parent 1:0 sfq perturb 10
不幸的是,一些聰明的軟件(如Kazaa和eMule等)會通過打開盡可能多的TCP會話(流)來消除公平排隊帶來的好處。在很多網絡中,對於行為良好的用戶,SFQ可以充分地將網絡資源分配給競爭的流,但當遭受惡意軟件入侵網絡時,可能需要采取其他措施。
可以參見說明文檔:man tc-sfq。
SFQ是多隊列算法,RED是單隊列算法,可以通過結合兩個算法來達到更好的流量控制的目的。
6.4. ESFQ, 擴展隨機公平隊列
從概念上而言,雖然這類qdisc相比SFQ給用戶提供了更多的參數,但它與SFQ並沒有什么不同。該qdisc旨在解決上述SFQ的缺點。通過允許用戶控制用於分配網絡帶寬的哈希算法(hash
參數),有可能實現更加公平的帶寬分配。
例9. ESFQ用法
Usage: ... esfq [ perturb SECS ] [ quantum BYTES ] [ depth FLOWS ]
[ divisor HASHBITS ] [ limit PKTS ] [ hash HASHTYPE]
Where:
HASHTYPE := { classic | src | dst }
6.5. RED,Random Early Drop
隨機早期探測(Random Early Detection)是一種靈活的用於管理隊列大小的classless qdisc。一般的隊列在滿后會從尾部丟棄報文,這種行為有可能不是最優的。RED也會執行尾部丟棄,但是以一種更平緩的方式。
一旦隊列達到特定的平均長度,入隊列的報文會有一定的(可配置)概率會被標記(有可能意味着丟棄該報文),這個概率會線性地增加到某一點,稱為最大平均隊列長度(隊列也可能會變更大)。
相比簡單的尾部丟棄,這樣做有很多好處,且不會占用大量處理器。這種方式可以避免在流量突增之后導致的同步重傳(這些重傳會導致更多的重傳)。這樣做的目的是使用一個比較小的隊列長度,在信息的交互的同時不會因為在流量突增之后導致的丟包而干擾TCP/IP流量。
取決於配置的ECN,標記有可能意味着丟棄或僅僅表示該報文是超限的報文。
6.5.2. Algorithm
平均隊列大小用於確定標記的概率,該概率是使用指數加權平均算法計算出來的,通過該值可以調節對流量突發的敏感度。當平均隊列大小低於最小字節時,此時不會標記任何報文;當超過最小字節時,概率會直線上升到probability(參數指定),直到平均隊列大小達到最大字節數。因為通常不會將概率設置為100%,而隊列大小也可能會超過最大字節,因此,limit參數用於硬性設置隊列大小的最大值。
大致工作方式為:
- 低於min:此時不做任何處理,隊列壓力較小,可以直接正常處理。
- 在min和max之間:此時界定為隊列感受到阻塞壓力,開始按照某一幾率P從隊列中丟包,幾率計算公式為:P = probability * (平均隊列長度 - min)/(max - min)。
- 高於max:此時新入隊的請求也將丟棄。
6.5.3. 用法
$ tc qdisc ... red limit bytes [min bytes] [max bytes] avpkt bytes [burst packets] [ecn] [harddrop] [bandwidth rate] [probability chance] [adaptive]
6.5.4. 參數
- min: 可能進行標記的平均隊列大小。默認為 max/3.
- max: 當平均隊列大小達到該值后,標記的概率值是最大的。為了避免同步重傳,應該至少是min的兩倍,且大於最小的min值,默認為limit/4。
- probability: 標記的概率的最大值,為0.0到1.0之間的浮點數,建議值為0.01或0.02(分別表示1%或2%),默認為0.02。
- limit: 真實(非平均)隊列的硬性限制,單位為字節。應該大於max+burst的值。建議將這個值設置為最大值的幾倍。
- burst: 用於決定平均隊列大小受真實隊列大小影響的速度。更大的值會延緩計算的速度,從而允許在標記開始之前出現更長的流量突發。實際經驗遵循如下准則: (min+min+max)/(3*avpkt)。
- Avpkt: 單位是字節,與burst一起確定平均隊列長度的時間常數。建議值1000。
- bandwidth: 該速率用於在一段空閑時間之后計算平均隊列的大小,設置為接口的帶寬。並不意味着RED會進行整流。可選,默認值為10Mbit。
- ecn: 如上所述,RED可以執行"標記"或"丟棄",顯式擁塞通知允許RED通知遠程主機它們的速率超過了可用帶寬。不支持ECN的主機僅會通知報文丟棄。如果指定了該參數,支持ECN的主機上的報文只會被標記而不會被丟棄(除非隊列達到limit的字節數),推薦使用。
- harddrop: 如果平均流大小大於max字節數,該參數會強制丟棄報文,而不是進行ECN標記。
- adaptive: (linux-3.3新加的功能 ) 在自適應模式中設置RED,參見 http://icir.org/floyd/papers/adaptiveRed.pdf,自適應的RED的目的是在1%和50%之間動態設置
probability
,以達到目標平均隊列:(max-min)/2。
6.5.5. 例子
# tc qdisc add dev eth0 parent 1:1 handle 10: red limit 400000 min 30000 max 90000 avpkt 1000 burst 55 ecn adaptive bandwidth 10Mbit
6.6. GRED, Generic Random Early Drop
GRED用在DiffServ 實現中,且在物理隊列中包含虛擬隊列(VQ)。當前,虛擬隊列的數值限制為16。
GRED分兩步配置:首先是通用的參數,用於選擇虛擬隊列DPs的數目,以及是否打開類RIO的緩沖區共享方案。此時會選擇一個默認的虛擬隊列。
其次為單獨的虛擬隊列設置參數。
6.6.1. 用法
... gred DP drop-probability limit BYTES min BYTES max BYTES avpkt BYTES burst PACKETS probability PROBABILITY bandwidth KBPS [prio value]
OR
... gred setup DPs "num of DPs" default "default DP" [grio]
6.6.2. 參數
- setup: 表示這是一個GRED的通用設置
- DPs: 虛擬隊列的數目
- default: 指定默認的虛擬隊列
- grio: 啟用類RIO的緩沖方案
- limit: 定義虛擬隊列的"物理"限制,單位字節
- min: 定義最小的閾值,單位字節
- max: 定義最大的閾值,單位字節
- avpkt: 平均報文大小,單位字節
- bandwidth: 接口的線路速度
- burst: 允許突發的平均大小的報文數目
- probability: 定義丟棄的概率范圍 (0…);
- DP: 標識分配給這些參數的虛擬隊列;
- prio: 如果在通用參數中設置了grio,則表示虛擬隊列的優先級
6.7. TBF,令牌桶過濾器
該qdisc構建在令牌和桶上。它會對接口上傳輸的流量進行整形(支持整流)。為了限制特定接口上出隊列的報文的速度,TBF qdisc是個不錯的選擇。它僅會將傳輸的流量下降到特定的速率。
只有在包含足夠的令牌時才能傳輸報文。否則,會推遲報文的發送。以這種方式延遲的報文將報文的往返時間中引入人為的延遲。
6.7.1. 算法
如其名稱所示,對流量的過濾會基於消耗的令牌。令牌會大致對應到字節數,每個報文會消耗一個令牌,無論該報文有多小。這樣會導致零字節的報文占用一定的鏈路時間。在創建時,TBF會保存一定的令牌,以應對一次性流量突發的量。令牌會以穩定的速率放到桶中,直到桶滿為止。如果沒有可用的令牌,則報文會保留在隊列中(隊列中的報文不能超過配置的上限)。TBF會計算令牌的虧空,並進行節流,直到可以發送隊列中的第一個報文。如果不能接收最大速度的報文突發,可以配置峰值速率來限制桶清空的速度。峰值速率使用第二個TBF來實現,其桶相對較小,因此不會發生突發。
6.7.2. 參數
-
limit or latency: limit表示可以在隊列中等待令牌的字節數。還可以通過設置延遲參數的方式變相地設置此值,延遲參數指定了報文可以在TBF中停留的最大時間。后者的計算會使用到桶的大小,速率和峰值速率(如果設置) 。這兩個參數是互斥的。
-
Burst: 即突發或最大突發。等於桶的大小,單位是字節。這是令牌在一瞬間可用的最大字節數。通常大的整流速率需要大的緩沖。如對於Intel上的10mbit/s,則至少需要10kbyte的緩沖才能跟上配置的速率。如果緩存過小,可能導致報文丟失,此時每個時間點到達的報文要大於桶的可用容量。最小的緩沖大小可以用頻率除以HZ計算出來。
對令牌使用的計算是通過一個表來計算的,該表默認情況下有8個報文的分辨率。可以通過指定突發的單元大小來更改此分辨率。例如,為了指定一個6000字節的緩沖,其單元大小為16字節,需要將突發設置為6000/16。單元大小必須是2的整數次冪。
-
Mpu: 零大小的報文不會使用零帶寬。對於以太網來說,報文的大小不能小於64字節。最小的報文單元(MTU)決定了一個報文使用的最小令牌(單位字節),默認是0。
-
Rate: 速度旋鈕。此外,如果需要峰值速率,可以使用以下參數:
-
peakrate: 桶的最大消耗速率。只有在需要毫秒級別的整流時才會用到峰值速率。
-
mtu/minburst: 指定了峰值速率的桶大小。為了精確計算,應該將其設置為MTU的大小。如果用了峰值速率,但有些突發又是可以接受的,則可以增加該值的大小。一個3000字節的minburst可以允許3mbit/s的峰值速率,支持1000字節的報文。與常規的突發大小一樣,也可以指定單元大小。
6.7.3. 例子
例10. 創建一個 256kbit/s 的TBF
[root@leander]# cat tbf.tcc
/*
* make a 256kbit/s TBF on eth0
*
*/
dev eth0 {
egress {
tbf( rate 256 kbps, burst 20 kB, limit 20 kB, mtu 1514 B );
}
}
[root@leander]# tcc < tbf.tcc
# ================================ Device eth0 ================================
tc qdisc add dev eth0 handle 1:0 root dsmark indices 1 default_index 0
tc qdisc add dev eth0 handle 2:0 parent 1:0 tbf burst 20480 limit 20480 mtu 1514 rate 32000bps