Linux網路驅動程式編寫(二)(轉)

worldblog發表於2007-08-10
Linux網路驅動程式編寫(二)(轉)[@more@]

  二.Linux系統網路裝置驅動程式

  2.1 網路驅動程式的結構

  所有的Linux網路驅動程式遵循通用的介面。設計時採用的是物件導向的方法。一個裝置就是一個物件(device 結構),它內部有自己的資料和方法。每一個裝置的方法被呼叫時的第一個引數都是這個裝置物件本身。這樣這個方法就可以存取自身的資料(類似物件導向程式設計時的this引用)。一個網路裝置最基本的方法有初始化、傳送和接收。

   -------------------      ---------------------

  |deliver packets  |     |receive packets queue|

  |(dev_queue_xmit()) |     |them(netif_rx())   |

   -------------------      ---------------------

    |     |           /     

         /           |     |

  -------------------------------------------------------

  | methods and variables(initialize,open,close,hard_xmit,|

  | interrupt handler,config,resources,status...)     |

  -------------------------------------------------------

     |    |           /     

         /           |     |

   -----------------       ----------------------

   |send to hardware |      |receivce from hardware|

   -----------------       ----------------------

     |    |           /     

         /           |     |

   -----------------------------------------------------

  |         hardware media           |

   -----------------------------------------------------

  初始化程式完成硬體的初始化、device中變數的初始化和系統資源的申請、傳送程式是在驅動程式的上層協議層有資料要傳送時自動呼叫的。一般驅動程式中不對傳送資料進行快取,而是直接使用硬體的傳送功能把資料傳送出去。接收資料一般是透過硬體中斷來通知的。在中斷處理程式裡,把硬體幀資訊填入一個skbuff結構中,然後呼叫netif_rx()傳遞給上層處理。

  2.2 網路驅動程式的基本方法

  網路裝置做為一個物件,提供一些方法供系統訪問。正是這些有統一介面的方法,掩蔽了硬體的具體細節,讓系統對各種網路裝置的訪問都採用統一的形式,做到硬體無關性。

  下面解釋最基本的方法。

  2.2.1 初始化(initialize)

  驅動程式必須有一個初始化方法。在把驅動程式載入系統的時候會呼叫這個初始化程式。它做以下幾方面的工作。檢測裝置。在初始化程式裡你可以根據硬體的特徵檢查硬體是否存在,然後決定是否啟動這個驅動程式。配置和初始化硬體。在初始化程式裡你可以完成對硬體資源的配置,比如即插即用的硬體就可以在這個時候進行配置(Linux核心對PnP功能沒有很好的支援,可以在驅動程式裡完成這個功能)。配置或協商好硬體佔用的資源以後,就可以向系統申請這些資源。有些資源是能)。配置或協商好硬體佔用的資源以後,就可以向系統申請這些資源。有些資源是可以和別的裝置共享的,如中斷。有些是不能共享的,如IO、DMA。接下來你要初始化device結構中的變數。最後,你可以讓硬體正式開始工作。

  2.2.2 開啟(open)

  open這個方法在網路裝置驅動程式裡是網路裝置被啟用的時候被呼叫(即裝置狀態由down--&gtup)。所以實際上很多在initialize中的工作可以放到這裡來做。比如資源的申請,硬體的啟用。如果dev->open返回非0(error),則硬體的狀態還是down。

  open方法另一個作用是如果驅動程式做為一個模組被裝入,則要防止模組解除安裝時裝置處於開啟狀態。在open方法裡要呼叫MOD_INC_USE_COUNT宏。

  2.2.3 關閉(stop)

  close方法做和open相反的工作。可以釋放某些資源以減少系統負擔。close是在裝置狀態由up轉為down時被呼叫的。另外如果是做為模組裝入的驅動程式,close裡應該呼叫MOD_DEC_USE_COUNT,減少裝置被引用的次數,以使驅動程式可以被解除安裝。另外close方法必須返回成功(0==success)。

  2.2.4 傳送(hard_start_xmit)

  所有的網路裝置驅動程式都必須有這個傳送方法。在系統呼叫驅動程式的xmit時,傳送的資料放在一個sk_buff結構中。一般的驅動程式把資料傳給硬體發出去。也有一些特殊的裝置比如loopback把資料組成一個接收資料再回送給系統,或者dummy裝置直接丟棄資料。如果傳送成功,hard_start_xmit方法裡釋放sk_buff,返回0(傳送成功)。如果傳送成功,hard_start_xmit方法裡釋放sk_buff,返回0(傳送成功)。如果裝置暫時無法處理,比如硬體忙,則返回1。這時如果dev->tbusy置為非0,則系統認為硬體忙,要等到dev->tbusy置0以後才會再次傳送。tbusy的置0任務一般由中斷完成。硬體在傳送結束後產生中斷,這時可以把tbusy置0,然後用mark_bh()呼叫通知系統可以再次傳送。在傳送不成功的情況下,也可以不置dev->tbusy為非0,這樣系統會不斷嘗試重發。如果hard_start_xmit傳送不成功,則不要釋放sk_buff。

  傳送下來的sk_buff中的資料已經包含硬體需要的幀頭。所以在傳送方法裡不需要再填充硬體幀頭,資料可以直接提交給硬體傳送。sk_buff是被鎖住的(locked),確保其他程式不會存取它。

  2.2.5 接收(reception)

  驅動程式並不存在一個接收方法。有資料收到應該是驅動程式來通知系統的。一般裝置收到資料後都會產生一箇中斷,在中斷處理程式中驅動程式申請一塊sk_buff(skb),從硬體讀出資料放置到申請好的緩衝區裡。接下來填充sk_buff中的一些資訊。skb->dev = dev,判斷收到幀的協議型別,填入skb->protocol(多協議的支援)。把指標skb->mac.raw指向硬體資料然後丟棄硬體幀頭(skb_pull)。還要設定skb->pkt_type,標明第二層(鏈路層)資料型別。可以是以下型別: 

  PACKET_BROADCAST : 鏈路層廣播

  PACKET_MULTICAST : 鏈路層組播

  PACKET_SELF   : 發給自己的幀

  PACKET_OTHERHOST : 發給別人的幀(監聽模式時會有這種幀)

  最後呼叫netif_rx()把資料傳送給協議層。netif_rx()裡資料放入處理佇列然後返最後呼叫netif_rx()把資料傳送給協議層。netif_rx()裡資料放入處理佇列然後返回,真正的處理是在中斷返回以後,這樣可以減少中斷時間。呼叫netif_rx()以後,驅動程式就不能再存取資料緩衝區skb。

  2.2.6 硬體幀頭(hard_header)

  硬體一般都會在上層資料傳送之前加上自己的硬體幀頭,比如乙太網(Ethernet)就有14位元組的幀頭。這個幀頭是加在上層ip、ipx等資料包的前面的。驅動程式提供一個hard_header方法,協議層(ip、ipx、arp等)在傳送資料之前會呼叫這段程式。硬體幀頭的長度必須填在dev->hard_header_len,這樣協議層回在資料之前保留好硬體幀頭的空間。這樣hard_header程式只要呼叫skb_push然後正確填入硬體幀頭就可以了。

  在協議層呼叫hard_header時,傳送的引數包括(2.0.xx):資料的sk_buff,

  device指標,protocol,目的地址(daddr),源地址(saddr),資料長度(len)。資料長度不要使用sk_buff中的引數,因為呼叫hard_header時資料可能還沒完全組織好。saddr是NULL的話是使用預設地址(default)。daddr是NULL表明協議層不知道硬體目的地址。如果hard_header完全填好了硬體幀頭,則返回新增的位元組數。如果硬體幀頭中的資訊還不完全(比如daddr為NULL,但是幀頭中需要目的硬體地址。典型的情況是乙太網需要地址解析(arp)),則返回負位元組數。hard_header返回負數的情況下,協議層會做進一步的build header的工作。目前Linux系統裡就是做arp(如果hard_header返回正,dev->arp=1,表明不需要做arp,返回負,dev->arp=0,做arp)。對hard_header的呼叫在每個協議層的處理程式裡。如ip_output。

  2.2.7 地址解析(xarp)

  有些網路有硬體地址(比如Ethernet),並且在傳送硬體幀時需要知道目的硬體地址。這樣就需要上層協議地址(ip、ipx)和硬體地址的對應。這個對應是透過地址解析完成的。需要做arp的的裝置在傳送之前會呼叫驅動程式的rebuild_header方法。呼叫的主要引數包括指向硬體幀頭的指標,協議層地址。如果驅動程式能夠解析硬體地址,就返回1,如果不能,返回0。

  對rebuild_header的呼叫在net/core/dev.c的do_dev_queue_xmit()裡。

  2.2.8 引數設定和統計資料

  在驅動程式裡還提供一些方法供系統對裝置的引數進行設定和讀取資訊。一般只有超級使用者(root)許可權才能對裝置引數進行設定。設定方法有:

  dev->set_mac_address()

  當使用者呼叫ioctl型別為SIOCSIFHWADDR時是要設定這個裝置的mac地址。一般對mac地址的設定沒有太大意義的。

  dev->set_config()

  當使用者呼叫ioctl時型別為SIOCSIFMAP時,系統會呼叫驅動程式的set_config方法。使用者會傳遞一個ifmap結構包含需要的I/O、中斷等引數。

  dev->do_ioctl()

  dev->do_ioctl()

  如果使用者呼叫ioctl時型別在SIOCDEVPRIVATE和SIOCDEVPRIVATE+15之間,系統會呼叫驅動程式的這個方法。一般是設定裝置的專用資料。

  讀取資訊也是透過ioctl呼叫進行。除次之外驅動程式還可以提供一個

  dev->get_stats方法,返回一個enet_statistics結構,包含傳送接收的統計資訊。

  ioctl的處理在net/core/dev.c的dev_ioctl()和dev_ifsioc()裡。

  2.3 網路驅動程式中用到的資料結構

  最重要的是網路裝置的資料結構。定義在include/linux/netdevice.h裡。它的註釋已經足夠詳盡。

  struct device

{

 /*

  * This is the first field of the "visible" part of this structure

  * (i.e. as seen by users in the "Space.c" file). It is the name

  * the interface.

  */

 char          *name;

 /* I/O specific fields - FIXME: Merge these and struct ifmap into one */

 unsigned long      rmem_end;       /* shmem "recv" end   */

 unsigned long      rmem_end;       /* shmem "recv" end   */

 unsigned long      rmem_start;      /* shmem "recv" start  */

 unsigned long      mem_end;       /* shared mem end    */

 unsigned long      mem_start;      /* shared mem start   */

 unsigned long      base_addr;      /* device I/O address  */

 unsigned char      irq;         /* device IRQ number  */

 /* Low-level status flags. */

 volatile unsigned char start,        /* start an operation  */

             interrupt;      /* interrupt arrived  */

 /* 在處理中斷時interrupt設為1,處理完清0。 */

 unsigned long      tbusy;        /* transmitter busy must be long

 for

 struct device      *next;

 /* The device initialization function. Called only once. */

 /* 指向驅動程式的初始化方法。 */

 int           (*init)(struct device *dev);

 /* Some hardware also needs these fields, but they are not part of the

   usual set specified in Space.c. */

 /* 一些硬體可以在一塊板上支援多個介面,可能用到if_port。 */

 /* 一些硬體可以在一塊板上支援多個介面,可能用到if_port。 */

 unsigned char      if_port;       /* Selectable AUI, TP,..*/

 unsigned char      dma;         /* DMA channel     */

 struct enet_statistics* (*get_stats)(struct device *dev);

 /*

  * This marks the end of the "visible" part of the structure. All

  * fields hereafter are internal to the system, and may change at

  * will (read: may be cleaned up at will).

  */

 /* These may be needed for future network-power-down code. */

 /* trans_start記錄最後一次成功傳送的時間。可以用來確定硬體是否工作正常。*/

 unsigned long      trans_start; /* Time (in jiffies) of last Tx */

 unsigned long      last_rx;   /* Time of last Rx       */

 /* flags裡面有很多內容,定義在include/linux/if.h裡。*/

 unsigned short     flags;    /* interface flags (a la BSD)  */

 unsigned short     family;    /* address family ID (AF_INET) */

 unsigned short     metric;    /* routing metric (not used)  */

 unsigned short     mtu;     /* interface MTU value     */

 /* type標明物理硬體的型別。主要說明硬體是否需要arp。定義在

   include/linux/if_arp.h裡。 */

 unsigned short     type;     /* interface hardware type   */

 /* 上層協議層根據hard_header_len在傳送資料緩衝區前面預留硬體幀頭空間。*/

 unsigned short     hard_header_len;   /* hardware hdr length */

 /* priv指向驅動程式自己定義的一些引數。*/

 void          *priv;    /* pointer to private data   */

 /* Interface address info. */

 unsigned char      broadcast[MAX_ADDR_LEN];   /* hw bcast add */

 unsigned char      pad;             /* make dev_addr aligned

 to 8

bytes */

 unsigned char      dev_addr[MAX_ADDR_LEN];    /* hw address  */

 unsigned char      addr_len;   /* hardware address length   */

 unsigned long      pa_addr;   /* protocol address       */

 unsigned long      pa_brdaddr;  /* protocol broadcast addr   */

 unsigned long      pa_dstaddr;  /* protocol P-P other side addr */

 unsigned long      pa_mask;   /* protocol netmask       */

 struct dev_mc_list   *mc_list;   /* Multicast mac addresses   */

 int          mc_count;   /* Number of installed mcasts  */

 struct ip_mc_list   *ip_mc_list;  /* IP multicast filter chain  */

 __u32         tx_queue_len;  /* Max frames per queue allowed */

 /* For load balancing driver pair support */

 unsigned long      pkt_queue;  /* Packets queued */

 struct device      *slave;    /* Slave device */

 struct net_alias_info     *alias_info;  /* main dev alias info */

 struct net_alias       *my_alias;   /* alias devs */

 /* Pointer to the interface buffers. */

 struct sk_buff_head   buffs[DEV_NUMBUFFS];

 /* Pointers to interface service routines. */

 int           (*open)(struct device *dev);

 int           (*hard_start_xmit) (struct sk_buff *skb,

                       struct device *dev);

 int           (*hard_header) (struct sk_buff *skb,

                     struct device *dev,

                     unsigned short type,

                     void *daddr,

                     void *saddr,

                     unsigned len);

 int           (*rebuild_header)(void *eth, struct device *dev,

                unsigned long raddr, struct sk_buff *skb);

#define HAVE_MULTICAST

 void          (*set_multicast_list)(struct device *dev);

#define HAVE_SET_MAC_ADDR

 int           (*set_mac_address)(struct device *dev, void *addr);

#define HAVE_PRIVATE_IOCTL

 int           (*do_ioctl)(struct device *dev, struct ifreq *ifr, int

 cmd);

#define HAVE_SET_CONFIG

 int           (*set_config)(struct device *dev, struct ifmap *map);

#define HAVE_HEADER_CACHE

 void          (*header_cache_bind)(struct hh_cache **hhp, struct dev

ice

*dev, unsigned short htype, __u32 daddr);

*dev, unsigned short htype, __u32 daddr);

 void          (*header_cache_update)(struct hh_cache *hh, struct dev

ice

*dev, unsigned char * haddr);

#define HAVE_CHANGE_MTU

 struct iw_statistics*  (*get_wireless_stats)(struct device *dev);

};

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-940215/,如需轉載,請註明出處,否則將追究法律責任。

相關文章