歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> 看Linux網管員如何進行網絡性能優化

看Linux網管員如何進行網絡性能優化

日期:2017/2/25 11:57:36   编辑:關於Linux

  IT系統的性能永遠是企業IT人員關注熱點,而隨著Linux系統應用的增多,一些應用方面的問題也隨之增多,本文將為大家介紹Linux內核下如何進行網絡性能優化。

  首先我們先看下網絡的行為,可以簡單劃分為3條路徑:發送路徑、轉發路徑、接收路徑,而網絡性能的優化則可基於這3條路徑來考慮。本文集中於發送路徑和接收路徑上的優化方法分析,其中的NAPI本質上是接收路徑上的優化,但因為它在Linux的內核出現時間較早,而它也是後續出現的各種優化方法的基礎,所以將其單獨分析。

  最為基本的 NAPI

  NAPI 的核心在於:在一個繁忙網絡,每次有網絡數據包到達時,不需要都引發中斷,因為高頻率的中斷可能會影響系統的整體效率,假象一個場景,我們此時使用標准的 100M 網卡,可能實際達到的接收速率為 80MBits/s,而此時數據包平均長度為 1500Bytes,則每秒產生的中斷數目為:

  80M bits/s / (8 Bits/Byte * 1500 Byte) = 6667 個中斷 /s

  每秒 6667 個中斷,對於系統是個很大的壓力,此時其實可以轉為使用輪詢 (polling) 來處理,而不是中斷;但輪詢在網絡流量較小的時沒有效率,因此低流量時,基於中斷的方式則比較合適,這就是 NAPI 出現的原因,在低流量時候使用中斷接收數據包,而在高流量時候則使用基於輪詢的方式接收。

  現在內核中 NIC 基本上已經全部支持 NAPI 功能,由前面的敘述可知,NAPI 適合處理高速率數據包的處理,而帶來的好處則是:

  1、中斷緩和 (Interrupt mitigation),由上面的例子可以看到,在高流量下,網卡產生的中斷可能達到每秒幾千次,而如果每次中斷都需要系統來處理,是一個很大的壓力,而 NAPI 使用輪詢時是禁止了網卡的接收中斷的,這樣會減小系統處理中斷的壓力;

  2、數據包節流 (Packet throttling),NAPI 之前的 Linux NIC 驅動總在接收到數據包之後產生一個 IRQ,接著在中斷服務例程裡將這個 skb 加入本地的 softnet,然後觸發本地 NET_RX_SOFTIRQ 軟中斷後續處理。如果包速過高,因為 IRQ 的優先級高於 SoftIRQ,導致系統的大部分資源都在響應中斷,但 softnet 的隊列大小有限,接收到的超額數據包也只能丟掉,所以這時這個模型是在用寶貴的系統資源做無用功。而 NAPI 則在這樣的情況下,直接把包丟掉,不會繼續將需要丟掉的數據包扔給內核去處理,這樣,網卡將需要丟掉的數據包盡可能的早丟棄掉,內核將不可見需要丟掉的數據包,這樣也減少了內核的壓力。

  對NAPI 的使用,一般包括以下的幾個步驟:

  1、在中斷處理函數中,先禁止接收中斷,且告訴網絡子系統,將以輪詢方式快速收包,其中禁止接收中斷完全由硬件功能決定,而告訴內核將以輪詢方式處理包則是使用函數 netif_rx_schedule(),也可以使用下面的方式,其中的 netif_rx_schedule_prep 是為了判定現在是否已經進入了輪詢模式:

  將網卡預定為輪詢模式

void netif_rx_schedule(struct net_device *dev);
或者
if (netif_rx_schedule_prep(dev))
__netif_rx_schedule(dev);

  2、在驅動中創建輪詢函數,它的工作是從網卡獲取數據包並將其送入到網絡子系統,其原型是:

  NAPI 的輪詢方法

int (*poll)(struct net_device *dev, int *budget);

  這裡的輪詢函數用於在將網卡切換為輪詢模式之後,用 poll() 方法處理接收隊列中的數據包,如隊列為空,則重新切換為中斷模式。切換回中斷模式需要先關閉輪詢模式,使用的是函數 netif_rx_complete (),接著開啟網卡接收中斷 .。

  退出輪詢模式

void netif_rx_complete(struct net_device *dev);

  3、在驅動中創建輪詢函數,需要和實際的網絡設備 struct net_device 關聯起來,這一般在網卡的初始化時候完成,示例代碼如下:

  設置網卡支持輪詢模式

dev->poll = my_poll;
dev->weight = 64;

  裡面另外一個字段為權重 (weight),該值並沒有一個非常嚴格的要求,實際上是個經驗數據,一般 10Mb 的網卡,我們設置為 16,而更快的網卡,我們則設置為 64。

  NAPI的一些相關Interface

  下面是 NAPI 功能的一些接口,在前面都基本有涉及,我們簡單看看:

  netif_rx_schedule(dev)

  在網卡的中斷處理函數中調用,用於將網卡的接收模式切換為輪詢

  netif_rx_schedule_prep(dev)

  在網卡是 Up 且運行狀態時,將該網卡設置為准備將其加入到輪詢列表的狀態,可以將該函數看做是 netif_rx_schedule(dev) 的前半部分

  __netif_rx_schedule(dev)

  將設備加入輪詢列表,前提是需要 netif_schedule_prep(dev) 函數已經返回了 1

  __netif_rx_schedule_prep(dev)

  與 netif_rx_schedule_prep(dev) 相似,但是沒有判斷網卡設備是否 Up 及運行,不建議使用

  netif_rx_complete(dev)

  用於將網卡接口從輪詢列表中移除,一般在輪詢函數完成之後調用該函數。

  __netif_rx_complete(dev)

  Newer newer NAPI

  其實之前的 NAPI(New API) 這樣的命名已經有點讓人忍俊不禁了,可見 Linux 的內核極客們對名字的掌控,比對代碼的掌控差太多,於是乎,連續的兩次對 NAPI 的重構,被戲稱為 Newer newer NAPI 了。

  與 netif_rx_complete(dev) 類似,但是需要確保本地中斷被禁止

  Newer newer NAPI

  在最初實現的 NAPI 中,有 2 個字段在結構體 net_device 中,分別為輪詢函數 poll() 和權重 weight,而所謂的 Newer newer NAPI,是在 2.6.24 版內核之後,對原有的 NAPI 實現的幾次重構,其核心是將 NAPI 相關功能和 net_device 分離,這樣減少了耦合,代碼更加的靈活,因為 NAPI 的相關信息已經從特定的網絡設備剝離了,不再是以前的一對一的關系了。例如有些網絡適配器,可能提供了多個 port,但所有的 port 卻是共用同一個接受數據包的中斷,這時候,分離的 NAPI 信息只用存一份,同時被所有的 port 來共享,這樣,代碼框架上更好地適應了真實的硬件能力。Newer newer NAPI 的中心結構體是napi_struct:

  NAPI 結構體

/*
* Structure for NAPI scheduling similar to tasklet but with weighting
*/
struct napi_struct {
/* The poll_list must only be managed by the entity which
* changes the state of the NAPI_STATE_SCHED bit. This means
* whoever atomically sets that bit can add this napi_struct
* to the per-cpu poll_list, and whoever clears that bit
* can remove from the list right before clearing the bit.
*/
struct list_head poll_list;

unsigned long state;
int weight;
int (*poll)(struct napi_struct *, int);
#ifdef CONFIG_NETPOLL
spinlock_t poll_lock;
int poll_owner;
#endif

unsigned int gro_count;

struct net_device *dev;
struct list_head dev_list;
struct sk_buff *gro_list;
struct sk_buff *skb;
};

  熟悉老的 NAPI 接口實現的話,裡面的字段 poll_list、state、weight、poll、dev、沒什麼好說的,gro_count 和 gro_list 會在後面講述 GRO 時候會講述。需要注意的是,與之前的 NAPI 實現的最大的區別是該結構體不再是 net_device 的一部分,事實上,現在希望網卡驅動自己單獨分配與管理 napi 實例,通常將其放在了網卡驅動的私有信息,這樣最主要的好處在於,如果驅動願意,可以創建多個 napi_struct,因為現在越來越多的硬件已經開始支持多接收隊列 (multiple receive queues),這樣,多個 napi_struct 的實現使得多隊列的使用也更加的有效。

  與最初的 NAPI 相比較,輪詢函數的注冊有些變化,現在使用的新接口是:

void netif_napi_add(struct net_device *dev, struct napi_struct *napi,
int (*poll)(struct napi_struct *, int), int weight)

  熟悉老的 NAPI 接口的話,這個函數也沒什麼好說的。

  值得注意的是,前面的輪詢 poll() 方法原型也開始需要一些小小的改變:

int (*poll)(struct napi_struct *napi, int budget);

  大部分 NAPI 相關的函數也需要改變之前的原型,下面是打開輪詢功能的 API:

void netif_rx_schedule(struct net_device *dev,
struct napi_struct *napi);
/* ...or... */
int netif_rx_schedule_prep(struct net_device *dev,
struct napi_struct *napi);
void __netif_rx_schedule(struct net_device *dev,
struct napi_struct *napi);

  輪詢功能的關閉則需要使用:

void netif_rx_complete(struct net_device *dev,
struct napi_struct *napi);

  因為可能存在多個 napi_struct 的實例,要求每個實例能夠獨立的使能或者禁止,因此,需要驅動作者保證在網卡接口關閉時,禁止所有的 napi_struct 的實例。

  函數 netif_poll_enable() 和 netif_poll_disable() 不再需要,因為輪詢管理不再和 net_device 直接管理,取而代之的是下面的兩個函數:

void napi_enable(struct napi *napi);
void napi_disable(struct napi *napi); 上一頁123下一頁查看全文 內容導航
  • 第1頁:最為基本的 NAPI
  • 第2頁:發送路徑上的優化
  • 第3頁:接收路徑上的優化
Copyright © Linux教程網 All Rights Reserved