https://www.ibm.com/developerworks/cn/linux/l-cn-networkdriver/index.htmlhtml
網絡設備介紹
網絡設備是計算機體系結構中必不可少的一部分,處理器若是想與外界通訊,一般都會選擇網絡設備做爲通訊接口。衆所周知,在 OSI(Open Systems Interconnection,開放網際互連)中,網絡被劃分爲七個層次,從下到上分別是物理層、數據鏈路層、網絡層、傳輸層、會話層、表示層和應用層。咱們所講的網絡設備也包括兩個層次,一層叫作 MAC(Media Access Control)層,對應於 OSI 的數據鏈路層;另外一層叫作 PHY(Physical Layer)層,對應於物理層。linux
經常使用的網絡設備有不少,好比 PPC85XX 的 TSEC、AMCC 440GX 的 EMAC、INTEL 的 82559 等,它們的工做原理基本相同。編程
DMA 介紹
網絡設備的核心處理模塊是一個被稱做 DMA(Direct Memory Access)的控制器,DMA 模塊可以協助處理器處理數據收發。對於數據發送來講,它可以將組織好的數據自動發出,無需處理器干預;對於數據接收來講,它可以將收到的數據以必定的格式組織起來,通知處理器,並等待處理器來取。緩存
DMA 模塊收發數據的單元被稱爲 BD(Buffer Description,緩存描述符),每一個包都會被分紅若干個幀,而每一個幀則被保存在一個 BD 中。BD 結構一般包含有如下字段:網絡
1
2
3
4
5
|
typedef struct {
void *bufptr; /* 保存當前 BD 對應緩存的起始地址 */
int length; /* 保存緩存中存儲的數據包長度 */
int sc; /* 保存當前 BD 的狀態信息 */
} BD_STRUCT;
|
全部的 BD 就組成了一張 BD 表,如圖 1 所示,通常來講發送方向和接收方向的 BD 表是各自獨立的。數據結構
圖 1. BD 表結構

數據發送流程
網絡設備經過 DMA 進行數據發送的流程如 圖 2所示。less
圖 2. 數據發送流程
圖中各步驟的具體含義描述以下:socket
(1)協議層通知處理器開始發送數據;jsp
(2)處理器從 BD 表中取出一個 BD,將須要發送的數據拷貝至當前 BD 對應的緩存內,並設置好 BD 的狀態;ide
(3)處理器通知網絡設備開始發送數據;
(4)MAC 模塊通知 DMA 單元開始發送數據;
(5)DMA 模塊操做 BD 表,取出當前有效 BD;
(6)DMA 模塊將當前 BD 對應緩存內的數據發送至 MAC 模塊;
(7)MAC 模塊將這些數據發送到網絡中;
(8)網絡設備通知處理器數據發送完畢;
(9)處理器通知協議層發送下面一幀數據。
其中步驟(4)~(8)是硬件自動完成的,不須要軟件的干預,如此能夠節省處理器的工做量。
數據接收流程
網絡設備經過 DMA 進行數據接收的流程如圖 3 所示。
圖 3. 數據接收流程

圖中各步驟的具體含義描述以下:
(1)處理器初始化 BD 表;
(2)處理器初始化網絡設備;
(3)MAC 模塊從網絡中接收數據;
(4)MAC 模塊通知 DMA 模塊來取數據;
(5)DMA 模塊從 BD 表中取出合適的 BD;
(6)MAC 模塊將數據發送至當前 BD 對應的緩存內;
(7)網絡設備通知處理器開始接收數據(以中斷方式或輪詢方式);
(8)協議層從當前的 BD 緩存內取走數據。
其中步驟(3)~(6)是硬件自動完成的,不須要軟件的干預,如此能夠節省處理器的工做量。
Linux 網絡設備驅動模型
數據結構
數據結構
Linux 內核中對網絡設備進行描述的核心結構類型叫作 net_device,net_device 結構定義在 include/linux/netdevice.h 文件中。該結構的字段能夠分爲如下幾類。
全局信息
該類中包含了設備名(name 字段)、設備狀態(state 字段)、設備初始化函數(init 字段)等。
硬件信息
該類中包含了設備內存使用狀況(mem_end 和 mem_start 字段)、中斷號(irq 字段)、IO 基地址(base_addr 字段)等。
接口信息
該類中包含了 MAC 地址(dev_addr 字段)、設備屬性(flag 字段)、最大傳輸單元(mtu 字段)等。
設備接口函數
該類中包含了當前設備所提供的全部接口函數,好比設備打開函數(open 字段),該函數負責打開設備接口,當用戶使用 ifconfig 命令配置網絡時,該函數默認被調用;設備中止函數(stop 字段),該函數負責關閉設備接口;數據發送函數(hard_start_xmit 字段),當用戶調用 socket 開始寫數據時,該函數被調用,並負責往網絡設備中發送數據。
函數接口
設備初始化函數
網絡設備驅動在 Linux 內核中是之內核模塊的形式存在的,對應於模塊的初始化,須要提供一個初始化函數來初始化網絡設備的硬件寄存器、配置 DMA 以及初始化相關內核變量等。設備初始化函數在內核模塊被加載時調用,它的函數形式以下:
1
2
3
4
|
static int __init xx_init (void) {
……
}
module_init(xx_init); // 這句話代表模塊加載時自動調用 xx_init 函數
|
設備初始化函數主要完成如下功能:
1. 硬件初始化
由於網絡設備主要分爲 PHY、MAC 和 DMA 三個硬件模塊,開發者須要分別對這三個模塊進行初始化。
- 初始化 PHY 模塊,包括設置雙工 / 半雙工運行模式、設備運行速率和自協商模式等。
- 初始化 MAC 模塊,包括設置設備接口模式等。
- 初始化 DMA 模塊,包括創建 BD 表、設置 BD 屬性以及給 BD 分配緩存等。
2. 內核變量初始化
初始化並註冊內核設備。內核設備是屬性爲 net_device 的一個變量,開發者須要申請該變量對應的空間(經過 alloc_netdev 函數)、設置變量參數、掛接接口函數以及註冊設備(經過 register_netdev 函數)。
經常使用的掛接接口函數以下:
1
2
3
4
5
6
|
net_device *dev_p;
dev_p->open = xx_open; // 設備打開函數
dev_p->stop = xx_stop; // 設備中止函數
dev_p->hard_start_xmit = xx_tx; // 數據發送函數
dev_p->do_ioctl = xx_ioctl; // 其它的控制函數
……
|
數據收發函數
數據的接收和發送是網絡設備驅動最重要的部分,對於用戶來講,他們無需瞭解當前系統使用了什麼網絡設備、網絡設備收發如何進行等,全部的這些細節對於用戶都是屏蔽的。Linux 使用 socket 作爲鏈接用戶和網絡設備的一個橋樑。用戶能夠經過 read / write 等函數操做 socket,而後經過 socket 與具體的網絡設備進行交互,從而進行實際的數據收發工做。
Linux 提供了一個被稱爲 sk_buff 的數據接口類型,用戶傳給 socket 的數據首先會保存在 sk_buff 對應的緩衝區中,sk_buff 的結構定義在 include/linux/skbuff.h 文件中。它保存數據包的結構示意圖以下所示。
圖 4. sk_buff 數據結構圖

1. 數據發送流程
當用戶調用 socket 開始發送數據時,數據被儲存到了 sk_buff 類型的緩存中,網絡設備的發送函數(設備初始化函數中註冊的 hard_start_xmit)也隨之被調用,流程圖以下所示。
圖 5. 數據發送流程圖

- 用戶首先建立一個 socket,而後調用 write 之類的寫函數經過 socket 訪問網絡設備,同時將數據保存在 sk_buff 類型的緩衝區中。
- socket 接口調用網絡設備發送函數(hard_start_xmit),hard_start_xmit 已經在初始化過程當中被掛接成相似於 xx_tx 的具體的發送函數,xx_tx 主要實現以下步驟。
- 從發送 BD 表中取出一個空閒的 BD。
- 根據 sk_buff 中保存的數據修改 BD 的屬性,一個是數據長度,另外一個是數據包緩存指針。值得注意的是,數據包緩存指針對應的必須是物理地址,這是由於 DMA 在獲取 BD 中對應的數據時只能識別儲存該數據緩存的物理地址。
12
bd_p->length = skb_p->len;
bd_p->bufptr = virt_to_phys(skb_p->data);
- 修改該 BD 的狀態爲就緒態,DMA 模塊將自動發送處於就緒態 BD 中所對應的數據。
- 移動發送 BD 表的指針指向下一個 BD。
- DMA 模塊開始將處於就緒態 BD 緩存內的數據發送至網絡中,當發送完成後自動恢復該 BD 爲空閒態。
2. 數據接收流程
當網絡設備接收到數據時,DMA 模塊會自動將數據保存起來並通知處理器來取,處理器經過中斷或者輪詢方式發現有數據接收進來後,再將數據保存到 sk_buff 緩衝區中,並經過 socket 接口讀出來。流程圖以下所示。
圖 6. 數據接收流程圖

- 網絡設備接收到數據後,DMA 模塊搜索接收 BD 表,取出空閒的 BD,並將數據自動保存到該 BD 的緩存中,修改 BD 爲就緒態,並同時觸發中斷(該步驟可選)。
- 處理器能夠經過中斷或者輪詢的方式檢查接收 BD 表的狀態,不管採用哪一種方式,它們都須要實現如下步驟。
- 從接收 BD 表中取出一個空閒的 BD。
- 若是當前 BD 爲就緒態,檢查當前 BD 的數據狀態,更新數據接收統計。
- 從 BD 中取出數據保存在 sk_buff 的緩衝區中。
- 更新 BD 的狀態爲空閒態。
- 移動接收 BD 表的指針指向下一個 BD。
- 用戶調用 read 之類的讀函數,從 sk_buff 緩衝區中讀出數據,同時釋放該緩衝區。
中斷和輪詢
Linux 內核在接收數據時有兩種方式可供選擇,一種是中斷方式,另一種是輪詢方式。
中斷方式
若是選擇中斷方式,首先在使用該驅動以前,須要將該中斷對應的中斷類型號和中斷處理程序註冊進去。網絡設備驅動在初始化時會將具體的 xx_open 函數掛接在驅動的 open 接口上,xx_open 函數掛接中斷的步驟以下。
1
2
|
request_irq(rx_irq, xx_isr_rx, …… );
request_irq(tx_irq, xx_isr_tx, …… );
|
網絡設備的中斷通常會分爲兩種,一種是發送中斷,另外一種是接收中斷。內核須要分別對這兩種中斷類型號進行註冊。
- 發送中斷處理程序(xx_isr_tx)的工做主要是監控數據發送狀態、更新數據發送統計等。
- 接收中斷處理程序(xx_isr_rx)的工做主要是接收數據並傳遞給協議層、監控數據接收狀態、更新數據接收統計等。
對於中斷方式來講,因爲每收到一個包都會產生一箇中斷,而處理器會迅速跳到中斷服務程序中去處理收包,所以中斷接收方式的實時性高,但若是遇到數據包流量很大的狀況時,過多的中斷會增長系統的負荷。
輪詢方式
若是採用輪詢方式,就不須要使能網絡設備的中斷狀態,也不須要註冊中斷處理程序。操做系統會專門開啓一個任務去定時檢查 BD 表,若是發現當前指針指向的 BD 非空閒,則將該 BD 對應的數據取出來,並恢復 BD 的空閒狀態。
因爲是採用任務定時檢查的原理,從而輪詢接收方式的實時性較差,但它沒有中斷那種系統上下文切換的開銷,所以輪詢方式在處理大流量數據包時會顯得更加高效。
Linux 網絡設備驅動優化
隨着科技的不斷髮展,網絡設備所能承載的速率在不斷提高,當前流行的網絡設備廣泛都能支持 10Mbps / 100Mbps / 1Gbps 這三種速率。雖然網絡設備的硬件性能在不斷的提高,可是實際在 Linux 系統中其運行性能(收發包速率)真能達到多達 1Gbps 的水平嗎?這和處理器的性能有關,通常來講咱們運行的系統中報文的收發速率是達不到 1Gbps 的(由於咱們不可能將全部處理器的資源都貢獻給報文的收發),可是咱們能夠在有限的條件下儘量的採起一些優化手段提升網絡設備的運行性能。
Cache 的應用
Cache 位於存儲系統金字塔的頂層(下面一層是內存),Cache 的容量不大(一級 Cache 通常是幾十 KB,二級 Cache 通常是幾 MB),可是它的訪問速率倒是內存的幾十倍。所以若是處理器經過 Cache 來訪問內存,將會極大的提升訪問速率。在網絡設備的數據收發中,恰當的應用 Cache 能夠優化驅動的性能。下面列舉幾點 Cache 的優化措施。
合理設置內存屬性
內存的頁表有多種屬性,其中有一項就是是否經過 Cache 訪問。在給 BD 表配置內存時,這些被分配的內存屬性須要支持 Cache 訪問。
Cache 的訪問還有兩種方式:一種是寫回操做(Write Back),處理器更新內存數據時,該數據首先保存在 Cache 中,Cache 並不及時將數據更新進內存,而是等到 Cache 須要再次更新時纔會將數據寫回到內存中。另外一種是寫穿操做(Write Through),處理器更新內存數據時,該數據首先保存在 Cache 中,Cache 隨即將數據馬上更新進內存。顯而易見,寫回操做的性能比寫穿操做更高,一般咱們設置內存頁表屬性爲寫回方式。
數據收發時的 Cache 操做
在內存支持 Cache 且採用寫回方式的狀況下,當發送數據時,處理器先將數據寫進 Cache,若是 DMA 模塊直接從內存中取出數據發送的話,該數據將與 Cache 並不一致,所以在驅動程序中,須要將 Cache 中的數據更新到內存,而後再通知 DMA 進行發送。
當接收數據時,DMA 模塊會將數據收到內存中,若是這時候處理器從該內存接收數據的話,處理器會從 Cache 中取數據,可是 Cache 並不知道內存已經被更新,這就會致使接收到的數據與實際不符,所以在驅動程序中,須要在接收數據以前刷新一下該 Cache,以保證 Cache 與內存的一致性。
須要說明的是,並非全部處理器都須要以上操做,有的處理器所帶的 DMA 控制器是能感知 Cache(IO-Cache Coherence)的,它們可以自動進行上述的 Cache 操做,所以對於這類處理器,驅動程序中無需關注 Cache。
中斷仍是輪詢?
前面曾經介紹過,網絡設備驅動支持兩種接收數據的方式,一種是中斷,另外一種是輪詢,在數據流量比較大的狀況下,能夠考慮採用輪詢的方式以達到更高的效率。
當採用輪詢方式時,還有一個不得不考慮的問題,那就是輪詢任務優先級的選擇,衆所周知,當任務優先級高時,該任務不會被其餘的低優先級任務所打斷,從而能夠保證處理器可以專心完成數據接收工做;但若是任務優先級低時,一旦發生了其餘高優先級的任務,處理器會將當前的數據接收工做暫停,轉而執行別的任務,如此會影響網絡設備驅動的效率。所以驅動設計者須要結合實際狀況,恰當的選擇任務的優先級。
設備接口模式
有時候咱們會發現雖然網絡設備號稱有 100Mbps 的速率,可是實際數據收發卻很是慢,遇到這種狀況,咱們首先須要檢查網絡設備接口模式是否設置正確。
PHY 模塊接口模式
PHY 模塊的接口模式有兩種,強制模式(強制 10M / 100M / 1G 等)和自協商模式。究竟選擇哪一種模式須要看當前 PHY 模塊所鏈接的對端 PHY 狀態才行,若是對端設置的是自協商模式,本端的 PHY 模塊也須要相應設置成自協商,如此就可以保證協商出來的結果是當前鏈路所能支持的最大速率。反之,若是對端設置成強制模式,本端也須要設置成強制,且強制速率要與對端設置的強制速率相同。
MAC 模塊接口模式
MAC 模塊對於不一樣的速率(10M / 100M / 1G 等)也會有不一樣的接口模式選擇,若是設置的模式與 PHY 模塊所運行的速率不匹配的話,會極大的影響網絡設備數據收發的速度。所以在初始化 MAC 模塊時,須要檢查 PHY 模塊的運行速率,從而選擇恰當的接口模式。
每一個 PHY / MAC 模塊設備的接口模式選擇都不盡相同,所以在開發網絡設備驅動時,須要明確所使用的設備,並在該設備初始化時正確配置其接口模式。
結束語
Linux 網絡設備驅動與具體的設備關聯很大,所以在實際編程中須要結合具體設備來寫驅動代碼,咱們在開發過程當中要格外注意驅動的優化,由於網絡設備驅動的好壞將直接影響到整個系統的性能。
相關主題
- Linux Device Drivers,Jonathan Corbet、Alessandro Rubini、Greg Kroah-Hartman 著,東南大學出版社。
- 參考 MPC8548E PowerQUICC III Integrated Processor Family Reference Manual,FreeScale。
- 在 developerWorks Linux 專區 尋找爲 Linux 開發人員(包括 Linux 新手入門)準備的更多參考資料,查閱咱們 最受歡迎的文章和教程。
- 在 developerWorks 上查閱全部 Linux 技巧 和 Linux 教程。