26.Linux-網卡驅動介紹以及制作虛擬網卡驅動(詳解)


1.描述

網卡的驅動其實很簡單,它還是與硬件相關,主要是負責收發網絡的數據包,它將上層協議傳遞下來的數據包以特定的媒介訪問控制方式進行發送, 並將接收到的數據包傳遞給上層協議。

網卡設備與字符設備和塊設備不同, 網絡設備並不對應於/dev目錄下的文件,不過會存放在/sys/class/net目錄下

如下圖所示,我們通過ls /sys/class/net/ 命令,可以看到有兩個網卡:

 

 

2.Linux系統對網絡設備驅動定義了4個層次, 這4個層次有到下分為:

1)網絡協議接口層:

實現統一的數據包收發的協議,該層主要負責調用dev_queue_xmit()函數發送數據, netif_rx()函數接收數據

2)網絡設備接口層:

通過net_device結構體來描述一個具體的網絡設備的信息,實現不同的硬件的統一

3)設備驅動功能層:

用來負責驅動網絡設備硬件來完成各個功能, 它通過hard_start_xmit() 函數啟動發送操作, 並通過網絡設備上的中斷觸發接收操作,

4)網絡設備與媒介層:

用來負責完成數據包發送和接收的物理實體, 設備驅動功能層的函數都在這物理上驅動的

層次結構如下圖所示:

 

3.網卡驅動初始化

而我們的網卡驅動程序,只需要編寫網絡設備接口層,填充net_device數據結構的內容並將net_device注冊入內核,設置硬件相關操作,使能中斷處理等

3.1其中net_device結構體的重要成員,整理后如下所示:

struct net_device
{
       char               name[IFNAMSIZ];              //網卡設備名稱
       unsigned long              mem_end;             //該設備的內存結束地址
       unsigned long              mem_start;            //該設備的內存起始地址
       unsigned long              base_addr;            //該設備的內存I/O基地址
       unsigned int          irq;                       //該設備的中斷號

       unsigned char        if_port;                  //多端口設備使用的端口類型
    unsigned char        dma;                     //該設備的DMA通道
unsigned long state; //網絡設備和網絡適配器的狀態信息 struct net_device_stats* (*get_stats)(struct net_device *dev); //獲取流量的統計信息
                        //運行ifconfig便會調用該成員函數,並返回一個net_device_stats結構體獲取信息
struct net_device_stats stats; //用來保存統計信息的net_device_stats結構體 unsigned long features; //接口特征, unsigned int flags; //flags指網絡接口標志,以IFF_(Interface Flags)開頭 //當flags =IFF_UP( 當設備被激活並可以開始發送數據包時, 內核設置該標志)、 IFF_AUTOMEDIA(設置設備可在多種媒介間切換)、
IFF_BROADCAST( 允許廣播)、IFF_DEBUG( 調試模式, 可用於控制printk調用的詳細程度) 、 IFF_LOOPBACK( 回環)、
IFF_MULTICAST( 允許組播) 、 IFF_NOARP( 接口不能執行ARP,點對點接口就不需要運行 ARP) 和IFF_POINTOPOINT( 接口連接到點到點鏈路) 等。
unsigned mtu; //最大傳輸單元,也叫最大數據包 unsigned short type;   //接口的硬件類型 unsigned short hard_header_len; //硬件幀頭長度,一般被賦為ETH_HLEN,即14     unsigned char dev_addr[MAX_ADDR_LEN]; //存放設備的MAC地址 unsigned long last_rx; //接收數據包的時間戳,調用netif_rx()后賦上jiffies即可 unsigned long trans_start; //發送數據包的時間戳,當要發送的時候賦上jiffies即可 unsigned char dev_addr[MAX_ADDR_LEN]; //MAC地址 int (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev); //數據包發送函數, sk_buff就是用來收發數據包的結構體     void (*tx_timeout) (struct net_device *dev); //發包超時處理函數     ... ... }

 

上面講到的統計信息net_device_stats結構體,其中重要成員如下所示:

struct net_device_stats
{
       unsigned long       rx_packets;            /*收到的數據包數*/
       unsigned long       tx_packets;            /*發送的數據包數    */
       unsigned long       rx_bytes;               /*收到的字節數,可以通過sk_buff結構體的成員len來獲取*/
       unsigned long       tx_bytes;               /*發送的字節數,可以通過sk_buff結構體的成員len來獲取*/
       unsigned long       rx_errors;              /*收到的錯誤數據包數*/
       unsigned long       tx_errors;              /*發送的錯誤數據包數*/
       ... ...
}

 

 

3.2 所以init函數,初始化網卡步驟如下所示:

 

  • 1)使用alloc_netdev()來分配一個net_device結構體 
  • 2)設置網卡硬件相關的寄存器
  • 3)設置net_device結構體的成員
  • 4)使用register_netdev()來注冊net_device結構體

 

4.網卡驅動發包過程

在內核中,當上層要發送一個數據包時, 就會調用網絡設備層里net_device數據結構的成員hard_start_xmit()將數據包發送出去。

hard_start_xmit()發包函數需要我們自己構建,該函數原型如下所示:

int    (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);

 

在這個函數中需要涉及到sk_buff結構體,含義為(socket buffer)套接字緩沖區,用來網絡各個層次之間傳遞數據.

4.1 sk_buff結構體是一個雙向鏈表,其中重要成員如下所示:

struct sk_buff {
       /* These two members must be first. */
       struct sk_buff        *next;      //指向下一個sk_buff結構體
       struct sk_buff        *prev;     //指向前一個sk_buff結構體
    ... ...
       unsigned int          len,         //數據包的總長度,包括線性數據和非線性數據
                            data_len,        //非線性的數據長度
                            mac_len;         //mac包頭長度

    __u32          priority;          //該sk_buff結構體的優先級   

    __be16        protocol;           //存放上層的協議類型,可以通過eth_type_trans()來獲取
       ... ...

      sk_buff_data_t              transport_header;    //傳輸層頭部的偏移值
      sk_buff_data_t              network_header;     //網絡層頭部的偏移值
      sk_buff_data_t              mac_header;          //MAC數據鏈路層頭部的偏移值

    sk_buff_data_t              tail;                    //指向緩沖區的數據包末尾
      sk_buff_data_t              end;                     //指向緩沖區的末尾
      unsigned char            *head,                   //指向緩沖區的協議頭開始位置
                                  *data;                   //指向緩沖區的數據包開始位置
       ... ...
}

 

其中sk_buff結構體的空間,如下圖所示:

 

其中sk_buff-> data數據包格式如下圖所示:

 

 

4.2 所以,hard_start_xmit()發包函數處理步驟如下所示:

  • 1)把數據包發出去之前,需要使用netif_stop_queue()來停止上層傳下來的數據包,
  • 2)設置寄存器,通過網絡設備硬件,來發送數據
  • 2)當數據包發出去后, 再調用dev_kfree_skb()函數來釋放sk_buff,該函數原型如下:
void dev_kfree_skb(struct sk_buff *skb);           

 

  • 3)當數據包發出成功,就會進入TX中斷函數,然后更新統計信息,調用netif_wake_queue()來喚醒,啟動上層繼續發包下來.
  • 4)若數據包發出去超時,一直進不到TX中斷函數,就會調用net_device結構體的(*tx_timeout)超時成員函數,在該函數中更新統計信息, 調用netif_wake_queue()來喚醒

 

其中netif_wake_queue()和netif_stop_queue()函數原型如下所示:

void netif_wake_queue(struct net_device *dev);  //喚醒被阻塞的上層,啟動繼續向網絡設備驅動層發送數據包

void netif_stop_queue(struct net_device *dev); //阻止上層向網絡設備驅動層發送數據包

 

 

 5.網卡驅動收包過程

而接收數據包主要是通過中斷函數處理,來判斷中斷類型,如果等於ISQ_RECEIVER_EVENT,表示為接收中斷,然后進入接收數據函數,通過netif_rx()將數據上交給上層

例如下圖所示,參考的內核中自帶的網卡驅動:/drivers/net/cs89x0.c

 

如上圖所示,通過獲取的status標志來判斷是什么中斷,如果是接收中斷,就進入net_rx()

4.1 其中net_rx()收包函數處理步驟如下所示:

  • 1)使用dev_alloc_skb()來構造一個新的sk_buff
  • 2)使用skb_reserve(rx_skb, 2); 將sk_buff緩沖區里的數據包先后位移2字節,來騰出sk_buff緩沖區里的頭部空間
  • 3)讀取網絡設備硬件上接收到的數據
  • 4)使用memcpy()將數據復制到新的sk_buff里的data成員指向的地址處,可以使用skb_put()來動態擴大sk_buff結構體里中的數據區
  • 5)使用eth_type_trans()來獲取上層協議,將返回值賦給sk_buff的protocol成員里
  • 6)然后更新統計信息,最后使用netif_rx( )來將sk_fuffer傳遞給上層協議中

其中skb_put()函數原型如下所示:

static inline unsigned char *skb_put(struct sk_buff *skb, unsigned int len); //len:將數據區向下擴大len字節

 

使用skb_put()函數后,其中sk_buff緩沖區變化如下圖:

 

 

6.寫虛擬網卡驅動

本節便開始來寫一個簡單的虛擬網卡驅動,也就是說不需要硬件相關操作,所以就沒有中斷函數,我們通過linux的ping命令來實現發包,然后在發包函數中偽造一個收的ping包函數,實現能ping通任何ip地址

在init初始函數中:

  • 1)使用alloc_netdev()來分配一個net_device結構體
  • 2)設置net_device結構體的成員
  • 3)使用register_netdev()來注冊net_device結構體

在發包函數中:

  • 1)使用netif_stop_queue()來阻止上層向網絡設備驅動層發送數據包
  • 2)調用收包函數,並代入發送的sk_buff緩沖區, 里面來偽造一個收的ping包函數
  • 3)使用dev_kfree_skb()函數來釋放發送的sk_buff緩存區
  • 4)更新發送的統計信息
  • 5)使用netif_wake_queue()來喚醒被阻塞的上層,

在收包函數中:

首先修改發送的sk_buff里數據包的數據,使它變為一個接收的sk_buff,其中數據包結構如下圖所示:

 

  • 1)需要對調上圖的ethhdr結構體 ”源/目的”MAC地址
  • 2)需要對調上圖的iphdr結構體”源/目的” IP地址
  • 3)使用ip_fast_csum()來重新獲取iphdr結構體的校驗碼
  • 4)設置上圖數據包的數據類型,之前是發送ping包0x08,需要改為0x00,表示接收ping包
  • 5)使用dev_alloc_skb()來構造一個新的sk_buff
  • 6)使用skb_reserve(rx_skb, 2);將sk_buff緩沖區里的數據包先后位移2字節,來騰出sk_buff緩沖區里的頭部空間
  • 7)使用memcpy()將之前修改好的sk_buff->data復制到新的sk_buff里的data成員指向的地址處:
memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len);
// skb_put():來動態擴大sk_buff結構體里中的數據區,避免溢出
  • 8)設置新的sk_buff 其它成員
  • 9)使用eth_type_trans()來獲取上層協議,將返回值賦給sk_buff的protocol成員里
  • 10)然后更新接收統計信息,最后使用netif_rx( )來將sk_fuffer傳遞給上層協議中

 

7.驅動具體代碼如下:

#include <linux/module.h>
#include <linux/errno.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/in.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/ip.h> 

#include <asm/system.h>
#include <asm/io.h>
#include <asm/irq.h>


static struct net_device    *virt_net;


static void virt_rs_packet(struct sk_buff *skb, struct net_device *dev)
{
       unsigned char *type;
       struct iphdr *ih;
       __be32 *saddr, *daddr, tmp;
       unsigned char tmp_dev_addr[ETH_ALEN];
       struct ethhdr *ethhdr;
       struct sk_buff *rx_skb;

    /*1) 對調ethhdr結構體 "源/目的"MAC地址*/
       ethhdr = (struct ethhdr *)skb->data;
       memcpy(tmp_dev_addr, ethhdr->h_dest, ETH_ALEN);
       memcpy(ethhdr->h_dest, ethhdr->h_source, ETH_ALEN);
       memcpy(ethhdr->h_source, tmp_dev_addr, ETH_ALEN);

/*2)對調 iphdr結構體"源/目的" IP地址*/ ih = (struct iphdr *)(skb->data + sizeof(struct ethhdr)); saddr = &ih->saddr; daddr = &ih->daddr;
tmp
= *saddr; *saddr = *daddr; *daddr = tmp; /*3)使用ip_fast_csum()來重新獲取iphdr結構體的校驗碼*/ ih->check = 0; ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl); /*4)設置數據類型*/ type = skb->data + sizeof(struct ethhdr) + sizeof(struct iphdr); *type = 0; //之前是發送ping包0x08,需要改為0x00,表示接收ping包 /*5)使用dev_alloc_skb()來構造一個新的sk_buff */ rx_skb = dev_alloc_skb(skb->len + 2); /*6)使用skb_reserve()來騰出2字節頭部空間 */ skb_reserve(rx_skb, 2); /*7)使用memcpy()將之前修改好的sk_buff->data復制到新的sk_buff里*/ memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len); // skb_put():來動態擴大sk_buff結構體里中的數據區,避免溢出 /*8)設置新的sk_buff 其它成員*/ rx_skb->dev = dev; rx_skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */ /*9)使用eth_type_trans()來獲取上層協議 */ rx_skb->protocol = eth_type_trans(rx_skb, dev); /*10) 更新接收統計信息,並使用netif_rx( )來 傳遞sk_fuffer收包 */ dev->stats.rx_packets++; dev->stats.rx_bytes += skb->len; dev->last_rx= jiffies; //收包時間戳 netif_rx(rx_skb); }
static int virt_send_packet(struct sk_buff *skb, struct net_device *dev) { /*1)使用netif_stop_queue()來阻止上層向網絡設備驅動層發送數據包*/ netif_stop_queue(dev); //期間設置硬件發送數據包 /*2)調用收包函數,里面來偽造一個收的ping包函數*/ virt_rs_packet(skb,dev); /*3)使用dev_kfree_skb()函數來釋放發送的sk_buff緩存區*/ dev_kfree_skb(skb); /*4)更新發送的統計信息*/ dev->stats.tx_packets++; //成功發送一個包 dev->stats.tx_bytes+=skb->len; //成功發送len長字節 dev->trans_start = jiffies; //發送時間戳 /*5)使用netif_wake_queue()來喚醒被阻塞的上層*/ netif_wake_queue(dev); return 0; } static int virt_net_init(void) { /*1)使用alloc_netdev()來分配一個net_device結構體*/ virt_net= alloc_netdev(sizeof(struct net_device), "virt_eth0", ether_setup); /*2)設置net_device結構體的成員 */ virt_net->hard_start_xmit = virt_send_packet; virt_net->dev_addr[0] = 0x08;     virt_net->dev_addr[1] = 0x89; virt_net->dev_addr[2] = 0x89; virt_net->dev_addr[3] = 0x89; virt_net->dev_addr[4] = 0x89; virt_net->dev_addr[5] = 0x89; virt_net->flags |= IFF_NOARP; virt_net->features |= NETIF_F_NO_CSUM; /*3)使用register_netdev()來注冊net_device結構體 */ register_netdev(virt_net);
return 0; } static void virt_net_exit(void) { unregister_netdev(virt_net); free_netdev(virt_net); } module_init(virt_net_init); module_exit(virt_net_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("by:zhang");

8.測試運行

掛載驅動,如下圖所示,可以看到net類下就有了這個網卡設備

 

開始試驗,首先設置這個網卡設備的ip,然后去ping一下其它的ip,如下圖所示:

 

上圖的ping,之所以成功,是因為我們在發包函數中,偽造了一個來收包,通過netif_rx()來將收包上傳給上層

使用ifconfig,可以看到這個網卡設備的統計信息共收發了6個包,以及收發的總數據

 

 

 

下節便開始學習網卡芯片DM9000C

如何編寫移植DM9000C網卡驅動程序: http://www.cnblogs.com/lifexy/p/7777961.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM