?? (ldd) ch14-網絡驅動程序(下)(轉載).htm
字號:
<TD width="8%" height=4>
<DIV align=center><A href="mailto:joyfire@sina.com"><FONT
color=#ffffff>聯系</FONT></A></DIV></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE>
<TABLE borderColor=#666666 cellPadding=2 width="90%" align=center border=2>
<TBODY>
<TR>
<TD bgColor=#000000>
<P align=center><A
href="http://211.71.69.201/joyfire/lsdp/index.htm"><FONT color=#ffffff
size=2>目錄頁</FONT></A> | <A
href="http://211.71.69.201/joyfire/lsdp/17.htm"><FONT color=#ffffff
size=2>上一頁</FONT></A> | <A
href="http://211.71.69.201/joyfire/lsdp/19.htm"><FONT color=#ffffff
size=2>下一頁</FONT></A></P>
<P align=center><FONT face=黑體 color=#ffffff size=6>(LDD)
Ch14-網絡驅動程序(下)(轉載) </FONT></P><SPAN
style="LINE-HEIGHT: 1; LETTER-SPACING: 0pt"><FONT color=#ffffff size=3>
<P>發信人: Altmayer (alt), 信區: GNULinux<BR>標 題: (LDD) Ch14-網絡驅動程序(下)(轉載)<BR>發信站: 飲水思源 (2001年12月13日08:57:59 星期四), 站內信件<BR> <BR>【 以下文字轉載自 <FONT
color=#00ff00>UNIXpost </FONT>討論區 】<BR>【 原文由<FONT
color=#00ff00> altmayer.bbs@bbs.nju.edu.cn,</FONT> 所發表 】<BR> <BR>【 以下文字轉載自 <FONT
color=#00ff00>altmayer </FONT>的信箱 】<BR> <BR> <BR>打開和關閉<BR> <BR>我們的驅動程序可以在模塊加載和核心引導時探測接口。下一步是給接口賦一個地址,<BR>這樣驅動程序就可以通過它交換數據了。打開和關閉一個接口由ifconfig命令完成。<BR> <BR>當使用ifconfig為一個接口賦地址時,它完成兩項工作。第一,它通過<BR>ioctl(SIOCSIFADDR)(即Socket I/O Control Set InterFace ADDRess)來賦地址。接著<BR>它通過ioctl(SIOCSIFFLAGS)(即Socket I/O Control Set InterFace FLAGS) 對dev->fl<BR>ag中的IFF_UP置位來打開接口。<BR> <BR>至于設備,ioctl(SIOCSIFADDR)設置dev->pa_addr,dev->family,dev->pa_mask,dev-<BR><FONT
color=#00ffff>>pa_brdaddr,沒有驅動程序函數被調用----這個任務是設備無關的,由核心來完成。不</FONT><BR>過,后一個命令ioctl(SIOCSIFFLAGS)為設備調用open方法。<BR></P></FONT><FONT
color=#ffffff size=3>
<P>過,后一個命令ioctl(SIOCSIFFLAGS)為設備調用open方法。<BR> <BR>類似地,當一個接口關閉時,ifconfig使用ioctl(SIOCSIFFLAGS)來清除IFF_UP,并且調<BR>用stop方法。<BR> <BR>兩個設備方法在成功時都返回0,發生錯誤時,通常返回一個負值。<BR> <BR>至于代碼,驅動程序必須執行與字符和塊設備同樣的工作。open請求它所需要的所有的<BR>系統資源,并告訴接口啟動;stop則關閉接口,并釋放系統資源。<BR> <BR>如果驅動程序不準備使用共享中斷(例如,它不打算與舊的核心兼容),還有最后一步<BR>需要做。核心引出一個irq2dev_map陣列,它由IRQ號尋址,持有空指針;驅動程序也許<BR>想用這個數組將中斷號映射到指向device結構的指針。這是在不使用接口處理程序的情<BR>況下,在一個驅動程序里支持一個以上接口的唯一方法。<BR> <BR>另外,在接口可以和外界通信以前,硬件地址還必須從板上復制到dev->dev_addr。硬件<BR>地址可以按驅動程序的意愿在探測時或打開時被賦值。snull軟件接口時從open里對其賦<BR>值;它用兩個ASCII串偽造一個硬件號碼。地址的第一個字節是個空字符(在后面的“地<BR>址解析”中解釋)。<BR> <BR>結果得到的open代碼如下所示:<BR> <BR>(代碼319)<BR></P></FONT><FONT
color=#ffffff size=3>
<P>(代碼319)<BR> <BR>(代碼320 #1)<BR> <BR>正如你所看到的,device結構中的幾個域被修改了。start表明接口已準備好, tbusy斷<BR>言發送者不忙(也就是說,核心可以發出一個包)。<BR> <BR>stop方法是open的操作的反轉。由于這個原因,實現 stop的函數通常調用 close。<BR> <BR>(代碼320 #2)<BR> <BR> <BR> <BR>包發送<BR> <BR>網絡接口執行的最重要的工作是數據發送和接收。我準備從發送開始,因為它相對比較<BR>簡單。<BR> <BR>當核心需要發送一個數據包時,它調用hard_start_transmit方法將數據放到一個輸出隊<BR>列。核心處理的每個包包含在一個套接字緩沖區結構(struct sk_buff)中,其定義見<<BR>linux/skbuff.h>。這個結構從Unix用來表示一個網絡連接的抽象,即套接字得名。即使<BR>接口與套接字無關,每個網絡包在較高的網絡層中一定屬于某個套接字,任何套接字的<BR>輸入輸出緩沖區都是sk_buff結構的列表。同樣的sk_buff結構在整個Linux網絡子系統中<BR></P></FONT><FONT
color=#ffffff size=3>
<P>輸入輸出緩沖區都是sk_buff結構的列表。同樣的sk_buff結構在整個Linux網絡子系統中<BR>都被用來承載網絡數據,但在考慮接口時,一個套接字緩沖區就是一個包。<BR> <BR>指向sk_buff的指針通常被稱做skb,我將在示例和正文中都使用使用這個習慣。<BR> <BR>套接字緩沖區是一個復雜的結構,核心提供一組函數來對其操作。這些函數在后面的“<BR>套接字緩沖區”中描述----目前,知道sk_buff的一些基本事實已足以寫出可工作的驅動<BR>程序。另外,我習慣于在扎入另人討厭的細節之前先弄明白是如何工作的。<BR> <BR>傳遞給hard_start_xmit的套接字緩沖區含有物理包,它具有傳輸層的包頭。接口不需要<BR>修改被發送的數據。skb->data指向被發送的包,skb->len是它的長度,以八元組為單位<BR>。<BR> <BR>snull的包發送代碼如下所示;物理發送機制被隔離在另一個函數中,因為每個接口驅動<BR>程序必須按照被驅動的特定硬件來實現它。<BR> <BR>(代碼321)<BR> <BR>這樣發送函數只進行一些清晰的對包的檢查,并通過硬件相關的函數發送數據。在一個<BR>中斷表明一個“發送結束”的條件時,dev->tbusy被清除。<BR> <BR> <BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR>包接收<BR> <BR>從網絡中接收數據比發送要復雜一些,因為必須分配一個sk_buff,并從一個中斷處理程<BR>序中將其交遞給高層----接收包的最好的辦法是通過中斷,除非接口是象snull一樣是純<BR>軟件的,或是環回接口。盡管有可能寫輪詢的驅動程序,而且在正式的核心里也的確有<BR>幾個,但中斷驅動的要好的多,不管是在數據吞吐率還是計算需求上。由于絕大多數網<BR>絡接口都是中斷驅動的,我不打算談論輪詢實現,它只是利用了核心計時器。<BR> <BR>snull的實現是將硬件細節和設備無關的工作分離開的。這樣,在硬件收到一個包后,sn<BR>ull_rx被調用,它已經在計算機的內存中了。snull_rx因此收到一個指向數據的指針和<BR>包的長度。。這個函數唯一的責任就是將包和一些額外信息發送到網絡代碼的高層。其<BR>代碼與數據指針及長度獲得的方法無關。<BR> <BR>(代碼322)<BR> <BR>這個函數足夠通用,可以作為任何網絡驅動程序模版,但在你有信心重用這個代碼段之<BR>前還需要一些解釋。<BR> <BR>注意緩沖區分配函數需要知道數據長度。者避免了在調用kmalloc時浪費內存。dev_allo<BR>c_skb以原子優先級調用分配函數,因此它也可以在中斷時安全地使用。核心還提供了套<BR>接字緩沖區分配的其它一些接口,但不值得在這里介紹;套接字緩沖區在本章后面的“<BR>套接字緩沖區”中詳細介紹。<BR></P></FONT><FONT
color=#ffffff size=3>
<P>套接字緩沖區”中詳細介紹。<BR> <BR>一旦有了一個有效的skb指針,就可以通過調用memcpy將包數據復制到這個緩沖區。skb_<BR>put更新緩沖區中數據尾的指針,并返回一個指向新生成空間的指針。<BR> <BR>不幸的是,包頭中沒有足夠的信息來正確處理網絡層----在緩沖區向上層傳遞之前,dev<BR>和protocol域必須被賦值。接著我們需要指定如何執行校驗和(snull不進行任何校驗和<BR>)。skb->ip_summed可能的策略為:<BR> <BR>CHECKSUM_HW<BR> <BR>板子用硬件執行校驗和。一個硬件校驗和的離子是Sparc HME接口。<BR> <BR>CHECKSUM_NONE<BR> <BR>校驗和完全有軟件完成。對新分配的緩沖區,這是缺省的策略。<BR> <BR>CHECKSUM_UNNECESSARY<BR> <BR>不做任何校驗和。這是snull和環回接口的策略。<BR> <BR>在1.2核心版本中沒有校驗和選項和ip_summed。<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR>最后,驅動程序更新它的統計計數器記錄一個新包被收到了。統計結構有幾個域組成,<BR>最重要的是rx_packets和tx_packets,它們包含收到的和發送的包的個數。所有的域在<BR>后面的“統計信息”中給出一個徹底的描述。<BR> <BR>包接收的最后一步由netif_rx完成,它將套接字緩沖區遞交到上一層。<BR> <BR> <BR> <BR>中斷驅動的操作<BR> <BR>大多數硬件接口以中斷處理程序的方式控制。接口中斷處理器表明兩種事件中的一種:<BR>一個新包到達了或一個包發送完成了。這種一般化并不是總適用,但它基本上揭示了與<BR>異步包傳送相關的問題。PLIP和PPP是不適用這種一般化的例子。它們處理同樣的事件,<BR>但低級中斷處理略有不同。<BR> <BR>一般的中斷例程可以通過檢查在硬件設備上的一個狀態寄存器來分辨新包到達中斷與完<BR>成發送的通知。snull接口工作方式類似,但其狀態字在dev->priv中。網絡接口的中斷<BR>處理程序看起來如下:<BR> <BR>(代碼324)<BR> <BR>處理程序的第一個任務是接收一個指向正確的device結構的指針。你可以用irq2dev_map<BR></P></FONT><FONT
color=#ffffff size=3>
<P>處理程序的第一個任務是接收一個指向正確的device結構的指針。你可以用irq2dev_map<BR>[](假如你在打開是給它賦了一個值)或者接收到的dev_id指針作為一個參數。如果你<BR>希望驅動程序可以與新于1.3.70的核心工作,你必須使用irq2dev_map[],因為早期版本<BR>中沒有dev_id。<BR> <BR>這個處理程序中有趣的部分是處理“發送完成”的部分。接口通過清除dev->tbusy并標<BR>志網絡下半部例程來相應發送完成。如果net_bh的確運行了,它會試圖發送所有等待的<BR>包。<BR> <BR>另一方面,包接收并不需要任何特殊的中斷處理。所有需要做的就是調用snull_rx。<BR> <BR>實際上,當netif_rx被接收函數調用時,它所進行的實際操作只有標志net_bh。換句話<BR>說,核心在一個下半部處理程序中完成了所有網絡相關的工作。因此,網絡驅動程序應<BR>該總是宣稱它的中斷處理程序太慢,因為下半部將會更早地執行(見第九章中“下半部<BR>設計”)。<BR> <BR> <BR> <BR>套接字緩沖區<BR> <BR>我們已經討論了于網絡接口相關的多數內容。下面幾節我們將更細地討論sk_buff時如何<BR>設計的。這幾節既介紹這個結構的主要域,也介紹在套接字緩沖區上操作的函數。<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR>盡管并沒有理解sk_buff內部的嚴格需要,但是如果能理解它的內容將會有助于你解決問<BR>題和優化代碼。例如,如果你看了loopback.c,你會發現一個基于sk_buff內部知識的優<BR>化。<BR> <BR>我不打算在這里描述整個結構,而只是那些可能被驅動程序用到的域。如果你想知道更<BR>多,你可以看<linux/skbuff.h>,結構的定義和函數的原形都在那里定義。至于這些域<BR>和函數如何使用的細節可以通過瀏覽核心源碼得到。<BR> <BR>重要的域<BR> <BR>出于我們的目的,結構里重要的域是那些驅動程序的作者可能要用到的域。它們如下所<BR>示,無特別順序。<BR> <BR>struct device *dev;<BR> <BR>設備接收或者發送這個緩沖區。<BR> <BR>__u32 saddr;<BR> <BR>__u32 daddr;<BR> <BR>__u32 raddr;<BR></P></FONT><FONT
color=#ffffff size=3>
<P>__u32 raddr;<BR> <BR>源地址,目的地址,和路由器地址,由IP協議使用。raddr是包要到達其目的地的第一步<BR>。這些域在包被發送前被設置,收到之后就不必賦值了。到達hard_start_xmit方法的外<BR>出包已經有了一個合適的硬件包頭設置反映了“第一步”信息。<BR> <BR>unsigned char *head;<BR> <BR>unsigned char *data;<BR> <BR>unsigned char *tail;<BR> <BR>unsigned char *end;<BR> <BR>這些指針用來訪問包中的數據。head指向分配空間的開始,data是有效八元組的開始(<BR>通常比head略大),tail是有效八元組的結束, end指向tail可以到達的最大地址。觀<BR>察它們的另一個方法是:可用緩沖區空間為skb->end-skb->head,當前使用的數據空間<BR>為skb->tail-skb->data。這種處理內存區域的清晰方法在1.3開發時才實現。這是snull<BR>沒有被移植在Linux1.2上編譯的主要原因。<BR> <BR>unsigned long len;<BR> <BR>數據本身的長度(skb->tail-skb->head)。<BR></P></FONT><FONT
color=#ffffff size=3>
<P>數據本身的長度(skb->tail-skb->head)。<BR> <BR>unsigned char ip_summed;<BR> <BR>這個域有驅動程序對進來包設置,由TCP/UDP校驗和使用。它在前面的“包接收”中介紹<BR>過。<BR> <BR>unsigned char pkt_type;<BR> <BR>這個域被內部用來發送進來包。驅動程序負責將其設置為PACKET_HOST(這個包是我的)<BR>, PACKET_BROADCAST,PACKET_MULTICAST,或是PACKET_OTHERHOST(不,這個包不是我<BR>的) 。以太網驅動程序并不顯式地修改pkt_type,因為eth_type_trans會為它做這件事<BR>。<BR> <BR>union { unsigned char *raw; […]} mac;<BR> <BR>與pkt_type類似,這個域被用來處理進來包,必須在包接收時設置。函數eth_type_tran<BR>s為以太網驅動程序負責這件事。非以太網驅動程序應設置skb->mac.raw指針,后面“非<BR>以太網包頭”中將會提到。<BR> <BR> <BR> <BR>結構中其余的域并無特別興趣。它們被用來維護緩沖區列表,解釋占有緩沖區的套接字<BR></P></FONT><FONT
color=#ffffff size=3>
<P>結構中其余的域并無特別興趣。它們被用來維護緩沖區列表,解釋占有緩沖區的套接字<BR>的內存,等等。<BR> <BR>在套接字緩沖區上操作的函數<BR> <BR>使用sock_buff的網絡設備通過正式的接口函數在這個結構上操作。有很多在套接字緩沖<BR>區上操作的函數,下面是最有趣的一些:<BR> <BR>struct sk_buff *alloc_skb(unsigned int len, int priority);<BR> <BR>struct sk_buff *dev_alloc_skb(unsigned int len);<BR> <BR>分配一個緩沖區。alloc_skb分配一個緩沖區并初始化skb->data和skb->tail到skb->hea<BR>d。 dev_alloc_skb函數(在Linux1.2中沒有)一個快捷方式,它用GFP_ATOMIC優先級調<BR>用alloc_skb,并反轉skb->head和skb->data之間的16個字節。這個數據空間可以用來“<BR>推”硬件包頭。<BR> <BR>void kfree_skb(struct sk_buff *skb, int rw);<BR> <BR>void dev_kfree)skb(struct sk_buff *skb, int rw);<BR> <BR>釋放一個緩沖區。kfree_skb被核心內部使用。驅動程序應該使用dev_kfree_skb,在擁<BR>有緩沖區的套接字需要再次使用它的情況下,它可以正確地處理緩沖區加鎖。兩個函數<BR></P></FONT><FONT
color=#ffffff size=3>
<P>有緩沖區的套接字需要再次使用它的情況下,它可以正確地處理緩沖區加鎖。兩個函數<BR>的rw參數是FREE_READ或FREE_WRITE。這個值用來跟蹤套接字的內存。外出緩沖區應用<BR>FREE_WRITE來釋放,而進來的則使用FREE_READ。<BR> <BR>unsigned char *skb_put(struct sk_buff *skb, int len);<BR> <BR>這個線入函數更新結構sk_buff的tail和len域,它被用來在緩沖區尾加入數據。其返回<BR>值是skb->tail以前的值(或者說,它指向剛生成的數據空間)。有些驅動程序通過調用<BR>ins(ioaddr,skb_put(…))或memcpy(skb_put(…), data,len)來使用這個返回值。這個<BR>函數及下面的一些在為Linux1.2構造模塊是不存在。<BR> <BR>unsigned char *skb_push(struct sk_buff *skb, int len);<BR> <BR>這個函數減小skb->data,增加skb->len。類似于skb_put,除了數據是加在包開始而不<BR>是結尾。返回值指向剛生成的空間。<BR> <BR>int skb_tailroom(struct sk_buff *skb);<BR> <BR>這個函數返回為在緩沖區中放置數據的可用空間量。如果驅動程序在緩沖區中放了多于<BR>它能承載的數據,系統可能回崩潰。你也許會反對并認為,用printk指出這個錯誤已經<BR>足夠了,而內存崩潰對系統太有害了,開發者肯定要采取一些措施。但實際上,如果緩<BR>沖區被正確分配了,你根本不必檢查可用空間。因為驅動程序通常在分配緩沖區之前獲<BR>得包大小,只有有嚴重缺陷的驅動程序才可能在緩沖區內放太多的數據,崩潰可以認為<BR></P></FONT><FONT
color=#ffffff size=3>
<P>得包大小,只有有嚴重缺陷的驅動程序才可能在緩沖區內放太多的數據,崩潰可以認為<BR>是應得的懲罰。<BR> <BR>int skb_headroom(struct sk_buff *skb);<BR> <BR>返回數據前面得可用空間量,也就是可以向緩沖區中“推”多少八元組。<BR> <BR>void skb_reserve(struct sk_buff *skb, int len);<BR> <BR>這個函數增加data和tail。它可以用來在填充緩沖區前預留空間。大多數以太網接口在<BR>包前預留兩個字節;這樣IP頭可以在一個4字節以太網頭之后,在16字節邊界對齊。snul<BR>l完成得很好,盡管在“包接收”中并未提到這一點,那主要是為了避免彼時引入過多得<BR>概念。<BR> <BR>unsigned char *skb)pull(struct sk_buff *skb, int len);<BR> <BR>從包頭中刪除數據。驅動程序并不用這個函數,但為了完整性也包含在這里。它減少skb<BR>->len,增加skb->data;這是從進來包的開始剝出以太網包頭的方法。<BR> <BR> <BR> <BR>核心還定義了幾個在套接字緩沖區上操作得別的函數,但它們主要應用于網絡代碼得高<BR>層,驅動程序并不需要它們。<BR></P></FONT><FONT
color=#ffffff size=3>
<P>層,驅動程序并不需要它們。<BR> <BR> <BR> <BR>地址解析<BR> <BR>以太網通信最急迫得問題之一是硬件地址(接口得唯一標志符)與IP號碼之間的關聯。<BR>大多數協議都有類似問題,但我只向重點討論一下以太網類得情況。我力圖給出一個全<BR>面得描述,因此我將顯示三種情況:ARP,沒有ARP的以太網頭(象plip),以及非以太網<BR>包頭。<BR> <BR>在以太網上使用ARP<BR> <BR>地址解析得一般方法是ARP,即地址解析協議。幸運的是,ARP由核心管理,以太網接口<BR>不必為支持ARP做任何特殊工作。只要在打開時正確地設置了dev->addr和dev->addr_len<BR>,驅動程序不需擔心任何從IP號碼到物理地址的轉換;ether_setup將正確的設備方法賦<BR>給dev->hard_header和dev->rebuild_header。<BR> <BR>當一個包被構造時,以太網包頭由dev->hard_header來布局,并由dev->rebuild_header<BR>在后來填充,它使用ARP協議將未知的IP號碼映射到地址上。驅動程序作者不必知道這個<BR>過程的細節去寫一個可工作得驅動程序。<BR> <BR>越過ARP<BR></P></FONT><FONT
color=#ffffff size=3>
<P>越過ARP<BR> <BR>簡單得點到點網絡接口如plip可以從以太網包頭獲益,但卻要避免來回發送ARP包得開銷<BR>。snull中得示例代碼就屬于這一類網絡設備。snull不能使用ARP,因為驅動程序修改被<BR>發送得包得IP地址,而ARP包也交換IP地址。<BR> <BR>如果你的設備想用一般的硬件包頭,卻不想運行ARP,你需要越過缺省的dev->rebuild_h<BR>eader方法。這就是snull實現的方法,這個簡單的函數有三條語句:<BR> <BR>(代碼329)<BR> <BR>事實上,并沒有指定eth->h_source和eth->h_dest內容的實際需要,因為這些值只被用<BR>來進行包得物理傳送,而一個點到點得連接保證能將包發送到它的目的地,而與硬件地<BR>址無關。snull重構包頭的原因是向你演示,當eth_rebuild_header不可用時,一個真實<BR>的網絡接口的重構函數是如何實現的,<BR> <BR>當接口收到一個包時,硬件包頭只被eth_type_trans使用。我們在snull_rx中已經見過<BR>這個調用:<BR> <BR> skb->protocol=eth_type_trans(skb,dev);<BR> <BR>這個函數從以太網包頭中抽取協議標志符(在這里是ETH_P_IP);它還要賦值skb->mac.<BR>raw,從包數據中刪去硬件包頭,并設置skb->pkt_type。最后一項在skb分配時缺省為PA<BR></P></FONT><FONT
color=#ffffff size=3>
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -