Linux网卡驱动框架及制作虚拟网卡

时间:2022-07-12 18:52:26

1.概述

网卡驱动与硬件相关,主要负责收发网络的数据包,将上层协议传递下来的数据包以特定的媒介访问控制方式进行发送,并将接收到的数据包传递给上层协议。

网卡设备与字符设备,块设备不同,网络设备驱动程序不依赖与 /dev 或 /sys 来与用户空间通信,应用程序是通过网络接口(如作为第一个网络接口的eth0)与网卡驱动程序互相操作的。

网卡设备存放在 /sys/class/net 目录下

 

2.Linux 系统网络协议的处理框架

网络设备驱动的框架层次分为四层:

Linux网卡驱动框架及制作虚拟网卡

1)网络设备与媒介层:

用来负责完成数据包发送和接收的物理实体, 设备驱动功能层的函数都在这物理上驱动的,即DM9000网络处理芯片。

2)设备驱动功能层:

用来负责驱动网络设备硬件来完成各个功能, 如通过hard_start_xmit() 函数启动发送操作, 并通过网络设备上的中断触发接收操作,即DM9000网卡驱动,实现文件在 linux/driver/net

3)网络设备接口层:

是整个网络接口的关键部位,它为网络协议提供统一的发送接口,屏蔽各种物理介质,同时又负责把来自下层的包向合适的协议配送。

通过net_device结构体来描述一个具体的网络设备的信息,实现不同的硬件的统一。实现文件在linux/net/core 下,其中dev.c 为主要实现文件。

4)网络协议接口层:

此部分实现了各种具体协议,Linux支持 TCP/IP,  IPX, X.25, AppleTalk 等协议,实现源码在Linux / net 目录下有对应名称。

此处主要讨论 TCP/IP (IPv4) 协议,实现源码在 linux/net/ipv4 下,其中 af_inet.c为主要实现文件。

实现统一的数据包收发的协议,该层主要负责调用dev_queue_xmit()函数发送数据, netif_rx()函数接收数据

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

在网络协议接口层之上还有一层为 网络接口socket层, 主要为用户提供网络服务的编程接口,方便用户进行网络应用程序开发,源码在 linux/net/socket.c

Linux网卡驱动框架及制作虚拟网卡

 

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

了解网络设备驱动的框架层次后,下面来分析网卡驱动的初始化,数据发送和接收的处理流程。

 

3.网卡驱动初始化

 此次的网卡驱动程序,只需要编写网络设备接口层,填充net_device数据结构的内容并将net_device注册入内核,设置硬件相关操作,使能中断处理等

3.1其中net_device结构体的重要成员:

 1 struct net_device
 2 {
 3     char             name[IFNAMSIZ];   //网卡设备名称
 4     unsigned long    mem_end;          //该设备的内存结束地址
 5     unsigned long    mem_start;        //该设备的内存起始地址
 6     unsigned long    base_addr;        //该设备的内存I/O基地址
 7     unsigned int     irq;              //该设备的中断号
 8     unsigned char    if_port;          //多端口设备使用的端口类型
 9    unsigned char    dma;              //该设备的DMA通道
10     unsigned long    state;            //网络设备和网络适配器的状态信息
11     ......
12     struct net_device_stats* (*get_stats)(struct net_device *dev); //获取流量的统计信息
13   //运行ifconfig便会调用该成员函数,并返回一个net_device_stats结构体获取信息
14 
15     struct net_device_stats  stats;     //用来保存统计信息的net_device_stats结构体
16 
17  
18     unsigned long   features;            //接口特征,     
19     unsigned int    flags;                 //flags指网络接口标志,以IFF_(Interface Flags)开头
20     //当flags =IFF_UP( 当设备被激活并可以开始发送数据包时, 内核设置该标志)、 IFF_AUTOMEDIA(设置设备可在多种媒介间切换)、
21     //IFF_BROADCAST( 允许广播)、IFF_DEBUG( 调试模式, 可用于控制printk调用的详细程度) 、 IFF_LOOPBACK( 回环)、
22     //IFF_MULTICAST( 允许组播) 、 IFF_NOARP( 接口不能执行ARP,点对点接口就不需要运行 ARP) 和IFF_POINTOPOINT( 接口连接到点到点链路) 等。
23 
24     unsigned        mtu;        //最大传输单元,也叫最大数据包
25 
26     unsigned short  type;     //接口的硬件类型
27 
28     unsigned short  hard_header_len;         //硬件帧头长度,一般被赋为ETH_HLEN,即14
29 
30   unsigned char   dev_addr[MAX_ADDR_LEN];   //存放设备的MAC地址
31 
32     unsigned long   last_rx;    //接收数据包的时间戳,调用netif_rx()后赋上jiffies即可
33 
34     unsigned long   trans_start; //发送数据包的时间戳,当要发送的时候赋上jiffies即可
35 
36     unsigned char   dev_addr[MAX_ADDR_LEN];    //MAC地址
37  
38     int  (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);
39      //数据包发送函数, sk_buff就是用来收发数据包的结构体
40 
41 
42   void  (*tx_timeout) (struct net_device *dev); //发包超时处理函数
43   ... ...
44 }

上面的统计信息net_device_stats结构体,其中重要成员如下所示:

 1 struct net_device_stats
 2 {
 3        unsigned long       rx_packets;            /*收到的数据包数*/
 4        unsigned long       tx_packets;            /*发送的数据包数    */
 5        unsigned long       rx_bytes;               /*收到的字节数,可以通过sk_buff结构体的成员len来获取*/
 6        unsigned long       tx_bytes;               /*发送的字节数,可以通过sk_buff结构体的成员len来获取*/
 7        unsigned long       rx_errors;              /*收到的错误数据包数*/
 8        unsigned long       tx_errors;              /*发送的错误数据包数*/
 9        ... ...
10 }

 

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结构体是一个双向链表,其中重要成员如下所示:

 1 struct sk_buff {
 2        /* These two members must be first. */
 3        struct sk_buff        *next;     //指向下一个sk_buff结构体
 4        struct sk_buff        *prev;     //指向前一个sk_buff结构体
 5     ... ...
 6        unsigned int         len,        //数据包的总长度,包括线性数据和非线性数据
 7                             data_len,      //非线性的数据长度
 8                             mac_len;       //mac包头长度
 9 
10     __u32         priority;           //该sk_buff结构体的优先级   
12     __be16        protocol;           //存放上层的协议类型,可以通过eth_type_trans()来获取
13        ... ...
14 
15       sk_buff_data_t              transport_header;    //传输层头部的偏移值
16       sk_buff_data_t              network_header;     //网络层头部的偏移值
17       sk_buff_data_t              mac_header;          //MAC数据链路层头部的偏移值
18 
19     sk_buff_data_t              tail;                    //指向缓冲区的数据包末尾
20       sk_buff_data_t              end;                     //指向缓冲区的末尾
21       unsigned char            *head,                   //指向缓冲区的协议头开始位置
22                                   *data;                   //指向缓冲区的数据包开始位置
23        ... ...
24 }

其中sk_buff结构体的空间,如下图所示:

Linux网卡驱动框架及制作虚拟网卡

其中sk_buff-> data数据包格式如下图所示:

Linux网卡驱动框架及制作虚拟网卡

4.2 所以,hard_start_xmit()发包函数处理步骤如下所示:

   1)把数据包发出去之前,需要使用netif_stop_queue()来停止上层传下来的数据包,

   2)设置寄存器,通过网络设备硬件,来发送数据

   3)当数据包发出去后, 再调用dev_kfree_skb()函数来释放sk_buff,该函数原型如下:

void dev_kfree_skb(struct sk_buff *skb);           

 4)当数据包发出成功,就会进入TX中断函数,然后更新统计信息,调用netif_wake_queue()来唤醒,启动上层继续发包下来.

 5)若数据包发出去超时,一直进不到TX中断函数,就会调用net_device结构体的(*tx_timeout)超时成员函数,在该函数中更新统计信息, 调用netif_wake_queue()来唤醒

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

1 void netif_wake_queue(struct net_device *dev);  //唤醒被阻塞的上层,启动继续向网络设备驱动层发送数据包
2 
3 void netif_stop_queue(struct net_device *dev); //阻止上层向网络设备驱动层发送数据包

 

5.网卡驱动收包过程

接收数据包主要是通过中断函数处理,来判断中断类型,如果等于 ISQ_RECEIVER_EVENT, 表示为接收中断,然后进入接收数据函数,通过 netif_rx() 将数据上交给上层

例如下图所示,参考的内核中自带的网卡驱动: /drivers/net/cs89x0.c

 1 static irqreturn_t net_interrupt(int irq, void *dev_id)
 2 {
 3     struct net_device *dev = dev_id;
 4     struct net_local *lp;
 5     int ioaddr, status;
 6      int handled = 0;
 7 
 8     ioaddr = dev->base_addr;
 9     lp = netdev_priv(dev);
10 
11     /* we MUST read all the events out of the ISQ, otherwise we'll never
12            get interrupted again.  As a consequence, we can't have any limit
13            on the number of times we loop in the interrupt handler.  The
14            hardware guarantees that eventually we'll run out of events.  Of
15            course, if you're on a slow machine, and packets are arriving
16            faster than you can read them off, you're screwed.  Hasta la
17            vista, baby!  */
18     while ((status = readword(dev->base_addr, ISQ_PORT))) {
19         if (net_debug > 4)printk("%s: event=%04x\n", dev->name, status);
20         handled = 1;
21         switch(status & ISQ_EVENT_MASK) {
22         case ISQ_RECEIVER_EVENT:     /* 判断是否为接收中断 */
23             /* Got a packet(s). */
24             net_rx(dev);  /* 进入net_rx(dev)函数,将接收的数据交给上层 */
25             break;
26         case ISQ_TRANSMITTER_EVENT:  /* 判断是否为发送中断 */
27             lp->stats.tx_packets++;
28             netif_wake_queue(dev);    /* Inform upper layers. */
29             if ((status & (    TX_OK |
30                     TX_LOST_CRS |
31                     TX_SQE_ERROR |
.......................................................

如上图所示,通过获取的status标志来判断是什么中断,如果是接收中断,就进入net_rx()

5.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缓冲区变化如下图:

Linux网卡驱动框架及制作虚拟网卡

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,其中数据包结构如下图所示:

 Linux网卡驱动框架及制作虚拟网卡

  • 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.驱动具体代码如下:

Linux网卡驱动框架及制作虚拟网卡Linux网卡驱动框架及制作虚拟网卡
  1 /*
  2  *参考linux-2.6.22.6\drivers\net\Cs89x0.c
  3  */
  4 
  5 #include <linux/module.h>
  6 #include <linux/errno.h>
  7 #include <linux/netdevice.h>
  8 #include <linux/etherdevice.h>
  9 #include <linux/kernel.h>
 10 #include <linux/types.h>
 11 #include <linux/fcntl.h>
 12 #include <linux/interrupt.h>
 13 #include <linux/ioport.h>
 14 #include <linux/in.h>
 15 #include <linux/skbuff.h>
 16 #include <linux/slab.h>
 17 #include <linux/spinlock.h>
 18 #include <linux/string.h>
 19 #include <linux/init.h>
 20 #include <linux/bitops.h>
 21 #include <linux/delay.h>
 22 #include <linux/ip.h>
 23 
 24 
 25 #include <asm/system.h>
 26 #include <asm/io.h>
 27 #include <asm/irq.h>
 28 
 29 
 30 static struct net_device *vnet_dev;
 31 
 32 static void emulator_rx_packet(struct sk_buff *skb, struct net_device *dev)
 33 {
 34     /* 参考LDD3 */
 35     unsigned char *type;
 36     struct iphdr *ih;
 37     __be32 *saddr, *daddr, tmp;
 38     unsigned char    tmp_dev_addr[ETH_ALEN];
 39     struct ethhdr *ethhdr;
 40     
 41     struct sk_buff *rx_skb;
 42         
 43     // 从硬件读出/保存数据
 44     /* 对调"源/目的"的mac地址 */
 45     ethhdr = (struct ethhdr *)skb->data;
 46     memcpy(tmp_dev_addr, ethhdr->h_dest, ETH_ALEN);
 47     memcpy(ethhdr->h_dest, ethhdr->h_source, ETH_ALEN);
 48     memcpy(ethhdr->h_source, tmp_dev_addr, ETH_ALEN);
 49 
 50     /* 对调"源/目的"的ip地址 */    
 51     ih = (struct iphdr *)(skb->data + sizeof(struct ethhdr));
 52     saddr = &ih->saddr;
 53     daddr = &ih->daddr;
 54 
 55     tmp = *saddr;
 56     *saddr = *daddr;
 57     *daddr = tmp;
 58     
 59     //((u8 *)saddr)[2] ^= 1; /* change the third octet (class C) */
 60     //((u8 *)daddr)[2] ^= 1;
 61     type = skb->data + sizeof(struct ethhdr) + sizeof(struct iphdr);
 62     //printk("tx package type = %02x\n", *type);
 63     // 修改类型, 原来0x8表示ping
 64     *type = 0; /* 0表示reply */
 65     
 66     ih->check = 0;           /* and rebuild the checksum (ip needs it) */
 67     ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl);
 68     
 69     // 构造一个sk_buff
 70     rx_skb = dev_alloc_skb(skb->len + 2);
 71     skb_reserve(rx_skb, 2); /* align IP on 16B boundary */    
 72     memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len);
 73 
 74     /* Write metadata, and then pass to the receive level */
 75     rx_skb->dev = dev;
 76     rx_skb->protocol = eth_type_trans(rx_skb, dev);
 77     rx_skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
 78     dev->stats.rx_packets++;
 79     dev->stats.rx_bytes += skb->len;
 80 
 81     // 提交sk_buff
 82     netif_rx(rx_skb);
 83 }
 84 
 85 static int    virt_net_send_packet(struct sk_buff *skb, struct net_device *dev)
 86 {
 87     static int cnt = 0; 
 88     printk("virt_net_send_packet = %d\n", ++cnt);
 89 
 90     /*对于真实的网卡,会把skb里的数据发送出去*/
 91     netif_stop_queue(dev);  /* 停止该网卡的队列 */
 92     /* -------------- */    /* 把skb的数据写入网卡 */
 93 
 94     /* 构造一个假的sk_buff,上报 */
 95     emulator_rx_packet(skb, dev);
 96     
 97     dev_kfree_skb (skb);    /* 释放skb */
 98     netif_wake_queue(dev);    /* 数据全部发送出去后,中断唤醒队列 */
 99     /*更新统计信息*/
100     dev->stats.tx_packets++;
101     dev->stats.tx_bytes += skb->len;
102 
103     /*构造一个假的sk_buff上报*/
104     emulator_rx_packet(skb, dev);    
105     
106     return 0;
107 }
108 
109 
110 static int  virt_net_init(void)
111 {
112     /* 1.分配一个net_device结构体 */
113     vnet_dev = alloc_netdev(0, "vnet%d", ether_setup);  /*alloc_etherdev*/
114     
115     /* 2.设置 */
116     vnet_dev->hard_start_xmit = virt_net_send_packet;
117 
118     vnet_dev->dev_addr[0] = 0x08;
119     vnet_dev->dev_addr[1] = 0x08;
120     vnet_dev->dev_addr[2] = 0x89;
121     vnet_dev->dev_addr[3] = 0x08;
122     vnet_dev->dev_addr[4] = 0x08;
123     vnet_dev->dev_addr[5] = 0x11;
124 
125     /* 设置一下两项才能ping通 */
126     vnet_dev->flags           |= IFF_NOARP;
127     vnet_dev->features        |= NETIF_F_NO_CSUM;    
128 
129     /* 3.注册 */
130     register_netdev(vnet_dev);
131     
132     return 0;
133 }
134 
135 static void  virt_net_exit(void)
136 {
137     unregister_netdev(vnet_dev);
138     free_netdev(vnet_dev);
139 
140 }
141 
142 module_init(virt_net_init);
143 module_exit(virt_net_exit);
144 MODULE_LICENSE("GPL");
virt_net.c

 

以上内容基本摘自:

26.Linux-网卡驱动介绍以及制作虚拟网卡驱动(详解)