?? (ldd) ch14-網絡驅動程序(下)(轉載).txt
字號:
(LDD) Ch14-網絡驅動程序(下)(轉載)
打開和關閉
我們的驅動程序可以在模塊加載和核心引導時探測接口。下一步是給接口賦一個地址,
這樣驅動程序就可以通過它交換數據了。打開和關閉一個接口由ifconfig命令完成。
當使用ifconfig為一個接口賦地址時,它完成兩項工作。第一,它通過
ioctl(SIOCSIFADDR)(即Socket I/O Control Set InterFace ADDRess)來賦地址。接著
它通過ioctl(SIOCSIFFLAGS)(即Socket I/O Control Set InterFace FLAGS) 對dev->fl
ag中的IFF_UP置位來打開接口。
至于設備,ioctl(SIOCSIFADDR)設置dev->pa_addr,dev->family,dev->pa_mask,dev-
>pa_brdaddr,沒有驅動程序函數被調用----這個任務是設備無關的,由核心來完成。不
過,后一個命令ioctl(SIOCSIFFLAGS)為設備調用open方法。
過,后一個命令ioctl(SIOCSIFFLAGS)為設備調用open方法。
類似地,當一個接口關閉時,ifconfig使用ioctl(SIOCSIFFLAGS)來清除IFF_UP,并且調
用stop方法。
兩個設備方法在成功時都返回0,發生錯誤時,通常返回一個負值。
至于代碼,驅動程序必須執行與字符和塊設備同樣的工作。open請求它所需要的所有的
系統資源,并告訴接口啟動;stop則關閉接口,并釋放系統資源。
如果驅動程序不準備使用共享中斷(例如,它不打算與舊的核心兼容),還有最后一步
需要做。核心引出一個irq2dev_map陣列,它由IRQ號尋址,持有空指針;驅動程序也許
想用這個數組將中斷號映射到指向device結構的指針。這是在不使用接口處理程序的情
況下,在一個驅動程序里支持一個以上接口的唯一方法。
另外,在接口可以和外界通信以前,硬件地址還必須從板上復制到dev->dev_addr。硬件
地址可以按驅動程序的意愿在探測時或打開時被賦值。snull軟件接口時從open里對其賦
值;它用兩個ASCII串偽造一個硬件號碼。地址的第一個字節是個空字符(在后面的“地
址解析”中解釋)。
結果得到的open代碼如下所示:
(代碼319)
(代碼319)
(代碼320 #1)
正如你所看到的,device結構中的幾個域被修改了。start表明接口已準備好, tbusy斷
言發送者不忙(也就是說,核心可以發出一個包)。
stop方法是open的操作的反轉。由于這個原因,實現 stop的函數通常調用 close。
(代碼320 #2)
網絡接口執行的最重要的工作是數據發送和接收。我準備從發送開始,因為它相對比較
簡單。
當核心需要發送一個數據包時,它調用hard_start_transmit方法將數據放到一個輸出隊
列。核心處理的每個包包含在一個套接字緩沖區結構(struct sk_buff)中,其定義見<
linux/skbuff.h>。這個結構從Unix用來表示一個網絡連接的抽象,即套接字得名。即使
接口與套接字無關,每個網絡包在較高的網絡層中一定屬于某個套接字,任何套接字的
輸入輸出緩沖區都是sk_buff結構的列表。同樣的sk_buff結構在整個Linux網絡子系統中
輸入輸出緩沖區都是sk_buff結構的列表。同樣的sk_buff結構在整個Linux網絡子系統中
都被用來承載網絡數據,但在考慮接口時,一個套接字緩沖區就是一個包。
指向sk_buff的指針通常被稱做skb,我將在示例和正文中都使用使用這個習慣。
套接字緩沖區是一個復雜的結構,核心提供一組函數來對其操作。這些函數在后面的“
套接字緩沖區”中描述----目前,知道sk_buff的一些基本事實已足以寫出可工作的驅動
程序。另外,我習慣于在扎入另人討厭的細節之前先弄明白是如何工作的。
傳遞給hard_start_xmit的套接字緩沖區含有物理包,它具有傳輸層的包頭。接口不需要
修改被發送的數據。skb->data指向被發送的包,skb->len是它的長度,以八元組為單位
。
snull的包發送代碼如下所示;物理發送機制被隔離在另一個函數中,因為每個接口驅動
程序必須按照被驅動的特定硬件來實現它。
(代碼321)
這樣發送函數只進行一些清晰的對包的檢查,并通過硬件相關的函數發送數據。在一個
中斷表明一個“發送結束”的條件時,dev->tbusy被清除。
包接收
從網絡中接收數據比發送要復雜一些,因為必須分配一個sk_buff,并從一個中斷處理程
序中將其交遞給高層----接收包的最好的辦法是通過中斷,除非接口是象snull一樣是純
軟件的,或是環回接口。盡管有可能寫輪詢的驅動程序,而且在正式的核心里也的確有
幾個,但中斷驅動的要好的多,不管是在數據吞吐率還是計算需求上。由于絕大多數網
絡接口都是中斷驅動的,我不打算談論輪詢實現,它只是利用了核心計時器。
snull的實現是將硬件細節和設備無關的工作分離開的。這樣,在硬件收到一個包后,sn
ull_rx被調用,它已經在計算機的內存中了。snull_rx因此收到一個指向數據的指針和
包的長度。。這個函數唯一的責任就是將包和一些額外信息發送到網絡代碼的高層。其
代碼與數據指針及長度獲得的方法無關。
(代碼322)
這個函數足夠通用,可以作為任何網絡驅動程序模版,但在你有信心重用這個代碼段之
前還需要一些解釋。
注意緩沖區分配函數需要知道數據長度。者避免了在調用kmalloc時浪費內存。dev_allo
c_skb以原子優先級調用分配函數,因此它也可以在中斷時安全地使用。核心還提供了套
接字緩沖區分配的其它一些接口,但不值得在這里介紹;套接字緩沖區在本章后面的“
套接字緩沖區”中詳細介紹。
套接字緩沖區”中詳細介紹。
一旦有了一個有效的skb指針,就可以通過調用memcpy將包數據復制到這個緩沖區。skb_
put更新緩沖區中數據尾的指針,并返回一個指向新生成空間的指針。
不幸的是,包頭中沒有足夠的信息來正確處理網絡層----在緩沖區向上層傳遞之前,dev
和protocol域必須被賦值。接著我們需要指定如何執行校驗和(snull不進行任何校驗和
)。skb->ip_summed可能的策略為:
CHECKSUM_HW
板子用硬件執行校驗和。一個硬件校驗和的離子是Sparc HME接口。
CHECKSUM_NONE
校驗和完全有軟件完成。對新分配的緩沖區,這是缺省的策略。
CHECKSUM_UNNECESSARY
不做任何校驗和。這是snull和環回接口的策略。
在1.2核心版本中沒有校驗和選項和ip_summed。
最后,驅動程序更新它的統計計數器記錄一個新包被收到了。統計結構有幾個域組成,
最重要的是rx_packets和tx_packets,它們包含收到的和發送的包的個數。所有的域在
后面的“統計信息”中給出一個徹底的描述。
包接收的最后一步由netif_rx完成,它將套接字緩沖區遞交到上一層。
中斷驅動的操作
大多數硬件接口以中斷處理程序的方式控制。接口中斷處理器表明兩種事件中的一種:
一個新包到達了或一個包發送完成了。這種一般化并不是總適用,但它基本上揭示了與
異步包傳送相關的問題。PLIP和PPP是不適用這種一般化的例子。它們處理同樣的事件,
但低級中斷處理略有不同。
一般的中斷例程可以通過檢查在硬件設備上的一個狀態寄存器來分辨新包到達中斷與完
成發送的通知。snull接口工作方式類似,但其狀態字在dev->priv中。網絡接口的中斷
處理程序看起來如下:
(代碼324)
處理程序的第一個任務是接收一個指向正確的device結構的指針。你可以用irq2dev_map
處理程序的第一個任務是接收一個指向正確的device結構的指針。你可以用irq2dev_map
[](假如你在打開是給它賦了一個值)或者接收到的dev_id指針作為一個參數。如果你
希望驅動程序可以與新于1.3.70的核心工作,你必須使用irq2dev_map[],因為早期版本
中沒有dev_id。
這個處理程序中有趣的部分是處理“發送完成”的部分。接口通過清除dev->tbusy并標
志網絡下半部例程來相應發送完成。如果net_bh的確運行了,它會試圖發送所有等待的
包。
另一方面,包接收并不需要任何特殊的中斷處理。所有需要做的就是調用snull_rx。
實際上,當netif_rx被接收函數調用時,它所進行的實際操作只有標志net_bh。換句話
說,核心在一個下半部處理程序中完成了所有網絡相關的工作。因此,網絡驅動程序應
該總是宣稱它的中斷處理程序太慢,因為下半部將會更早地執行(見第九章中“下半部
設計”)。
套接字緩沖區
我們已經討論了于網絡接口相關的多數內容。下面幾節我們將更細地討論sk_buff時如何
設計的。這幾節既介紹這個結構的主要域,也介紹在套接字緩沖區上操作的函數。
盡管并沒有理解sk_buff內部的嚴格需要,但是如果能理解它的內容將會有助于你解決問
題和優化代碼。例如,如果你看了loopback.c,你會發現一個基于sk_buff內部知識的優
化。
我不打算在這里描述整個結構,而只是那些可能被驅動程序用到的域。如果你想知道更
多,你可以看<linux/skbuff.h>,結構的定義和函數的原形都在那里定義。至于這些域
和函數如何使用的細節可以通過瀏覽核心源碼得到。
重要的域
出于我們的目的,結構里重要的域是那些驅動程序的作者可能要用到的域。它們如下所
示,無特別順序。
struct device *dev;
設備接收或者發送這個緩沖區。
__u32 saddr;
__u32 daddr;
__u32 raddr;
__u32 raddr;
源地址,目的地址,和路由器地址,由IP協議使用。raddr是包要到達其目的地的第一步
。這些域在包被發送前被設置,收到之后就不必賦值了。到達hard_start_xmit方法的外
出包已經有了一個合適的硬件包頭設置反映了“第一步”信息。
unsigned char *head;
unsigned char *data;
unsigned char *tail;
unsigned char *end;
這些指針用來訪問包中的數據。head指向分配空間的開始,data是有效八元組的開始(
通常比head略大),tail是有效八元組的結束, end指向tail可以到達的最大地址。觀
察它們的另一個方法是:可用緩沖區空間為skb->end-skb->head,當前使用的數據空間
為skb->tail-skb->data。這種處理內存區域的清晰方法在1.3開發時才實現。這是snull
沒有被移植在Linux1.2上編譯的主要原因。
unsigned long len;
數據本身的長度(skb->tail-skb->head)。
數據本身的長度(skb->tail-skb->head)。
unsigned char ip_summed;
這個域有驅動程序對進來包設置,由TCP/UDP校驗和使用。它在前面的“包接收”中介紹
過。
unsigned char pkt_type;
這個域被內部用來發送進來包。驅動程序負責將其設置為PACKET_HOST(這個包是我的)
, PACKET_BROADCAST,PACKET_MULTICAST,或是PACKET_OTHERHOST(不,這個包不是我
的) 。以太網驅動程序并不顯式地修改pkt_type,因為eth_type_trans會為它做這件事
。
union { unsigned char *raw; […]} mac;
與pkt_type類似,這個域被用來處理進來包,必須在包接收時設置。函數eth_type_tran
s為以太網驅動程序負責這件事。非以太網驅動程序應設置skb->mac.raw指針,后面“非
以太網包頭”中將會提到。
結構中其余的域并無特別興趣。它們被用來維護緩沖區列表,解釋占有緩沖區的套接字
結構中其余的域并無特別興趣。它們被用來維護緩沖區列表,解釋占有緩沖區的套接字
的內存,等等。
在套接字緩沖區上操作的函數
使用sock_buff的網絡設備通過正式的接口函數在這個結構上操作。有很多在套接字緩沖
區上操作的函數,下面是最有趣的一些:
struct sk_buff *alloc_skb(unsigned int len, int priority);
struct sk_buff *dev_alloc_skb(unsigned int len);
分配一個緩沖區。alloc_skb分配一個緩沖區并初始化skb->data和skb->tail到skb->hea
d。 dev_alloc_skb函數(在Linux1.2中沒有)一個快捷方式,它用GFP_ATOMIC優先級調
用alloc_skb,并反轉skb->head和skb->data之間的16個字節。這個數據空間可以用來“
推”硬件包頭。
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -