Linux網絡設備驅動結構概述

網絡設備驅動相比字符型設備的驅動要複雜一些,除了整體上驅動的框架有一些類似外,有不少地方都是不一樣,但網絡設備驅動有一個很大的特色就是有固定的框架能夠遵循,具體的框架會在後邊詳細的敘述。
1.網絡協議接口層
在網絡協議接口層,只提供了兩個抽象函數dev_queue_xmit()與 netif_rx(),之因此稱之爲抽象函數,是由於這兩個函數抽象了不少底層的操做,無論是那個芯片它在網絡協議結構的操做函數都是這兩個函數,採用這樣的抽象後,給上層帶來了不少的方便,給上層協議提供統一的數據包收發接口,不管上層是ARP協議仍是IP協議,都經過dev_queue_xmit() 函數發送數據,經過netif_rx()函數接收數據。此層使上層協議獨立於具體的設備。
相關數據結構sk_buff:
  sk_buff 稱爲「套接字緩衝區」,用於在Linux網絡子系統中各層之間傳遞數據。是Linux網絡子系統數據傳遞的「中樞神經」。sk_buff定義位置爲:include/linux/skbuff.h,這個數據結構定義了不少用於網絡操做的函數,更多的設計整個協議的實現,包括各層報文的頭信息,以及報文的幀格式,下邊這個這個結構體是有關報文header信息的,下邊的代碼摘自kernel2.6.29/include/linux /skbuff.h
struct sk_buff {
    /* These two members must be first. */
    struct sk_buff        *next;
    struct sk_buff        *prev;
    struct sock        *sk;
    struct skb_timeval    tstamp;
    struct net_device    *dev;
    struct net_device    *input_dev;
    union {
        struct tcphdr    *th;
        struct udphdr    *uh;
        struct icmphdr    *icmph;
        struct igmphdr    *igmph;
        struct iphdr    *ipiph;
        struct ipv6hdr    *ipv6h;
        unsigned char    *raw;
    } h;
    union {
        struct iphdr    *iph;
        struct ipv6hdr    *ipv6h;
        struct arphdr    *arph;
        unsigned char    *raw;
    } nh;
    union {
          unsigned char     *raw;
    } mac;

    struct  dst_entry    *dst;
    struct    sec_path    *sp;
    /*
     * This is the control buffer. It is free to use for every
     * layer. Please put your private variables there. If you
     * want to keep them across layers you have to do a skb_clone()
     * first. This is owned by whoever has the skb queued ATM.
     */
    char            cb[48];

    unsigned int        len,
                data_len,
                mac_len;
    union {
        __wsum        csum;
        __u32        csum_offset;
    };
    __u32            priority;
    __u8            local_df:1,
                cloned:1,
                ip_summed:2,
                nohdr:1,
                nfctinfo:3;
    __u8            pkt_type:3,
                fclone:2,
                ipvs_property:1;
    __be16            protocol;

    void            (*destructor)(struct sk_buff *skb);
#ifdef CONFIG_NETFILTER
    struct nf_conntrack    *nfct;
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
    struct sk_buff        *nfct_reasm;
#endif
#ifdef CONFIG_BRIDGE_NETFILTER
    struct nf_bridge_info    *nf_bridge;
#endif
#endif /* CONFIG_NETFILTER */
#ifdef CONFIG_NET_SCHED
    __u16            tc_index;    /* traffic control index */
#ifdef CONFIG_NET_CLS_ACT
    __u16            tc_verd;    /* traffic control verdict */
#endif
#endif
#ifdef CONFIG_NET_DMA
    dma_cookie_t        dma_cookie;
#endif
#ifdef CONFIG_NETWORK_SECMARK
    __u32            secmark;
#endif

    __u32            mark;

    /* These elements must be at the end, see alloc_skb() for details.  */
    unsigned int        truesize;
    atomic_t        users;
    unsigned char        *head,
                *data,
                *tail,
                *end;
};
  sk_buff中的數據緩衝區的指針
  Linux必須分配用於容納數據包的緩衝區,sk_buff中定義了4個指向這片緩衝區的不一樣位置的指針head、data、tail、end。
  head:指針指向內存中已分配的用於存儲網路數據的緩衝區起始地址,sk_buff和相關數據塊在分配後,該指針的值就固定了。
  data:指針指向對應當前協議層有效數據的起始地址。每一個協議的有效數據含義不一樣。
  tail:指向對應當前協議層有效數據負載的結尾地址,與data對應。
  end:指向內存分配的數據緩衝區的結尾地址,與head指針對應。和head同樣,sk_buff和相關數據塊被分配後,end指針也就固定了。
  套接字緩衝區sk_buff相關操做:
  分配空間:
  struct sk_buff *dev_alloc_skb(unsigned len)
  釋放空間:
  dev_kfree_skb(struct sk_buff *skb)
  dev_kfree_skb_irq(struct sk_buff *skb)
  dev_kfree_skb_any(struct sk_buff *skb)
  put操做:
  unsigned char *skb_put(struct sk_buff *skb, unsigned int len);
  unsigned char *__skb_put(struct sk_buff *skb, unsigned int len);

  做用:tail指針下移len長度,並增長sk_buff中len的值,返回改變後的tail值。主要用於在尾部追加數據。
  push操做:
  unsigned char *skb_push(struct sk_buff *skb, unsigned int len);
  unsigned char *__skb_push(struct sk_buff *skb, unsigned int len);
  做用:將data指針上移,同時增長sk_buff中的len。主要用於在數據包發送時添加頭部。函數帶__和不帶__的區別在於:帶__的會檢測放入緩衝區的數據,後則不會。
  pull操做:
  unsigned char *skb_pull(struct sk_buff *skb, unsigned int len);
  做用:將data指針下移,並減小sk_buff中的len值。這個操做通常用於下層協議向上層協議移交數據包,使data指針指向上一層協議的協議頭
  reserve操做:
  void skb_reserve(struct sk_buff *skb, unsigned int len);
  做用:將data和tail指針同時下移,這個操做主要用於在存儲空間的頭部預留len長度的空隙。
  2.網絡設備接口層
  咱們知道,一般的驅動編寫就是填充一個設備相關的結構體,網絡設備也是這樣,在2.6.29的內核中,須要填寫結構體以下:
  kernel2.6.29/include/linux/netdevice.h

  struct net_device_ops {

  int            (*ndo_init)(struct net_device *dev);

  void            (*ndo_uninit)(struct net_device *dev);

  int            (*ndo_open)(struct net_device *dev);

  int            (*ndo_stop)(struct net_device *dev);

  int            (*ndo_start_xmit) (struct sk_buff *skb,

  struct net_device *dev);

  u16            (*ndo_select_queue)(struct net_device *dev,

  struct sk_buff *skb);

  #define HAVE_CHANGE_RX_FLAGS

  void            (*ndo_change_rx_flags)(struct net_device *dev,

  int flags);

  #define HAVE_SET_RX_MODE

  void            (*ndo_set_rx_mode)(struct net_device *dev);

  #define HAVE_MULTICAST

  void            (*ndo_set_multicast_list)(struct net_device *dev);

  #define HAVE_SET_MAC_ADDR

  int            (*ndo_set_mac_address)(struct net_device *dev,

  void *addr);

  #define HAVE_VALIDATE_ADDR

  int            (*ndo_validate_addr)(struct net_device *dev);

  #define HAVE_PRIVATE_IOCTL

  int            (*ndo_do_ioctl)(struct net_device *dev,

  struct ifreq *ifr, int cmd);

  #define HAVE_SET_CONFIG

  int            (*ndo_set_config)(struct net_device *dev,

  struct ifmap *map);

  #define HAVE_CHANGE_MTU

  int            (*ndo_change_mtu)(struct net_device *dev,

  int new_mtu);

  int            (*ndo_neigh_setup)(struct net_device *dev,

  struct neigh_parms *);

  #define HAVE_TX_TIMEOUT

  void            (*ndo_tx_timeout) (struct net_device *dev);

  struct net_device_stats* (*ndo_get_stats)(struct net_device *dev);

  void   (*ndo_vlan_rx_register)(struct net_device *dev, struct vlan_group *grp);

  void            (*ndo_vlan_rx_add_vid)(struct net_device *dev,

  unsigned short vid);

  void            (*ndo_vlan_rx_kill_vid)(struct net_device *dev,

  unsigned short vid);

  #ifdef CONFIG_NET_POLL_CONTROLLER

  #define HAVE_NETDEV_POLL

  void (*ndo_poll_controller)(struct net_device *dev);

  #endif

  };
  在咱們實現這些函數中的一部分後,就實現了驅動的功能了,爲變幻無窮的網絡設備定義統一的、抽象的數據結構net_device結構體,以不變應萬變,實現多種硬件在軟件層次上的統一。net_device結構體在內核中指代一個網絡設備,網絡設備驅動只需填充其結構體就能夠實現內核與具體硬件操做函數的掛接。實際驅動的編寫過程當中,咱們並不須要實現所有的函數,實際上,咱們只要根據具體的須要實現上邊的部分就能夠了。
net_device結構體的相關成員
  網絡設備的名稱:
  char name[IFNAMESIZ]
  設備初始化指針:
  int (*init)(struct net_device *dev);
  硬件信息:
  unsigned long mem_end;
  unsigned long mem_start;
  兩者分別定義了設備所用的共享內存的起始和結束地址
  unsigned long base_addr;網絡設備的I/O基地址
  unsigned char irq;設備使用的中斷號
  unsigned char if_port;多端口設備中端口選擇

  unsigned char dma;分配給設備的DMA通道
  接口信息
  unsigned short hard_header_len;網絡設備的硬件頭長度,在以太網設備的初始化函數中,該成員被賦值爲ETH_HLEN,即14
  unsigned short type;接口的硬件類型
  unsigned mtu;最大傳輸單元
  unsigned char dev_addr[MAX_ADDR_LEN];
  unsigned char broadcase[MAX_ADDR_LEN];
  兩者分別用於存放設備的硬件地址和廣播地址,以太網設備的廣播地址爲6個0xFF
  unsigned short flags;網絡接口標誌
  設備操做函數
  打開、關閉
  int (*open)(struct net_device *dev);
  int (*stop)(struct net_device *dev);
  啓動數據包發送
  int (*hard_start_xmit)(struct sk_buff *skb, struct net_device *dev);
  得到網絡設備狀態
  struct net_device_status * (*get_status)(struct net_device *dev);
  設備I/O控制
  int (*do_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd);

  配置接口,I/O地址,中斷號
  int (*set_config)(struct net_device *dev, struct ifmap *map);
  設置MAC地址
  int (*set_mac_address)(struct net_device *dev, void *addr);
  輔助成員
  unsigned long trans_start;
  unsigned long last_rx;
  兩者分別爲:最後一次開始發送的時間戳、最後一次接收到數據包的時間戳,這兩個時間戳記錄都是jiffies,驅動程序應維護這兩個成員。
  void *priv;私有信息指針
  spinlock_t xmit_lock;
  int xmit_lock_owner;
  xmit_lock是避免hard_start_xmit函數同時屢次調用的自旋鎖。xmit_lock_owner指向擁有此自旋鎖的CPU的編號。
本篇文章來源於 www.LinuxBoss.Cn 原文連接:http://www.linuxboss.cn/Net/2010/0611/20777.html





Linux網絡設備驅動指南(1)
    
做者:王成國 時間:2007-10-08 14:31 出處:51cto 責編:月夜寒簫
    
              摘要:Linux網絡設備驅動指南(1)
    

                                    
    
前言介紹,本文所涉及概念:
1)snull:是一種虛擬的網絡模型,經過它能夠基本瞭解真實物理網絡接口設備驅動程序的運做。
2)本文代碼都是標準C語言格式。
3)*skb:重要的指針量,指向一塊內存區域用以緩衝待處理的進出網絡數據包。
Linux網絡接口設備概述
Linux中網絡接口是3大標準類設備之一,其餘2種類型分別是字符設備和塊設備。下面介紹網絡接口設備的驅動程序如何和內核模塊進行交互的知識。網絡設備驅動程序異步地接收來自外部世界的網絡數據包,經過push操做將進來的數據包壓向內核,而塊設備驅動程序則將一片數據緩衝發送給內核,驅動程序都須要將相應設備的特徵信息登記到內核中特定的數據結構中,塊設備驅動程序可使用文件形式來描述,而網絡驅動程序則不可使用通常的文件讀寫操做調用,網絡設備驅動程序有本身的內存名字空間,使用push等操做來完成數據包的轉換與遞送。
Unix世界裏的"一切皆是文件"的論述,對網絡設備接口來講是不適用的。塊設備在系統文件樹的/dev目錄下能夠找到特定的文件入口標誌,而網絡設備則沒有這種文件操做入口,一個物理網絡接口上的數百個用於網絡數據交換的插口能夠被應用程序重複使用。

圖1

如上圖,內核中有專門爲網絡設備驅動程序設計的數據包操做接口。經過網絡接口sn0到達網絡snullnet0就是路由的意思.使用route add -net snullnet0 dev sn0指令來完成,2.2以上的Linux內核無需這樣作了,內核自動將添加。若是網絡非C類地址,請用掩碼指定255.255.255.0指定的是C類網絡,由於在snull實驗網絡模型中地址機制被修改,因此發向192.168.0.33的數據包將經過sn0接口送達snullnet1中得192.168.1.33機器,送到其它網絡地址的數據包將被sn1接口丟棄.說到物理數據傳輸,snull網絡模型機制隸屬於以太網Ethernet範疇.
因爲以太網已經被普遍使用的緣由,甚至連打印協議plip接口也聲稱本身隸屬於以太網設備範疇.你還能夠用tcpdump工具來察看snull實驗網模型中的網絡數據走向。使用tcpdump工具在2.0內核中加載snull設備驅動時須要顯示指定Eth=1項。注意snull網絡模型只能用來試驗ip網絡協議的數據包。利用snull網絡模型傳送非IP網絡數據包必須對snull模塊的源代碼進行修改,不然會破壞其它非ip網絡數據包。
Snull模型核心操做
snull核心操做大概有11多項,它們是:
 ether_setup,
            open,
            stop,
            set_config,
            hard_start_xmit,
            do_ioctl,get_stats,
            rebuild_header,
            tx_timeout,
            watchdog_timeo,
            flags,
            hard_header_cache,
            SET_MODULE_OWNER等.
注意:snull網絡不處理arp數據包,由於核心操做中的IFF_NOARP標誌位.ARP是一個底層的以太網協議標準。任務是將IP地址對應到以太網物理媒質訪問控制地址,也就是MAC地址。由於snull網絡模擬出來的網絡機器無需處理MAC地址,故不處理了。一樣道理hard_header_cache也無需處理了,snull模型中將它設成了NULL。
如何操做網絡設備
如下要介紹如何對網絡設備展開具體操做。網絡接口能夠操做數據包以前必須由內核打開接口並賦予內存地址,使用ifconfig命令就能夠作到,首先經過ioctl分配地址,再將IFF_UP標誌位打開,這時候驅動程序的功能尚未用到,只是內核在執行而已。插槽輸入輸出接口標誌位打開就調用了open方法打開了設備接口。關閉接口方法同理。若是執行成功將返回0,不然返回一個負數。
驅動程序的實際代碼完成不少相似於char或block類型的操做,open調用並分配系統資源,stop中止並釋放系統資源。接口和外部世界通信以前須要將物理設備上的地址複製到dev->dev_addr變量中,snull模型驅動程序中使用ASCII字符串ETH_ALEN來杜撰一個物理網卡地址。
open方法也開啓了一個傳輸隊列,內核提供了一個函數來實現一個隊列:
void netif_start_queue (struct net_device *dev);
另外snull模型的代碼相似如下:
int snull_open(struct net_device *dev)
            {
            MOD_INC_USE_COUNT;
            memcpy(dev->dev_addr, "\0SNUL0", ETH_ALEN);
            dev->dev_addr[ETH_ALEN-1] += (dev - snull_devs);
            netif_start_queue(dev); return 0;
            }
            
由於snull模型中並無真實的硬件網絡設備因此open方法中沒有太多代碼,stop方法同理:
int snull_release(struct net_device *dev)
            {
            /*釋放端口和irq,相似於fops->close */
            netif_stop_queue(dev);
            /*沒法再傳輸數據了*/
            MOD_DEC_USE_COUNT;
            return 0;
            }
開啓和關閉接口的方法是一對矛盾。
網絡接口的最重要任務:發送和接受網絡數據包
接着講網絡接口要完成的最重要的任務:發送和接受網絡數據包。發送包容易理解因此先講,在高級網絡層中每一個包都屬於一個特定編號的網絡插槽(socket)進出的包使用sk_buff結構變量來列表表示,插槽緩衝(sk_buff)結構變量貫穿於整個linux網絡之系統中。具體定義能夠在linux/skbuff.h文件中找到。
指向sk_buff變量的指針一般被稱做skb,插槽緩衝是一個複雜的結構,內核負責提供必定數量的動做函數來操做該結構,開始經過調用hard_start_transmit方法函數把須要發送的包弄進出站隊列,hard_start_xmit中含有包在物理媒介上的地址,skb->data指向正在傳輸的包,skb->len是用八進制表示的包長度。snull模型中包的傳輸代碼和實際物理網卡的驅動代碼是隔離的,snull的包傳輸代碼以下:
int snull_tx(struct sk_buff *skb, struct net_device *dev)
            {
            int len;
            char *data;
            struct snull_priv *priv = (struct snull_priv *) dev->priv;
            len = skb->len < ETH_ZLEN ? ETH_ZLEN : skb->len; data = skb->data;
            dev->trans_start = jiffies;
            /*保存時戳*/
            priv->skb = skb;
            /*記住緩衝指針以便隨時中斷釋放,
            實際傳送數據與設備相關,這裏省略了*/ snull_hw_tx(data, len, dev);
            return 0;
            }
            
那麼如何併發地控制傳輸呢?對於緩衝暫時滿載時就要經過netif_stop_queue來暫停,並使用void netif_wake_queue(struct net_device *dev)來繼續從新啓動,設備被分的內存中可同時有多個包緩衝。
一種機制控制設定的動做沒有在規定時間內完成則被視爲超過期間,有問題發生,相關的監測函數是watchdog_timeo,該函數在net_device結構中,jiffies是時間控制表。而後調用tx_timeout方法來處理。超時一旦發生,驅動程序代碼必須作出錯誤標記,snull模型中錯誤一旦發生將調用snull_interrupt,netif_wake_queue等函數。                                    
    
Linux網絡設備驅動指南(1)
    
做者:王成國 時間:2007-10-08 14:31 出處:51cto 責編:月夜寒簫
    
              摘要:Linux網絡設備驅動指南(1)
    

                                    
    
前言介紹,本文所涉及概念:
1)snull:是一種虛擬的網絡模型,經過它能夠基本瞭解真實物理網絡接口設備驅動程序的運做。
2)本文代碼都是標準C語言格式。
3)*skb:重要的指針量,指向一塊內存區域用以緩衝待處理的進出網絡數據包。
Linux網絡接口設備概述
Linux中網絡接口是3大標準類設備之一,其餘2種類型分別是字符設備和塊設備。下面介紹網絡接口設備的驅動程序如何和內核模塊進行交互的知識。網絡設備驅動程序異步地接收來自外部世界的網絡數據包,經過push操做將進來的數據包壓向內核,而塊設備驅動程序則將一片數據緩衝發送給內核,驅動程序都須要將相應設備的特徵信息登記到內核中特定的數據結構中,塊設備驅動程序可使用文件形式來描述,而網絡驅動程序則不可使用通常的文件讀寫操做調用,網絡設備驅動程序有本身的內存名字空間,使用push等操做來完成數據包的轉換與遞送。
Unix世界裏的"一切皆是文件"的論述,對網絡設備接口來講是不適用的。塊設備在系統文件樹的/dev目錄下能夠找到特定的文件入口標誌,而網絡設備則沒有這種文件操做入口,一個物理網絡接口上的數百個用於網絡數據交換的插口能夠被應用程序重複使用。

圖1

如上圖,內核中有專門爲網絡設備驅動程序設計的數據包操做接口。經過網絡接口sn0到達網絡snullnet0就是路由的意思.使用route add -net snullnet0 dev sn0指令來完成,2.2以上的Linux內核無需這樣作了,內核自動將添加。若是網絡非C類地址,請用掩碼指定255.255.255.0指定的是C類網絡,由於在snull實驗網絡模型中地址機制被修改,因此發向192.168.0.33的數據包將經過sn0接口送達snullnet1中得192.168.1.33機器,送到其它網絡地址的數據包將被sn1接口丟棄.說到物理數據傳輸,snull網絡模型機制隸屬於以太網Ethernet範疇.
因爲以太網已經被普遍使用的緣由,甚至連打印協議plip接口也聲稱本身隸屬於以太網設備範疇.你還能夠用tcpdump工具來察看snull實驗網模型中的網絡數據走向。使用tcpdump工具在2.0內核中加載snull設備驅動時須要顯示指定Eth=1項。注意snull網絡模型只能用來試驗ip網絡協議的數據包。利用snull網絡模型傳送非IP網絡數據包必須對snull模塊的源代碼進行修改,不然會破壞其它非ip網絡數據包。
Snull模型核心操做
snull核心操做大概有11多項,它們是:
 ether_setup,
            open,
            stop,
            set_config,
            hard_start_xmit,
            do_ioctl,get_stats,
            rebuild_header,
            tx_timeout,
            watchdog_timeo,
            flags,
            hard_header_cache,
            SET_MODULE_OWNER等.
注意:snull網絡不處理arp數據包,由於核心操做中的IFF_NOARP標誌位.ARP是一個底層的以太網協議標準。任務是將IP地址對應到以太網物理媒質訪問控制地址,也就是MAC地址。由於snull網絡模擬出來的網絡機器無需處理MAC地址,故不處理了。一樣道理hard_header_cache也無需處理了,snull模型中將它設成了NULL。
如何操做網絡設備
如下要介紹如何對網絡設備展開具體操做。網絡接口能夠操做數據包以前必須由內核打開接口並賦予內存地址,使用ifconfig命令就能夠作到,首先經過ioctl分配地址,再將IFF_UP標誌位打開,這時候驅動程序的功能尚未用到,只是內核在執行而已。插槽輸入輸出接口標誌位打開就調用了open方法打開了設備接口。關閉接口方法同理。若是執行成功將返回0,不然返回一個負數。
驅動程序的實際代碼完成不少相似於char或block類型的操做,open調用並分配系統資源,stop中止並釋放系統資源。接口和外部世界通信以前須要將物理設備上的地址複製到dev->dev_addr變量中,snull模型驅動程序中使用ASCII字符串ETH_ALEN來杜撰一個物理網卡地址。
open方法也開啓了一個傳輸隊列,內核提供了一個函數來實現一個隊列:
void netif_start_queue (struct net_device *dev);
另外snull模型的代碼相似如下:
int snull_open(struct net_device *dev)
            {
            MOD_INC_USE_COUNT;
            memcpy(dev->dev_addr, "\0SNUL0", ETH_ALEN);
            dev->dev_addr[ETH_ALEN-1] += (dev - snull_devs);
            netif_start_queue(dev); return 0;
            }
            
由於snull模型中並無真實的硬件網絡設備因此open方法中沒有太多代碼,stop方法同理:
int snull_release(struct net_device *dev)
            {
            /*釋放端口和irq,相似於fops->close */
            netif_stop_queue(dev);
            /*沒法再傳輸數據了*/
            MOD_DEC_USE_COUNT;
            return 0;
            }
開啓和關閉接口的方法是一對矛盾。
網絡接口的最重要任務:發送和接受網絡數據包
接着講網絡接口要完成的最重要的任務:發送和接受網絡數據包。發送包容易理解因此先講,在高級網絡層中每一個包都屬於一個特定編號的網絡插槽(socket)進出的包使用sk_buff結構變量來列表表示,插槽緩衝(sk_buff)結構變量貫穿於整個linux網絡之系統中。具體定義能夠在linux/skbuff.h文件中找到。
指向sk_buff變量的指針一般被稱做skb,插槽緩衝是一個複雜的結構,內核負責提供必定數量的動做函數來操做該結構,開始經過調用hard_start_transmit方法函數把須要發送的包弄進出站隊列,hard_start_xmit中含有包在物理媒介上的地址,skb->data指向正在傳輸的包,skb->len是用八進制表示的包長度。snull模型中包的傳輸代碼和實際物理網卡的驅動代碼是隔離的,snull的包傳輸代碼以下:
int snull_tx(struct sk_buff *skb, struct net_device *dev)
            {
            int len;
            char *data;
            struct snull_priv *priv = (struct snull_priv *) dev->priv;
            len = skb->len < ETH_ZLEN ? ETH_ZLEN : skb->len; data = skb->data;
            dev->trans_start = jiffies;
            /*保存時戳*/
            priv->skb = skb;
            /*記住緩衝指針以便隨時中斷釋放,
            實際傳送數據與設備相關,這裏省略了*/ snull_hw_tx(data, len, dev);
            return 0;
            }
            
那麼如何併發地控制傳輸呢?對於緩衝暫時滿載時就要經過netif_stop_queue來暫停,並使用void netif_wake_queue(struct net_device *dev)來繼續從新啓動,設備被分的內存中可同時有多個包緩衝。
一種機制控制設定的動做沒有在規定時間內完成則被視爲超過期間,有問題發生,相關的監測函數是watchdog_timeo,該函數在net_device結構中,jiffies是時間控制表。而後調用tx_timeout方法來處理。超時一旦發生,驅動程序代碼必須作出錯誤標記,snull模型中錯誤一旦發生將調用snull_interrupt,netif_wake_queue等函數。                                    
    
    
Linux網絡設備驅動編程
    
    
來源: ChinaUnix博客  日期: 2008.02.03 17:36 (共有1條評論) 我要評論
 
在此僅僅討論網絡設備驅動的通常寫法,有關硬件部分的相關代碼因爲硬件規格不一樣,予以省略。有什麼地方錯誤,或補充,歡迎你們提出。
1, 驅動模塊的加載和卸載
若是網絡設備(包括wireless)是PCI規範的,則先是向內核註冊該PCI設備(pci_register_driver),而後由pci_driver數據結構中的probe函數指針所指向的偵測函數來初始化該PCI設備,而且同時註冊和初始化該網絡設備。
若是網絡設備(包括wireless)是PCMCIA規範的,則先是向內核註冊該PCMCIA設備(register_pccard_driver),而後driver_info_t數據結構中的attach函數指針所指向的偵測函數來初始化該PCMCIA設備,而且同時註冊和初始化該網絡設備。
static int __init tg3_init(void)
{
//先註冊成PCI設備,並初始化,若是是其餘的ESIA,PCMCIA,用其餘函數
return pci_module_init(&tg3_driver);
}
static void __exit tg3_cleanup(void)
{
pci_unregister_driver(&tg3_driver);//註銷PCI設備
}
module_init(tg3_init); //驅動模塊的加載
module_exit(tg3_cleanup); //驅動模塊的卸載
申明爲PCI設備:
static struct pci_driver tg3_driver = {
.name = DRV_MODULE_NAME,
.id_table = tg3_pci_tbl, //此驅動所支持的網卡系列,vendor_id, device_id
.probe = tg3_init_one, //初始化網絡設備的回調函數
.remove = __devexit_p(tg3_remove_one), //註銷網絡設備的回調函數
.suspend = tg3_suspend, //設備掛起函數
.resume = tg3_resume //設備恢復函數
};
2,PCI設備探測函數probe,初始化網絡設備
static int __devinit tg3_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
{
//初始化設備,使I/O,memory可用,喚醒設備
pci_enable_device(pdev);
//申請內存空間,配置網卡的I/O,memory資源
pci_request_regions(pdev, DRV_MODULE_NAME);
pci_set_master(pdev);
//設置DMA屬性
pci_set_dma_mask(pdev, (u64) 0xffffffffffffffff);
//網卡 I/O,memory資源的啓始地址
tg3reg_base = pci_resource_start(pdev, 0);
//網卡I/O,memory資源的大小
tg3reg_len = pci_resource_len(pdev, 0);
//分配並設置網絡設備
dev = alloc_etherdev(sizeof(*tp));
//申明爲內核設備模塊
SET_MODULE_OWNER(dev);
//初始化私有結構中的各成員值
tp = dev->priv;
tp->pdev = pdev;
tp->dev = dev;
……
//鎖的初始化
spin_lock_init(&tp->lock);
//映射I/O,memory地址到私有域中的寄存器結構
tp->regs = (unsigned long) ioremap(tg3reg_base, tg3reg_len);
dev->irq = pdev->irq;
//網絡設備回調函數賦值
dev->open = tg3_open;
dev->stop = tg3_close;
dev->get_stats = tg3_get_stats;
dev->set_multicast_list = tg3_set_rx_mode;
dev->set_mac_address = tg3_set_mac_addr;
dev->do_ioctl = tg3_ioctl;
dev->tx_timeout = tg3_tx_timeout;
dev->hard_start_xmit= tg3_start_xmit;
//網卡的MAC地址賦值dev->addr
tg3_get_device_address(tp);
//註冊網絡設備
register_netdev(dev);
//把網絡設備指針地址放入PCI設備中的設備指針中
pci_set_drvdata(pdev, dev);
}
3,註銷網絡設備
static void __devexit tg3_remove_one(struct pci_dev *pdev)
{
struct net_device *dev = pci_get_drvdata(pdev);
//註銷網絡設備
unregister_netdev(dev);
//取消地址映射
iounmap((void *) ((struct tg3 *)(dev->priv))->regs);
//釋放網絡設備
kfree(dev);
//釋放PCI資源
pci_release_regions(pdev);
//停用PCI設備
pci_disable_device(pdev);
//PCI設備中的設備指針賦空
pci_set_drvdata(pdev, NULL);
}
4,打開網絡設備
static int tg3_open(struct net_device *dev)
{
//分配一箇中斷
request_irq(dev->irq, tg3_interrupt, SA_SHIRQ, dev->name, dev);
/* int request_irq(unsigned int irq,
void (*handler)(int irq, void *dev_id, struct pt_regs *regs),
unsigned long irqflags,
const char * devname,
void *dev_id);
irq是要申請的硬件中斷號。在Intel平臺,範圍0--15。handler是向系統登記的中斷處理函數。這是一個回調函數,中斷髮生時,系統調用這個函數,傳入的參數包括硬件中斷號,device id,寄存器值。dev_id就是下面的request_irq時傳遞給系統的參數dev_id。irqflags是中斷處理的一些屬性。比較重要的有SA_INTERRUPT,標明中斷處理程序是快速處理程序(設置SA_INTERRUPT)仍是慢速處理程序(不設置SA_INTERRUPT)。快速處理程序被調用時屏蔽全部中斷。慢速處理程序不屏蔽。還有一個SA_SHIRQ屬性,設置了之後運行多個設備共享中斷。dev_id在中斷共享時會用到。通常設置爲這個設備的device結構自己或者NULL。中斷處理程序能夠用dev_id找到相應的控制這個中斷的設備,或者用rq2dev_map找到中斷對應的設備。*/
//初始化硬件
tg3_init_hw(tp);
//初始化收包和發包的緩衝區
tg3_init_rings(tp);
//初始化定時器
init_timer(&tp->timer);
tp->timer.expires = jiffies + tp->timer_offset;
tp->timer.data = (unsigned long) tp;
tp->timer.function = tg3_timer; //超時回調函數
add_timer(&tp->timer);
//容許網卡開始傳輸包
netif_start_queue(dev);
}
5,關閉網絡設備
static int tg3_close(struct net_device *dev)
{
//中止網卡傳輸包
netif_stop_queue(dev);
netif_carrier_off(tp->dev);
//去除定時器
del_timer_sync(&tp->timer);
//釋放收包和發包的緩衝區
tg3_free_rings(tp);
//釋放中斷
free_irq(dev->irq, dev);
}
6,硬件處理數據包發送
static int tg3_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
len = (skb->len - skb->data_len);
//以DMA方式向網卡物理設備傳輸包。若是是wireless的話,須要根據802.11協議及硬件的規範重新填充
//硬件幀頭,而後提交給硬件發送。
mapping = pci_map_single(tp->pdev, skb->data, len, PCI_DMA_TODEVICE);
tp->tx_buffers[entry].skb = skb;
pci_unmap_addr_set(&tp->tx_buffers[entry], mapping, mapping);
//硬件發送
tg3_set_txd(tp, entry, mapping, len, base_flags, mss_and_is_end);
//記錄發包開始時間
dev->trans_start = jiffies;
}
7,中斷處理收包,發包
static void tg3_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
//若是要收包
tg3_rx(tp);
//若是要發包
tg3_tx(tp);
}
8,發包
static void tg3_tx(struct tg3 *tp)
{
struct tx_ring_info *ri = &tp->tx_buffers[sw_idx];
struct sk_buff *skb = ri->skb;
//以DMA方式向網卡傳輸包完畢
pci_unmap_single(tp->pdev, pci_unmap_addr(ri, mapping),
(skb->len - skb->data_len), PCI_DMA_TODEVICE);
ri->skb = NULL;
dev_kfree_skb_irq(skb);
}
9,收包
static int tg3_rx(struct tg3 *tp, int budget)
{
struct sk_buff *copy_skb;
//分配一個包
copy_skb = dev_alloc_skb(len + 2);
copy_skb->dev = tp->dev;
//修改包頭空間
skb_reserve(copy_skb, 2);
//加入數據到包中
skb_put(copy_skb, len);
//以DMA方式從網卡傳輸回數據
pci_dma_sync_single(tp->pdev, dma_addr, len, PCI_DMA_FROMDEVICE);
memcpy(copy_skb->data, skb->data, len);
skb = copy_skb;
//解析包的協議
skb->protocol = eth_type_trans(skb, tp->dev);
//把包送到協議層
netif_rx(skb);
//記錄收包時間
tp->dev->last_rx = jiffies;
}
10, 讀取包的網卡收發包的狀態,統計數據
static struct net_device_stats *tg3_get_stats(struct net_device *dev)
{
//從硬件相關的寄存器讀取數據,累加
//stats->rx_packets, stats->tx_packets, stats->rx_bytes, stats->tx_bytes等
}
11, 用戶的ioctl命令系統調用
static int tg3_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
struct mii_ioctl_data *data = (struct mii_ioctl_data *)&ifr->ifr_data;
switch(cmd) {
//ethtool程序命令的調用
case SIOCETHTOOL:
return tg3_ethtool_ioctl(dev, (void *) ifr->ifr_data);
//mii程序命令的調用
case SIOCGMIIREG: {
err = tg3_readphy(tp, data->reg_num & 0x1f, &mii_regval)
data->val_out = mii_regval;
return err;
}
……
}
}
12, PCI設備的掛起和恢復函數
static int tg3_suspend(struct pci_dev *pdev, u32 state)
{
//停用網卡的中斷寄存器
tg3_disable_ints(tp);
//中止網卡收發包
netif_device_detach(dev);
//中止網卡某些硬件,fireware的一些功能
tg3_halt(tp);
//設置網卡的電源狀態
tg3_set_power_state(tp, state);
}
static int tg3_resume(struct pci_dev *pdev)
{
//恢復網卡電源
tg3_set_power_state(tp, 0);
//容許網卡收發包
netif_device_attach(dev);
//初始化收發包的緩衝區
tg3_init_rings(tp);
//初始化網卡硬件
tg3_init_hw(tp);
//打開網卡中斷寄存器
tg3_enable_ints(tp);
}
13,參數設置
在驅動程序裏還提供一些方法供系統對設備的參數進行設置和讀取信息。通常只有超級用戶(root)權限才能對設備參數進行設置。設置方法有:
tg3_set_mac_addr (dev->set_mac_address)
當用戶調用ioctl類型爲SIOCSIFHWADDR時是要設置這個設備的mac地址。通常對mac地址的設置沒有太大意義的。
dev->set_config()
當用戶調用ioctl時類型爲SIOCSIFMAP時,系統會調用驅動程序的set_config方法
用戶會傳遞一個ifmap結構包含須要的I/O、中斷等參數。
總結:
全部的Linux網絡驅動程序遵循通用的接口。設計時採用的是面向對象的方法。一個設備就是一個對象(net_device 結構),它內部有本身的數據和方法。一個網絡設備最基本的方法有初始化,發送和接收。
Linux網絡驅動程序的體系結構能夠劃分爲四層:
網絡協議接口,網絡設備接口,設備驅動功能,網絡設備和網絡媒介層
網絡驅動程序,最主要的工做就是完成設備驅動功能層。在Linux中全部網絡設備都抽象爲一個接口,這個接口提供了對全部網絡設備的操做集合。由數據結構struct net_device來表示網絡設備在內核中的運行狀況,即網絡設備接口。它既包括純軟件網絡設備接口,如環路(Loopback),也包括硬件網絡設備接口,如以太網卡。而由以dev_base爲頭指針的設備鏈表來集體管理全部網絡設備,該設備鏈表中的每一個元素表明一個網絡設備接口。數據結構net_device中有不少供系統訪問和協議層調用的設備方法,包括初始化,打開和關閉網絡設備的open和stop函數,處理數據包發送的hard_start_xmit函數,以及中斷處理函數等。
網絡設備在Linux裏作專門的處理。Linux的網絡系統主要是基於BSD unix的socket機制。在系統和驅動程序之間定義有專門的數據結構(sk_buff)進行數據的傳遞。系統裏支持對發送數據和接收數據的緩存,提供流量控制機制,提供對多協議的支持。
Linux網絡設備驅動編程
最高的質量最低的成本——節省70%PCB返修成本 查看最近90天中添加的最新產品 最新電子元器件資料免費下載 派睿電子TI有獎問答 - 送3D汽車鼠標 IR推出採用焊前金屬的汽車級絕緣柵雙極晶體管 全球電子鏈接器生產商—samtec 最新斷路器保護套
  在此僅僅討論網絡設備驅動的通常寫法,有關硬件部分的相關代碼因爲硬件規格不一樣,予以省略。有什麼地方錯誤,或補充,歡迎你們提出。
  1.驅動模塊的加載和卸載
  若是網絡設備(包括wireless)是PCI規範的,則先是向內核註冊該PCI設備(pci_register_driver),而後由pci_driver數據結構中的probe函數指針所指向的偵測函數來初始化該PCI設備,而且同時註冊和初始化該網絡設備。
  若是網絡設備(包括wireless)是PCMCIA規範的,則先是向內核註冊該PCMCIA設備(register_pccard_driver),而後driver_info_t數據結構中的attach函數指針所指向的偵測函數來初始化該PCMCIA設備,而且同時註冊和初始化該網絡設備。
  static int __init tg3_init(void)
  {
  //先註冊成PCI設備,並初始化,若是是其餘的ESIA,PCMCIA,用其餘函數
  return pci_module_init(&tg3_driver);
  }
  static void __exit tg3_cleanup(void)
  {
  pci_unregister_driver(&tg3_driver);//註銷PCI設備
  }
  module_init(tg3_init); //驅動模塊的加載
  module_exit(tg3_cleanup); //驅動模塊的卸載
  申明爲PCI設備:
  static struct pci_driver tg3_driver = {
  .name = DRV_MODULE_NAME,
  .id_table = tg3_pci_tbl, //此驅動所支持的網卡系列,vendor_id, device_id
  .probe = tg3_init_one, //初始化網絡設備的回調函數
  .remove = __devexit_p(tg3_remove_one), //註銷網絡設備的回調函數
  .suspend = tg3_suspend, //設備掛起函數
  .resume = tg3_resume //設備恢復函數
  };
  2.PCI設備探測函數probe,初始化網絡設備
  static int __devinit tg3_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
  {
  //初始化設備,使I/O,memory可用,喚醒設備
  pci_enable_device(pdev);
  //申請內存空間,配置網卡的I/O,memory資源
  pci_request_regions(pdev, DRV_MODULE_NAME);
  pci_set_master(pdev);
  //設置DMA屬性
  pci_set_dma_mask(pdev, (u64) 0xffffffffffffffff);
  //網卡 I/O,memory資源的啓始地址
  tg3reg_base = pci_resource_start(pdev, 0);
  //網卡I/O,memory資源的大小
  tg3reg_len = pci_resource_len(pdev, 0);
  //分配並設置網絡設備
  dev = alloc_etherdev(sizeof(*tp));
  //申明爲內核設備模塊
  SET_MODULE_OWNER(dev);
  //初始化私有結構中的各成員值
  tp = dev->priv;
  tp->pdev = pdev;
  tp->dev = dev;
  ……
  //鎖的初始化
  spin_lock_init(&tp->lock);
  //映射I/O,memory地址到私有域中的寄存器結構
  tp->regs = (unsigned long) ioremap(tg3reg_base, tg3reg_len);
  dev->irq = pdev->irq;
  //網絡設備回調函數賦值
  dev->open = tg3_open;
  dev->stop = tg3_close;
  dev->get_stats = tg3_get_stats;
  dev->set_multicast_list = tg3_set_rx_mode;
  dev->set_mac_aDDRess = tg3_set_mac_addr;
  dev->do_ioctl = tg3_ioctl;
  dev->tx_timeout = tg3_tx_timeout;
  dev->hard_start_xmit= tg3_start_xmit;
  //網卡的MAC地址賦值dev->addr
  tg3_get_device_address(tp);
  //註冊網絡設備
  register_netdev(dev);
  //把網絡設備指針地址放入PCI設備中的設備指針中
  pci_set_drvdata(pdev, dev);
  }
  3.註銷網絡設備
  static void __devexit tg3_remove_one(struct pci_dev *pdev)
  {
  struct net_device *dev = pci_get_drvdata(pdev);
  //註銷網絡設備
  unregister_netdev(dev);
  //取消地址映射
  iounmap((void *) ((struct tg3 *)(dev->priv))->regs);
  //釋放網絡設備
  kfree(dev);
  //釋放PCI資源
  pci_release_regions(pdev);
  //停用PCI設備
  pci_disable_device(pdev);
  //PCI設備中的設備指針賦空
  pci_set_drvdata(pdev, NULL);
  }
  4.打開網絡設備
  static int tg3_open(struct net_device *dev)
  {
  //分配一箇中斷
  request_irq(dev->irq, tg3_interrupt, SA_SHIRQ, dev->name, dev);
  /* int request_irq(unsigned int irq,
  void (*handler)(int irq, void *dev_id, struct pt_regs *regs),
  unsigned long irqflags,
  const char * devname,
  void *dev_id);
  irq是要申請的硬件中斷號。在Intel平臺,範圍0--15。handler是向系統登記的中斷處理函數。這是一個回調函數,中斷髮生時,系統調用這個函數,傳入的參數包括硬件中斷號,device id,寄存器值。dev_id就是下面的request_irq時傳遞給系統的參數dev_id。irqflags是中斷處理的一些屬性。比較重要的有SA_INTERRUPT,標明中斷處理程序是快速處理程序(設置SA_INTERRUPT)仍是慢速處理程序(不設置SA_INTERRUPT)。快速處理程序被調用時屏蔽全部中斷。慢速處理程序不屏蔽。還有一個SA_SHIRQ屬性,設置了之後運行多個設備共享中斷。dev_id在中斷共享時會用到。通常設置爲這個設備的device結構自己或者NULL。中斷處理程序能夠用dev_id找到相應的控制這個中斷的設備,或者用rq2dev_map找到中斷對應的設備。*/
  //初始化硬件
  tg3_init_hw(tp);
  //初始化收包和發包的緩衝區
  tg3_init_rings(tp);
  //初始化定時器
  init_timer(&tp->timer);
  tp->timer.expires = jiffies + tp->timer_offset;
  tp->timer.data = (unsigned long) tp;
  tp->timer.function = tg3_timer; //超時回調函數
  add_timer(&tp->timer);
  //容許網卡開始傳輸包
  netif_start_queue(dev);
  }
  5.關閉網絡設備
  static int tg3_close(struct net_device *dev)
  {
  //中止網卡傳輸包
  netif_stop_queue(dev);
  netif_carrier_off(tp->dev);
  //去除定時器
  del_timer_sync(&tp->timer);
  //釋放收包和發包的緩衝區
  tg3_free_rings(tp);
  //釋放中斷
  free_irq(dev->irq, dev);
  }
  [NextPage]
  6.硬件處理數據包發送
  static int tg3_start_xmit(struct sk_buff *skb, struct net_device *dev)
  {
  len = (skb->len - skb->data_len);
  //以DMA方式向網卡物理設備傳輸包。若是是wireless的話,須要根據802.11協議及硬件的規範重新填充
  //硬件幀頭,而後提交給硬件發送。
  mapping = pci_map_single(tp->pdev, skb->data, len, PCI_DMA_TODEVICE);
  tp->tx_buffers[entry].skb = skb;
  pci_unmap_addr_set(&tp->tx_buffers[entry], mapping, mapping);
  //硬件發送
  tg3_set_txd(tp, entry, mapping, len, base_flags, mss_and_is_end);
  //記錄發包開始時間
  dev->trans_start = jiffies;
  }
  7.中斷處理收包,發包
  static void tg3_interrupt(int irq, void *dev_id, struct pt_regs *regs)
  {
  //若是要收包
  tg3_rx(tp);
  //若是要發包
  tg3_tx(tp);
  }
  8.發包
  static void tg3_tx(struct tg3 *tp)
  {
  struct tx_ring_info *ri = &tp->tx_buffers[sw_idx];
  struct sk_buff *skb = ri->skb;
  //以DMA方式向網卡傳輸包完畢
  pci_unmap_single(tp->pdev, pci_unmap_addr(ri, mapping),
  (skb->len - skb->data_len), PCI_DMA_TODEVICE);
  ri->skb = NULL;
  dev_kfree_skb_irq(skb);
  }
  9.收包
  static int tg3_rx(struct tg3 *tp, int budget)
  {
  struct sk_buff *copy_skb;
  //分配一個包
  copy_skb = dev_alloc_skb(len + 2);
  copy_skb->dev = tp->dev;
  //修改包頭空間
  skb_reserve(copy_skb, 2);
  //加入數據到包中
  skb_put(copy_skb, len);
  //以DMA方式從網卡傳輸回數據
  pci_dma_sync_single(tp->pdev, dma_addr, len, PCI_DMA_FROMDEVICE);
  memcpy(copy_skb->data, skb->data, len);
  skb = copy_skb;
  //解析包的協議
  skb->protocol = eth_type_trans(skb, tp->dev);
  //把包送到協議層
  netif_rx(skb);
  //記錄收包時間
  tp->dev->last_rx = jiffies;
  }
  10.讀取包的網卡收發包的狀態,統計數據
  static struct net_device_stats *tg3_get_stats(struct net_device *dev)
  {
  //從硬件相關的寄存器讀取數據,累加
  //stats->rx_packets, stats->tx_packets, stats->rx_bytes, stats->tx_bytes等
  }
  11.用戶的ioctl命令系統調用
  static int tg3_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
  {
  struct mii_ioctl_data *data = (struct mii_ioctl_data *)&ifr->ifr_data;
  switch(cmd) {
  //ethtool程序命令的調用
  case SIO*HTOOL:
  return tg3_ethtool_ioctl(dev, (void *) ifr->ifr_data);
  //mii程序命令的調用
  case SIOCGMIIREG: {
  err = tg3_readphy(tp, data->reg_num & 0x1f, &mii_regval)
  data->val_out = mii_regval;
  return err;
  }
  ……
  }
  }
  12.PCI設備的掛起和恢復函數
  static int tg3_suspend(struct pci_dev *pdev, u32 state)
  {
  //停用網卡的中斷寄存器
  tg3_disable_ints(tp);
  //中止網卡收發包
  netif_device_detach(dev);
  //中止網卡某些硬件,fireware的一些功能
  tg3_halt(tp);
  //設置網卡的電源狀態
  tg3_set_power_state(tp, state);
  }
  static int tg3_resume(struct pci_dev *pdev)
  {
  //恢復網卡電源
  tg3_set_power_state(tp, 0);
  //容許網卡收發包
  netif_device_attach(dev);
  //初始化收發包的緩衝區
  tg3_init_rings(tp);
  //初始化網卡硬件
  tg3_init_hw(tp);
  //打開網卡中斷寄存器
  tg3_enable_ints(tp);
  }
  13.參數設置
  在驅動程序裏還提供一些方法供系統對設備的參數進行設置和讀取信息。通常只有超級用戶(root)權限才能對設備參數進行設置。設置方法有:
  tg3_set_mac_addr (dev->set_mac_address)
  當用戶調用ioctl類型爲SIOCSIFHWADDR時是要設置這個設備的mac地址。通常對mac地址的設置沒有太大意義的。
  dev->set_config()
  當用戶調用ioctl時類型爲SIOCSIFMAP時,系統會調用驅動程序的set_config方法
  用戶會傳遞一個ifmap結構包含須要的I/O、中斷等參數。
  總結:
  全部的Linux網絡驅動程序遵循通用的接口。設計時採用的是面向對象的方法。一個設備就是一個對象(net_device 結構),它內部有本身的數據和方法。一個網絡設備最基本的方法有初始化,發送和接收。
  Linux網絡驅動程序的體系結構能夠劃分爲四層:
  網絡協議接口,網絡設備接口,設備驅動功能,網絡設備和網絡媒介層
  網絡驅動程序,最主要的工做就是完成設備驅動功能層。在Linux中全部網絡設備都抽象爲一個接口,這個接口提供了對全部網絡設備的操做集合。由數據結構struct net_device來表示網絡設備在內核中的運行狀況,即網絡設備接口。它既包括純軟件網絡設備接口,如環路(Loopback),也包括硬件網絡設備接口,如以太網卡。而由以dev_base爲頭指針的設備鏈表來集體管理全部網絡設備,該設備鏈表中的每一個元素表明一個網絡設備接口。數據結構net_device中有不少供系統訪問和協議層調用的設備方法,包括初始化,打開和關閉網絡設備的open和stop函數,處理數據包發送的hard_start_xmit函數,以及中斷處理函數等。
  網絡設備在Linux裏作專門的處理。Linux的網絡系統主要是基於BSD unix的socket機制。在系統和驅動程序之間定義有專門的數據結構(sk_buff)進行數據的傳遞。系統裏支持對發送數據和接收數據的緩存,提供流量控制機制,提供對多協議的支持。
淺談 Linux 內核開發之網絡設備驅動
趙 昊翔, 軟件工程師, Cisco Systems
簡介: 本文介紹了網絡設備的基本概念,並從 Linux 內核的角度出發,介紹了網絡設備驅動的開發方法和開發中的注意事項。
標記本文!
發佈日期: 2010 年 7 月 15 日
級別: 中級
訪問狀況 435 次瀏覽
建議: 3 (查看或添加評論)  
     平均分 (共 10 個評分 )
 網絡設備介紹
網絡設備是計算機體系結構中必不可少的一部分,處理器若是想與外界通訊,一般都會選擇網絡設備做爲通訊接口。衆所周知,在 OSI(Open Systems Interconnection,開放網際互連)中,網絡被劃分爲七個層次,從下到上分別是物理層、數據鏈路層、網絡層、傳輸層、會話層、表示層和應用層。咱們所講的網絡設備也包括兩個層次,一層叫作 MAC(Media Access Control)層,對應於 OSI 的數據鏈路層;另外一層叫作 PHY(Physical Layer)層,對應於物理層。
經常使用的網絡設備有不少,好比 PPC85XX 的 TSEC、AMCC 440GX 的 EMAC、INTEL 的 82559 等,它們的工做原理基本相同。
DMA 介紹
網絡設備的核心處理模塊是一個被稱做 DMA(Direct Memory Access)的控制器,DMA 模塊可以協助處理器處理數據收發。對於數據發送來講,它可以將組織好的數據自動發出,無需處理器干預;對於數據接收來講,它可以將收到的數據以必定的格式組織起來,通知處理器,並等待處理器來取。
DMA 模塊收發數據的單元被稱爲 BD(Buffer Description,緩存描述符),每一個包都會被分紅若干個幀,而每一個幀則被保存在一個 BD 中。BD 結構一般包含有如下字段:
 typedef struct {
    void *bufptr;    /* 保存當前 BD 對應緩存的起始地址  */
    int length;      /* 保存緩存中存儲的數據包長度      */
    int sc;           /* 保存當前 BD 的狀態信息          */
 } BD_STRUCT;

全部的 BD 就組成了一張 BD 表,如圖 1 所示,通常來講發送方向和接收方向的 BD 表是各自獨立的。

圖 1. BD 表結構
 
數據發送流程
網絡設備經過 DMA 進行數據發送的流程如 圖 2所示。

圖 2. 數據發送流程
 
圖中各步驟的具體含義描述以下:
(1)協議層通知處理器開始發送數據;
(2)處理器從 BD 表中取出一個 BD,將須要發送的數據拷貝至當前 BD 對應的緩存內,並設置好 BD 的狀態;
(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 以及初始化相關內核變量等。設備初始化函數在內核模塊被加載時調用,它的函數形式以下:
 static int __init xx_init (void) {
   ……
 }
 module_init(xx_init);   // 這句話代表模塊加載時自動調用 xx_init 函數

設備初始化函數主要完成如下功能:
1. 硬件初始化
由於網絡設備主要分爲 PHY、MAC 和 DMA 三個硬件模塊,開發者須要分別對這三個模塊進行初始化。
1.初始化 PHY 模塊,包括設置雙工 / 半雙工運行模式、設備運行速率和自協商模式等。
2.初始化 MAC 模塊,包括設置設備接口模式等。
3.初始化 DMA 模塊,包括創建 BD 表、設置 BD 屬性以及給 BD 分配緩存等。
2. 內核變量初始化
初始化並註冊內核設備。內核設備是屬性爲 net_device 的一個變量,開發者須要申請該變量對應的空間(經過 alloc_netdev 函數)、設置變量參數、掛接接口函數以及註冊設備(經過 register_netdev 函數)。
經常使用的掛接接口函數以下:
 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. 數據發送流程圖
 

a.用戶首先建立一個 socket,而後調用 write 之類的寫函數經過 socket 訪問網絡設備,同時將數據保存在 sk_buff 類型的緩衝區中。
b.socket 接口調用網絡設備發送函數(hard_start_xmit),hard_start_xmit 已經在初始化過程當中被掛接成相似於 xx_tx 的具體的發送函數,xx_tx 主要實現以下步驟。
1.從發送 BD 表中取出一個空閒的 BD。
2.根據 sk_buff 中保存的數據修改 BD 的屬性,一個是數據長度,另外一個是數據包緩存指針。值得注意的是,數據包緩存指針對應的必須是物理地址,這是由於 DMA 在獲取 BD 中對應的數據時只能識別儲存該數據緩存的物理地址。
 bd_p->length = skb_p->len;
 bd_p->bufptr = virt_to_phys(skb_p->data);

3.修改該 BD 的狀態爲就緒態,DMA 模塊將自動發送處於就緒態 BD 中所對應的數據。
4.移動發送 BD 表的指針指向下一個 BD。
c.DMA 模塊開始將處於就緒態 BD 緩存內的數據發送至網絡中,當發送完成後自動恢復該 BD 爲空閒態。
2. 數據接收流程
當網絡設備接收到數據時,DMA 模塊會自動將數據保存起來並通知處理器來取,處理器經過中斷或者輪詢方式發現有數據接收進來後,再將數據保存到 sk_buff 緩衝區中,並經過 socket 接口讀出來。流程圖以下所示。

圖 6. 數據接收流程圖
 
a.網絡設備接收到數據後,DMA 模塊搜索接收 BD 表,取出空閒的 BD,並將數據自動保存到該 BD 的緩存中,修改 BD 爲就緒態,並同時觸發中斷(該步驟可選)。
b.處理器能夠經過中斷或者輪詢的方式檢查接收 BD 表的狀態,不管採用哪一種方式,它們都須要實現如下步驟。
1.從接收 BD 表中取出一個空閒的 BD。
2.若是當前 BD 爲就緒態,檢查當前 BD 的數據狀態,更新數據接收統計。
3.從 BD 中取出數據保存在 sk_buff 的緩衝區中。
4.更新 BD 的狀態爲空閒態。
5.移動接收 BD 表的指針指向下一個 BD。
c.用戶調用 read 之類的讀函數,從 sk_buff 緩衝區中讀出數據,同時釋放該緩衝區。
中斷和輪詢
Linux 內核在接收數據時有兩種方式可供選擇,一種是中斷方式,另一種是輪詢方式。
中斷方式
若是選擇中斷方式,首先在使用該驅動以前,須要將該中斷對應的中斷類型號和中斷處理程序註冊進去。網絡設備驅動在初始化時會將具體的 xx_open 函數掛接在驅動的 open 接口上,xx_open 函數掛接中斷的步驟以下。
 request_irq(rx_irq, xx_isr_rx, …… );
 request_irq(tx_irq, xx_isr_tx, …… );

網絡設備的中斷通常會分爲兩種,一種是發送中斷,另外一種是接收中斷。內核須要分別對這兩種中斷類型號進行註冊。
1.發送中斷處理程序(xx_isr_tx)的工做主要是監控數據發送狀態、更新數據發送統計等。
2.接收中斷處理程序(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網絡設備驅動指南(1)
http://os.51cto.com  2007-06-22 11:49  王成國  51CTO.com  我要評論(0)
摘要:Linux中網絡接口是3大標準類設備之一,其餘2種類型分別是字符設備和塊設備。Unix世界裏的"一切皆是文件"的論述,對網絡設備接口來講是不適用的,下面介紹網絡接口設備的驅動程序如何和內核模塊進行交互的知識。
標籤:驅動  網絡設備  Linux  snull  接口

限時報名參加「甲骨文全球大會·2010·北京」及「JavaOne和甲骨文開發者大會2010」
【51CTO.com獨家特稿】前言介紹,本文所涉及概念:
1)snull:是一種虛擬的網絡模型,經過它能夠基本瞭解真實物理網絡接口設備驅動程序的運做。
2)本文代碼都是標準C語言格式。
3)*skb:重要的指針量,指向一塊內存區域用以緩衝待處理的進出網絡數據包。
Linux網絡接口設備概述
Linux中網絡接口是3大標準類設備之一,其餘2種類型分別是字符設備和塊設備。下面介紹網絡接口設備的驅動程序如何和內核模塊進行交互的知識。網絡設備驅動程序異步地接收來自外部世界的網絡數據包,經過push操做將進來的數據包壓向內核,而塊設備驅動程序則將一片數據緩衝發送給內核,驅動程序都須要將相應設備的特徵信息登記到內核中特定的數據結構中,塊設備驅動程序可使用文件形式來描述,而網絡驅動程序則不可使用通常的文件讀寫操做調用,網絡設備驅動程序有本身的內存名字空間,使用push等操做來完成數據包的轉換與遞送。
Unix世界裏的"一切皆是文件"的論述,對網絡設備接口來講是不適用的。塊設備在系統文件樹的/dev目錄下能夠找到特定的文件入口標誌,而網絡設備則沒有這種文件操做入口,一個物理網絡接口上的數百個用於網絡數據交換的插口能夠被應用程序重複使用。

圖1

如上圖,內核中有專門爲網絡設備驅動程序設計的數據包操做接口。經過網絡接口sn0到達網絡snullnet0就是路由的意思.使用route add -net snullnet0 dev sn0指令來完成,2.2以上的Linux內核無需這樣作了,內核自動將添加。若是網絡非C類地址,請用掩碼指定255.255.255.0指定的是C類網絡,由於在snull實驗網絡模型中地址機制被修改,因此發向192.168.0.33的數據包將經過sn0接口送達snullnet1中得192.168.1.33機器,送到其它網絡地址的數據包將被sn1接口丟棄.說到物理數據傳輸,snull網絡模型機制隸屬於以太網Ethernet範疇.
因爲以太網已經被普遍使用的緣由,甚至連打印協議plip接口也聲稱本身隸屬於以太網設備範疇.你還能夠用tcpdump工具來察看snull實驗網模型中的網絡數據走向。使用tcpdump工具在2.0內核中加載snull設備驅動時須要顯示指定Eth=1項。注意snull網絡模型只能用來試驗ip網絡協議的數據包。利用snull網絡模型傳送非IP網絡數據包必須對snull模塊的源代碼進行修改,不然會破壞其它非ip網絡數據包。
Snull模型核心操做
snull核心操做大概有11多項,它們是:
 ether_setup,
open,
stop,
set_config,
hard_start_xmit,
do_ioctl,get_stats,
rebuild_header,
tx_timeout,
watchdog_timeo,
flags,
hard_header_cache,
SET_MODULE_OWNER等.
注意:snull網絡不處理arp數據包,由於核心操做中的IFF_NOARP標誌位.ARP是一個底層的以太網協議標準。任務是將IP地址對應到以太網物理媒質訪問控制地址,也就是MAC地址。由於snull網絡模擬出來的網絡機器無需處理MAC地址,故不處理了。一樣道理hard_header_cache也無需處理了,snull模型中將它設成了NULL。
如何操做網絡設備
如下要介紹如何對網絡設備展開具體操做。網絡接口能夠操做數據包以前必須由內核打開接口並賦予內存地址,使用ifconfig命令就能夠作到,首先經過ioctl分配地址,再將IFF_UP標誌位打開,這時候驅動程序的功能尚未用到,只是內核在執行而已。插槽輸入輸出接口標誌位打開就調用了open方法打開了設備接口。關閉接口方法同理。若是執行成功將返回0,不然返回一個負數。
驅動程序的實際代碼完成不少相似於char或block類型的操做,open調用並分配系統資源,stop中止並釋放系統資源。接口和外部世界通信以前須要將物理設備上的地址複製到dev->dev_addr變量中,snull模型驅動程序中使用ASCII字符串ETH_ALEN來杜撰一個物理網卡地址。
open方法也開啓了一個傳輸隊列,內核提供了一個函數來實現一個隊列:
void netif_start_queue (struct net_device *dev);
另外snull模型的代碼相似如下:
int snull_open(struct net_device *dev)
{
MOD_INC_USE_COUNT;
memcpy(dev->dev_addr, "\0SNUL0", ETH_ALEN);
dev->dev_addr[ETH_ALEN-1] += (dev - snull_devs);
netif_start_queue(dev); return 0;
}
由於snull模型中並無真實的硬件網絡設備因此open方法中沒有太多代碼,stop方法同理:
int snull_release(struct net_device *dev)
{
/*釋放端口和irq,相似於fops->close */
netif_stop_queue(dev);
/*沒法再傳輸數據了*/
MOD_DEC_USE_COUNT;
return 0;
}
開啓和關閉接口的方法是一對矛盾。
網絡接口的最重要任務:發送和接受網絡數據包
接着講網絡接口要完成的最重要的任務:發送和接受網絡數據包。發送包容易理解因此先講,在高級網絡層中每一個包都屬於一個特定編號的網絡插槽(socket)進出的包使用sk_buff結構變量來列表表示,插槽緩衝(sk_buff)結構變量貫穿於整個linux網絡之系統中。具體定義能夠在linux/skbuff.h文件中找到。
指向sk_buff變量的指針一般被稱做skb,插槽緩衝是一個複雜的結構,內核負責提供必定數量的動做函數來操做該結構,開始經過調用hard_start_transmit方法函數把須要發送的包弄進出站隊列,hard_start_xmit中含有包在物理媒介上的地址,skb->data指向正在傳輸的包,skb->len是用八進制表示的包長度。snull模型中包的傳輸代碼和實際物理網卡的驅動代碼是隔離的,snull的包傳輸代碼以下:
int snull_tx(struct sk_buff *skb, struct net_device *dev)
{
int len;
char *data;
struct snull_priv *priv = (struct snull_priv *) dev->priv;
len = skb->len < ETH_ZLEN ? ETH_ZLEN : skb->len; data = skb->data;
dev->trans_start = jiffies;
/*保存時戳*/
priv->skb = skb;
/*記住緩衝指針以便隨時中斷釋放,
實際傳送數據與設備相關,這裏省略了*/ snull_hw_tx(data, len, dev);
return 0;
}
那麼如何併發地控制傳輸呢?對於緩衝暫時滿載時就要經過netif_stop_queue來暫停,並使用void netif_wake_queue(struct net_device *dev)來繼續從新啓動,設備被分的內存中可同時有多個包緩衝。
一種機制控制設定的動做沒有在規定時間內完成則被視爲超過期間,有問題發生,相關的監測函數是watchdog_timeo,該函數在net_device結構中,jiffies是時間控制表。而後調用tx_timeout方法來處理。超時一旦發生,驅動程序代碼必須作出錯誤標記,snull模型中錯誤一旦發生將調用snull_interrupt,netif_wake_queue等函數。

Linux網絡設備驅動指南(2)
http://os.51cto.com  2007-06-22 11:49  王成國  51CTO.com  我要評論(0)
摘要:Linux中網絡接口是3大標準類設備之一,其餘2種類型分別是字符設備和塊設備。Unix世界裏的"一切皆是文件"的論述,對網絡設備接口來講是不適用的,下面介紹網絡接口設備的驅動程序如何和內核模塊進行交互的知識。
標籤:驅動  網絡設備  Linux  snull  接口

限時報名參加「甲骨文全球大會·2010·北京」及「JavaOne和甲骨文開發者大會2010」
網絡數據包的接收代碼說明:
首先網絡接口設備插槽的接受緩衝區sk_buff必須先得到必定的內存空間,並且處在緩衝區上層的程序代碼能夠經過中斷操做和sk_buff進行數據交換。網包通常用中斷機制來處理,也可使用polling機制,但後者多用在分析內核計時器的時候。網絡接口設備多數是用中斷來處理的,這樣能夠提供吞吐量和計算性能。
在snull網絡模型中,模擬出的網絡數據到達內存時,對應的位置指針將提交給負責接收數據的函數snull_rx來進行處理,snull_rx函數就知道了接收到的數據長度以及在內存中的具體位置了,而後snull_rx再將數據提交到網絡代碼的上一層應用代碼進行處理。snull_rx代碼和網包數據指針的獲取代碼是相互獨立的。
中斷機制的操做柄
絕大多數硬件是經過產生中斷的事件來取得和處理器的交互的,當網絡硬件接口發生新的數據收發將會產生中斷事件信號給處理器以得到用於處理的計算資源。注意但面對一樣的中斷事件時,異步的網絡包傳送和PLIP,PPP三種形式的底層中斷代碼實現是不同的,在常見的中斷例行程序代碼能夠找到區別。
snull模型的中斷代碼以下:
注意要鎖住設備,數據收發工做完成後要釋放緩衝佔用的內存空間,這兩項工做能夠用
spin_lock(&priv->lock);

dev_kfree_skb(priv->skb);來完成。
操做器先得到準確的net_device結構指針,該指針來自於dev_id的參數。包接受工做無需調用中斷操做,只要執行snull_rx動做函數就能夠了。
改變聯結狀態的代碼部分。真實網絡中的傳輸介質上附着載波信號。拔掉網線載波信號消失,說明網絡狀態爲關。
網絡設備默認具有載波信號存在,驅動程序可經過動做函數來顯式地改變狀態。好比:
void netif_carrier_on (struct net_device *dev);若是驅動程序沒有偵測到設備上的載波信號,那麼將執行
netif_carrier_off(struct net_device *dev);動做,並告訴內核,一旦偵測到載波信號,那麼將執行
void netif_carrier_on動做函數,也可用int netif_carrier_ok(struct net_device *dev);。
介質訪問控制(MAC)地址的解析
介質訪問控制(MAC)地址的解析簡介:網絡接口通常具備惟一的硬件標識號,用MAC地址來表示,如何將MAC地址和IP地址創建關聯呢?下面將分別對ARP(地址解析協議),沒有ARP的以太網包頭的PLIP協議以及非以太網包頭分別介紹。
在以太網上使用ARP,幸運的是ARP協議由內核來進行管理,網絡接口不要作這些特殊的管理工做,只要接口打開時dev->addr和dev->addr_len變量被正確地賦值,驅動程序就無需去爲IP地址如何解析到MAC硬件地址而擔憂了,ether_setup動做爲dev->hard_header和dev->rebuild_header變量提供正確的設備方法,內核在緩衝管理詳細的MAC地址管理的時候,調用接口驅動程序來幫助創建網絡數據包,內核利用ARP查詢的結果並調用驅動程序中的hard_header方法釋放出網絡包,網絡代碼員通常沒必要須關心這些細節。
不使用arp而直接操做硬件網包頭部分的代碼,修改dev->hard_header方法,snull模型就是這樣:
int snull_header(struct sk_buff *skb, struct net_device *dev, unsigned short type, void *daddr, void *saddr, unsigned int len)
{
struct ethhdr *eth = (struct ethhdr *)skb_push(skb,ETH_HLEN);
eth->h_proto = htons(type);
memcpy(eth->h_source, saddr ? saddr : dev->dev_addr, dev->addr_len);
memcpy(eth->h_dest, daddr ? daddr : dev->dev_addr, dev->addr_len);
eth->h_dest[ETH_ALEN-1] ^= 0x01; return(dev->hard_header_len);
}
以上功能將內核提供的信息格式化成標準的以太網包頭,同時還在目標以太網地址中設定一個位,若是包頭中的pkt_type被設置成PACKET_OTHERHOST,除非接口被設置成混雜模式(promiscuous),不然這些包將被接收主機的netif_rx動做函數丟棄,plip驅動程序將硬件地址的開始的八進制設置爲0xfc,而snull模型驅動設置爲0x00,plip和snull模型地址效果跟以太網的點對點(ppp)鏈接。
接着介紹非以太網包頭,具體信息可去內核源代碼庫中找,絕大多數驅動程序代碼員只要直接採用以太網方案實現就能夠了,包中的協議如何表示的呢?好比IP協議能夠用ETH_P_IP來表示。用16位的二進制序列來表示協議。點對點鏈接來講,地址信息能夠省略掉,由於目的地址和源地址已固定,只要提交協議就能夠了。
字符指針變量skb->mac.raw被網絡層的地址解析機制使用,在網絡層中實現,具體能夠參考net/ipv4/arp.c源碼文件。網絡設備的類型值在Linux/if_arp.h中定義,eth_type_trans函數負責爲接受到的包進行以太網包頭處理,例如:
skb->mac.raw = skb->data;
skb_pull(skb, dev->hard_header_len);
網絡硬件設備的具體類型說明可參考drivers/net/appletalk/cops.c,drivers/net/irda/smc_ircc.c,drivers/net/ppp_generic.c等源文件。
多播網包
多播網包,就是同時發往多個網絡設備的網絡數據包,網包是否屬於多播察看目標地址的前面幾位就能夠了,不少網卡硬件地址都有清楚的相應標誌位,內核負責給網包付給正確的目的地址,驅動程序負責爲內核控制的網絡地址提供相應的數據網包就能夠了,
注意:有些網絡接口可能沒法處理多播網包。
內核支持多播網包時,須要設備操做方法函數、數據結構、設備標誌等的支持,好比:
void (*dev->set_multicast_list) (struct net_device *dev);
struct dev_mc_list *dev->mc_list;(<-與設備相關的多播網包列表)
int dev->mc_count;
IFF_MULTICAST,
IFF_ALLMULTI(多播網包路由開啓時有效),
IFF_PROMISC等,
dev_mc_list結構的定義以下:
struct dev_mc_list
{
struct dev_mc_list *next;
__u8 dmi_addr[MAX_ADDR_LEN];
unsigned char dmi_addrlen;
int dmi_users; int dmi_gusers;
};
用一段僞代碼來描述,代碼中以ff_開頭的都是存放硬件操做的,具體代碼:
void set_multicast_list(struct net_device *dev)
{
struct dev_mc_list *mcptr;
if (dev->flags & IFF_PROMISC) {ff_get_all_packets();
return;
}
/*若是包不少則用另外軟件空間來排序和處理*/
if (dev->flags & IFF_ALLMULTI || dev->mc_count > FF_TABLE_SIZE){ff_get_all_multicast_packets();
return;
}
if (dev->mc_count == 0){ff_get_only_own_packets();
return;
/*全部多播地址存放在硬件過濾器中*/
}
ff_clear_mc_list();
for (mc_ptr = dev->mc_list;
mc_ptr;
mc_ptr = mc_ptr->next)
ff_store_mc_address(mc_ptr->dmi_addr);
ff_get_packets_in_multicast_list();
}
若是接口沒法處理多播包,則上述代碼可簡化,FF_TABLE_SIZE就是0,只須要代碼的最後4個分句就能夠了,但set_multicast_list方法仍是要的,須要它來通知dev->flags標誌位的改變。簡化後的多播包處理代碼叫nonefeatured(nf),以下:
void nf_set_multicast_list(struct net_device *dev)
{
if (dev->flags & IFF_PROMISC) nf_get_all_packets();
else nf_get_only_own_packets();
}
對於點對點的鏈接來講,無需設置set_multicast_list由於接口會接受到全部來的包,代碼中的IFF_PROMISC變量很重要,使用tcpdump等網絡包分析工具時必須開啓該變量。
其餘提醒說明
本文參考Linux內核版本是2.3.43,各Linux版本之間的網絡子系統變量和函數的修訂區別,這裏省略。內核中的網絡驅動部分能夠看到一些可選其餘方式的處理代碼:
#if_def HAVE_DEVLIST
struct netdev_entry netcard_drv = {cardname, netcard_probel, NETCARD_IO_EXTENT, netcard_portlist};
#else 正常的探測例行程序代碼。
瞭解更多其它類型的Linux設備驅動,能夠參考.
設計Linux系統網絡設備驅動程序 (1)
發佈時間:2002.09.18 13:23      來源:開放系統世界——賽迪網     做者:李衛剛
Linux網絡設備驅動程序是Linux操做系統網絡應用中的一個重要組成部分。分析其運行機理,對於設計Linux網絡應用程序是頗有幫助的。咱們能夠在網絡驅動程序這一級作一些與應用相關聯的特殊事情,例如在設計Linux防火牆和網絡入侵檢測系統時,能夠在網絡驅動程序的基礎上攔截網絡數據包,繼而對其進行分析。因爲Linux是開放源代碼的,因此給咱們提供了一個分析和改造網絡驅動程序,並使其知足特殊應用的絕好機會。本文對Linux內核中的網絡驅動程序部分進行了詳細討論,並給出了實現Linux網絡驅動程序的重要過程、一種實現模式和具體實例。
運行機理
1.體系結構
Linux網絡驅動程序的體系結構如圖1所示。能夠劃分爲四層,從上到下分別爲協議接口層、網絡設備接口層、提供實際功能的設備驅動功能層,以及網絡設備和網絡媒介層。在設計網絡驅動程序時,最主要的工做就是完成設備驅動功能層,使其知足咱們本身所需的功能。在Linux中,把全部網絡設備都抽象爲一個接口。這個接口提供了對全部網絡設備的操做集合。由數據結構 struct device來表示網絡設備在內核中的運行狀況,即網絡設備接口。它既包括純軟件網絡設備接口,如環路(Loopback),也能夠包括硬件網絡設備接口,如以太網卡。它由以dev_base爲頭指針的設備鏈表來集中管理全部網絡設備。該設備鏈表中的每一個元素表明一個網絡設備接口。數據結構device中有不少供系統訪問和協議層調用的設備方法,包括供設備初始化和往系統註冊用的init函數、打開和關閉網絡設備的open和stop函數、處理數據包發送的函數hard_ start_xmit,以及中斷處理函數等。有關device數據結構(在內核中也就是net_device)的詳細內容,請參看/linux/include/linux/netdevice.h。
 
2.初始化
網絡設備的初始化主要是由device數據結構中的init函數指針所指的初始化函數來完成的。當內核啓動或加載網絡驅動模塊的時候,就會調用初始化過程。這個過程將首先檢測網絡物理設備是否存在。它經過檢測物理設備的硬件特徵來完成,而後再對設備進行資源配置。這些完成以後就要構造設備的device數據結構,用檢測到的數值來對device中的變量初始化。這一步很重要。最後向Linux內核註冊該設備並申請內存空間。
3. 數據包的發送與接收
數據包的發送和接收是實現Linux網絡驅動程序中兩個最關鍵的過程。對這兩個過程處理的好壞將直接影響到驅動程序的總體運行質量。圖1中也很明確地說明了網絡數據包的傳輸過程。首先在網絡設備驅動加載時,經過device域中的init函數指針調用網絡設備的初始化函數,對設備進行初始化。若是操做成功就能夠經過device域中的open函數指針調用網絡設備的打開函數打開設備,再經過device域中的創建硬件包頭函數指針hard_header來創建硬件包頭信息。最後經過協議接口層函數dev_queue_xmit(詳見/linux/net/core/dev.c)來調用device域中的hard_start_xmit函數指針,完成數據包的發送。該函數將把存放在套接字緩衝區中的數據發送到物理設備。該緩衝區是由數據結構sk_buff (詳見/linux/include/linux/sk_buff.h)來表示的。
數據包的接收是經過中斷機制來完成的。當有數據到達時,就產生中斷信號,網絡設備驅動功能層就調用中斷處理程序,即數據包接收程序來處理數據包的接收。而後,網絡協議接口層調用netif_rx函數(詳見/linux/net/core/dev.c),把接收到的數據包傳輸到網絡協議的上層進行處理。
實現模式
實現Linux網絡設備驅動功能主要有兩種形式:一是經過內核來進行加載,當內核啓動的時候,就開始加載網絡設備驅動程序,內核啓動完成以後,網絡驅動功能也隨即實現了;再就是經過模塊加載的形式。比較二者,第二種形式更加靈活。在此着重對模塊加載形式進行討論。
模塊設計是Linux中特有的技術,它使Linux內核功能更容易擴展。採用模塊來設計Linux網絡設備驅動程序會很輕鬆,而且可以造成固定的模式。任何人只要依照這個模式去設計,都能設計出優良的網絡驅動程序。先簡要介紹一下基於模塊加載網絡驅動程序的設計步驟,後面還結合具體實例來說解。首先經過模塊加載命令insmod來把網絡設備驅動程序插入到內核之中。而後,insmod將調用init_module()函數首先對網絡設備的init函數指針初始化,再經過調用register_netdev()函數在Linux系統中註冊該網絡設備。若是成功,再調用init函數指針所指的網絡設備初始化函數來對設備初始化,將設備的device數據結構插入到dev_base鏈表的末尾。最後能夠經過執行模塊卸載命令rmmod,來調用網絡驅動程序中的cleanup_module()函數,對網絡驅動程序模塊進行卸載。具體實現過程見圖2所示。
 
經過模塊初始化網絡接口是在編譯內核時標記爲編譯爲模塊。系統在啓動時並不知道該接口的存在,須要用戶在/etc/rc.d/目錄中定義的初始啓動腳本中寫入命令或手動將模塊插入內核空間來激活網絡接口。這也給咱們在什麼時候加載網絡設備驅動程序提供了靈活性。
1 2 下一頁>>
設計Linux系統網絡設備驅動程序 (2)
發佈時間:2002.09.18 13:23      來源:開放系統世界——賽迪網     做者:李衛剛
應用實例
咱們以NE2000兼容網卡爲例,來具體介紹基於模塊的網絡驅動程序的設計過程。能夠參考文件linux/drivers/net/ne.c和linux/drivers/net/8390.c。
1.模塊加載和卸載
NE2000網卡的模塊加載功能由init_module()函數完成。具體過程及解釋以下:
int init_module(void)
{
int this_dev, found = 0;
//循環檢測ne2000類型的網絡設備接口
for (this_dev = 0; this_dev < MAX_NE_CARDS; this_dev++)
{
//得到網絡接口對應的net-device結構指針
 struct net_device *dev = &#38;dev_ne[this_dev];
 dev->irq = irq[this_dev];    //初始化該接口的中斷請求號
 dev->mem_end = bad[this_dev];    //初始化接收緩衝區的終點位置
 dev->base_addr = io[this_dev];     //初始化網絡接口的I/O基地址
 dev->init = ne_probe;             //初始化init爲ne_probe,後面介紹此函數
//調用registre_netdevice()向系統登記網絡接口,在這個函數中將分配給網絡接口在系統中唯一
的名稱。而且將該網絡接口設備添加到系統管理的鏈表dev-base中進行管理。
if (register_netdev(dev) == 0) {
     found++;
     continue; }
…  //省略
}
return 0;}
模塊卸載功能由cleanup_module()函數來實現。以下所示:
void cleanup_module(void)
{
int this_dev;
//遍歷整個dev-ne數組
for (this_dev = 0; this_dev < MAX_NE_CARDS; this_dev++) {
    //得到net-device結構指針
struct net_device *dev = &#38;dev_ne[this_dev];
    if (dev->priv != NULL) {
        void *priv = dev->priv;
        struct pci_dev *idev = (struct pci_dev *)ei_status.priv;
//調用函數指針 idev->deactive將已經激活的網卡關閉使用
if (idev)  idev->deactivate(idev);  
free_irq(dev->irq, dev);
//調用函數release_region()釋放該網卡佔用的I/O地址空間
release_region(dev->base_addr, NE_IO_EXTENT);
//調用unregister_netdev()註銷 這個net_device()結構
unregister_netdev(dev);
kfree(priv);  //釋放priv空間
     }
  }
}
2.網絡接口初始化
實現此功能是由ne_probe()函數來完成的。前面已經提到過,在init_module()函數中用它來初始化init函數指針。它主要對網卡進行檢測,而且初始化系統中網絡設備信息,用於後面的網絡數據的發送和接收。具體過程及解釋以下:
int __init ne_probe(struct net_device *dev)
{
  unsigned int base_addr = dev->base_addr;
  //初始化dev-owner成員,由於使用模塊類型驅動,會將dev-owner指向對象modules結構指針。
  SET_MODULE_OWNER(dev);
  //檢測dev->base_addr是否合法,是則執行ne-probe1()函數檢測過程。不是,則須要自動檢測。
  if (base_addr > 0x1ff)  
    return ne_probe1(dev, base_addr);
  else if (base_addr != 0)      
    return -ENXIO;
  //若是有ISAPnP設備,則調用ne_probe_isapnp()檢測這種類型的網卡。
if (isapnp_present() &#38;&#38; (ne_probe_isapnp(dev) == 0))
    return 0;
 …//省略
   return -ENODEV;
}
這其中兩個函數ne_probe_isapnp()和ne_probe19()的區別在於檢測中斷號上。PCI方式只需指定I/O基地址就能夠自動得到IRQ,是由BIOS自動分配的;而ISA方式須要得到空閒的中斷資源才能分配。
3.網絡接口設備打開和關閉
網絡接口設備打開就是激活網絡接口,使它能接收來自網絡的數據而且傳遞到網絡協議棧的上面,也能夠將數據發送到網絡上。設備關閉就是中止操做。
在NE2000網絡驅動程序中,網絡設備打開由dev_open()和ne_open()完成,設備關閉有dev_close()和ne_close()完成。它們相應調用底層函數ei_open()和ei_close()來完成。其實現過程相對簡單,再也不贅述。
4.數據包接收和發送
在驅動程序層次上的發送和接收數據都是經過低層對硬件的讀寫來完成的。當網絡上的數據到來時,將觸發硬件中斷,根據註冊的中斷向量表肯定處理函數,進入中斷向量處理程序,將數據送到上層協議進行處理。
對NE2000網卡的數據接收過程是由ne_probe()函數中的中斷處理函數ei_interrupt來完成的。在進入ei_interrupt()以後再經過ei_receive()從8309的接收緩衝區得到數據,並組合成sk_buff結構,再經過netif_rx()函數將接收到的數據存放在系統的餓接收隊列之中。
Ei-interrupt()的函數原型:void ei_interrupt(int irq,void *dev_id, struct pt_regs *regs)。其中irq爲中斷號,dev-id是表示產生中斷的網絡接口設備對應的結構指針,regs表示當前的寄存器內容。數據發送是由dev_dev_start_xmit函數指針對應的ei_start_xmit函數它來完成數據包的發送。在函數ethdev_init()把net_device結構的hard_start_xmit指針初始化爲ei_start_xmit。
設計Linux網絡設備驅動程序有必定的模式。遵循這個模式,將會大大減輕咱們設計程序的工做量。本文分析了網絡驅動程序在內核中的工做機理,提出了設計網絡驅動程序的一種模式,並給出了實例。這對設計複雜的網絡驅動程序是頗有幫助的

html

相關文章
相關標籤/搜索