Linux 網絡堆棧的排隊機制(二)

排隊準則(QDisc)

我們已經瞭解到,驅動隊列只是簡單的先入先出隊列,它不能將來自不同數據流的包區分開來。這樣的設計能使網卡驅動軟件變得小巧並且有更高的效率。需要注意的是,一些更加先進的以太網和無線網卡可以支持多種相互獨立的傳送隊列,但是它們實際上都是非常類似的先進先出隊列而已。系統中更高層負責在其中選擇一種隊列進行使用。

夾在IP數據棧和驅動隊列中間的,是排隊準則(QDisc)層(見Figure1),它負責實現Linux內核中的(數據傳輸)交通管理功能,比如交通分類,優先級別和速率協商。QDisc層的配置比起其他層要更加困難一些。要了解QDisc層,必須瞭解三個非常重要的概念:排隊規則,類和過濾器。

排隊準則(QDisc)是Linux對交通隊列的抽象,但是比“先入先出”要複雜一些。通過網絡接口,QDisc能獨立地進行復雜的隊列管理,而不需要改變IP數據棧或者網卡驅動的運行模式。每個網絡接口都會默認被設爲pfifo_fast QDisc ,pfifo_fast QDisc在TOS的基礎上,實現了簡單的三帶優先機制(three band prioritization scheme)。雖然是默認機制,但是這並不代表它是最好的機制,因爲pfifo_fast QDisc的隊列深度很大(在下面txqueuelen中會有解釋),並且它也不能區分不同數據流。

第二個和QDisc緊密相關的概念是類。單個QDisc能通過實現不同類,對數據中的不同部分進行不同處理。例如,層級表示桶(the Hierarchical Token Bucket (HTB))QDisc,能使用戶配置500Kbps 和 300Kbps的類,並且控制數據使之進入用戶希望其進入的類中。並不是所有QDisc都實現這種功能,我們一般把能夠這樣分類的QDisc稱爲——可分類的QDiscs。

過濾器(也叫分類器)是一種分類的機制,用來將數據分類到特定QDisc或類中。過濾器的種類很多,它們的複雜度也都不相同。u32 是其中最普通也最容易使用的一種。現在還沒有針對u32的文檔,但是你可以在這裏找到一個例子one of my QoS scripts。

在LARTC HOWTO and the tc man pages,你可以瞭解到更多關於QDiscs、類和過濾器的細節。

傳輸層和排隊準則間的緩存

你可能已經注意到了,在原來的圖例中,排隊準則層之上就沒有數據包的隊列了。這意味着網絡數據棧要麼直接把數據包放入排隊準則中,要麼把數據包推回它的上一層(比如套接字緩存的情況),如果這時隊列已滿的話。接下來一個很明顯的問題是,當棧有很多數據包要發送的時候該怎麼辦?當TCP的擁塞窗口很大,或者某個應用正在快速發送UDP數據包時,就會出現要發送很多數據包的情況。對於只有一個隊列的QDisc來說,類似問題在圖例4中就已經出現過了。這裏的問題是,一個帶寬很大或者包速率很大的數據流會把隊列裏的所有空間佔滿,造成數據包丟失和延時的問題。更糟的是,這樣很可能會使另一個緩存產生,進而產生一個靜止隊列(standing queue),造成更嚴重的延時並使TCP的RTT和擁塞窗口的計算出現問題。由於Linux默認使用僅有一個隊列的pfifo_fast QDisc(因爲大多數數據流的TOS=0),所以這樣的問題非常常見。

Linux3.6.0(2012-09-30)出現後,Linux內核增加了TCP小隊列(TCP small queue)的機制,用於解決該問題。TCP小隊列對每個TCP數據流中,能夠同時參與排隊的字節數做出了限制。這樣會產生意想不到的作用,能使內核將數據推回原先的應用,使得應用能更有效地優先處理寫入套接字的請求。目前(2012-12-28),除TCP之外的其他傳輸協議的單個數據流,還是有可能阻塞QDsic層的。

另一種能部分解決傳輸層阻塞的辦法,是使用有許多隊列的QDsic層,最好是對每一個數據流都能有一個隊列。SFQ和fq_codel的QDsic都能夠很好地解決這個問題,使每個數據流都分到一個隊列。

如何控制Linux中的隊列長度

驅動隊列

對於以太網設備,可以用ethtool命令來控制隊列長度。ethtool提供底層的接口數據,並能控制IP數據棧和驅動的各種特性。

-g 能夠將驅動隊列的參數展示出來:

1

2

3

4

5

6

7

8

9

10

11

12

[root@alpha net-next]# ethtool -g eth0

Ring parameters for eth0:

Pre-set maximums:

RX:        16384

RX Mini:    0

RX Jumbo:    0

TX:        16384

Current hardware settings:

RX:        512

RX Mini:    0

RX Jumbo:    0

TX:        256

從上面可以看出,網卡驅動默認在傳輸隊列中有256個描述符。前面我們提到過,爲了免緩存丟包等情況,應該減小驅動隊列的大小,這同時也能減小延時。引入BQL後,就沒有必要再去調整驅動隊列的大小了(下文中有配置BQL的介紹)。

ethtool也能用於調整優化特性,如TSO、UFO和GSO。-k 指令能展示出現在卸貨(offload)狀態,-K能調整這些狀態。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

[dan@alpha ~]$ ethtool -k eth0

Offload parameters for eth0:

rx-checksumming: off

tx-checksumming: off

scatter-gather: off

tcp-segmentation-offload: off

udp-fragmentation-offload: off

generic-segmentation-offload: off

generic-receive-offload: on

large-receive-offload: off

rx-vlan-offload: off

tx-vlan-offload: off

ntuple-filters: off

receive-hashing: off

因爲TSO, GSO, UFO 和 GRO大大增加了能夠參與排隊的字節數,如果相比於吞吐量你更在意延時的話,你就應該禁用這些優化。一般來說禁用這些優化,不會使你感到CPU和吞吐量收到了影響,除非你的系統要處理的數據率很大。

字節隊列限制(BQL)

BQL算法能夠自我適應,所以應該沒什麼必要去經常調整它。但是,如果你很關注最大在低碼率上的最大延時的話,可能你會想要比現有LIMIT更大的上限值。在/sys 的目錄中可以找到配置BQL的文件,目錄的具體地址取決於網卡的位置和名字。在我的服務器上,eth0的目錄是:

1

/sys/devices/pci0000:00/0000:00:14.0/net/eth0/queues/tx-0/byte_queue_limits

裏面的文件有:

  • hold_time:修改LIMIT間的時間(millisecond爲單位)。

  • inflight:參與排隊但沒有發送的字節數。

  • limit:BQL計算出的LIMIT值。如果該網卡不支持BQL機制,則爲0。

  • limit_max:能夠修改的LIMIT最大值。調低該值能減小延時。

  • limit_min:能夠修改的LIMIT最小值。調高該值能增大吞吐量。

爲能夠參與排隊的字節數設置一個上限,改一下limit_max 文件就行了:

1

echo "3000" > limit_max

什麼是txqueuelen?

在前面我們已經提到了減小網卡傳輸隊列大小的作用。當前的隊列大小能夠通過ip和ifconfig命令獲得。但是令人困惑地是,兩個命令得到的隊列長度不同:

1

2

3

4

5

6

7

8

9

10

[dan@alpha ~]$ ifconfig eth0

eth0      Link encap:Ethernet  HWaddr 00:18:F3:51:44:10  

          inet addr:69.41.199.58  Bcast:69.41.199.63  Mask:255.255.255.248

          inet6 addr: fe80::218:f3ff:fe51:4410/64 Scope:Link

          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1

          RX packets:435033 errors:0 dropped:0 overruns:0 frame:0

          TX packets:429919 errors:0 dropped:0 overruns:0 carrier:0

          collisions:0 txqueuelen:1000

          RX bytes:65651219 (62.6 MiB)  TX bytes:132143593 (126.0 MiB)

          Interrupt:23


1

2

3

4

5

[dan@alpha ~]$ ip link

1: lo:  mtu 16436 qdisc noqueue state UNKNOWN

    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

2: eth0:  mtu 1500 qdisc pfifo_fast state UP qlen 1000

    link/ether 00:18:f3:51:44:10 brd ff:ff:ff:ff:ff:ff

默認的傳輸隊列長度爲1000包,在低帶寬下這已經是很大的緩存了。

有趣的是,這個變量到底控制着什麼東西呢?我以前也不知道,於是花了很多時間閱讀內核代碼。就我所知,txqueuelen只是丟某些排隊準則的默認隊列長度。這些排隊準則有:

  • pfifo_fast (Linux default queueing discipline)

  • sch_fifo

  • sch_gred

  • sch_htb (only for the default queue)

  • sch_plug

  • sch_sfb

  • sch_teql

再來看圖例1,txqueuelen參數控制着上面準則的隊列大小。對於大多數其中的隊列,tc命令行中的limit參數,都超過了txqueuelen的默認大小。總之,如果你不使用上面的隊列準則,或者隊列長度超過了exqueuelen大小,txqueuelen都是沒有意義的。

再說一句,還有一點使我非常困惑,ifconfig命令顯示的是如mac地址一類的底層網絡接口細節,但是txqueuelen顯示的卻是更高層的QDisc層的信息。如果ifconfig顯示驅動隊列的大小可能會更合理些。

通過ip或者ifconfig命令能夠控制傳輸隊列的長度:

1

[root@alpha dan]# ip link set txqueuelen 500 dev eth0

注意,ip命令使用 ‘txqueuelen’,但在顯示接口細節時使用的是‘qlen’。

隊列準則

前面介紹過,Linux 內核有很多QDsic,每個QDisc有自己的包隊列排隊方法。在這篇文章裏描述清楚如何配置這些QDisc的細節是不可能的。想要了解所有細節,可以參考man page(man tc)。在‘man tc qdisc-name’ (ex: ‘man tc htb’ or ‘man tc fq_codel’)中,能夠找到每個QDisc的細節。LARTC也能找到許多有用的資源,但是缺少一些特性的資料。

下面是一些使用的tc命令的小技巧:

  • 如果數據包沒有經過過濾器分類,HTBQDisc的默認隊列會接收所有的數據包。其他QDisc比如DRR會把所有未分類的數據都接收進來。可以通過“tc qdisc show”中的direct_packets_stat查看到所有沒有分類然後直接進入到隊列中的數據包。

  • HTB類僅對沒有帶寬分配的分類起作用。所有帶寬分配會在查看它們的葉子和相關的優先級時發生。

  • QDisc設施(infrastructure)會鑑別出帶有大小數目(major and minor numbers )QDisc和類,它們被冒號分開。大數(major number)是QDisc的標識符,小數(minor number)標識QDisc中的類。tc命令使用十六進制來表示這些數字。因爲很多字符串在十六和十進制中都一樣(10以內),所以許多用戶一直不知道tc使用十六進制表示法。可以在my tcscripts裏看看我是如何對付它的。

  • 如果你使用的是ADSL,並且基於ATM(幾乎所有DSL服務都是基於ATM的,但也有例外,比如基於VDSL2的),你很可能會想要加入“linklayer adsl”選項。它的意思是將IP數據包分解成53個字節大小的ATM塊的上限。

  • 如果你正在使用PPPoE,那你很可能會想通過‘overhead’參數來調整PPPoE的上限。

TCP 小隊列

每個套接字TCP隊列的大小限制,可以通過下面的/proc文件來查看和控制:

1

/proc/sys/net/ipv4/tcp_limit_output_bytes

我覺得一般情況下都不需要修改這個文件。

你控制不了的超大隊列

不幸的是,並不是所有影響網絡性能超大隊列都能夠被我們控制。很常見的是,問題往往出在服務提供商提供的裝置和其他配套的設備(比如DSL或者電纜調製器)上。當服務提供商的裝置本身有問題時,就沒什麼辦法了,因爲你不可能控制向你傳輸的數據。但是,在逆向上,你可以控制數據流使之比鏈路率(link rate)稍微低些。這會讓裝置中的隊列不會有幾對數據包出現。很多家庭路由器都有鏈路率限制的功能,你能夠通過它來控制你的數據流低於鏈路率。如果你將Linux Box作爲路由器,控制數據流同時也會使內核的排隊機制運行地更加高效。你能找到很多在線的tc腳本,比如the one I use with some related performance results。

概要

在每個使用分組交換的網絡中,數據包緩存的排隊機制都是非常必要的。控制這些緩存的包大小是至關重要的,能直接影響到網絡延時等問題。雖然靜態的緩存大小分配對於降低延時也很有效,但是更好的方法還是用更加智能的方法動態控制包的大小。實現動態控制的最好方法是使用動態機制,比如BQL和active queue management(AQM)技術,比如Codel。本文描述了數據包時如何在Linux的網絡棧中進行排隊的,並且介紹了相關特性的配置方法,給出了降低延時的建議。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章