1.概述

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

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

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

2.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

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

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

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)设置寄存器,通过网络设备硬件,来发送数据

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()函数原型如下所示:

 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

 static irqreturn_t net_interrupt(int irq, void *dev_id)
{
struct net_device *dev = dev_id;
struct net_local *lp;
int ioaddr, status;
int handled = ; ioaddr = dev->base_addr;
lp = netdev_priv(dev); /* we MUST read all the events out of the ISQ, otherwise we'll never
get interrupted again. As a consequence, we can't have any limit
on the number of times we loop in the interrupt handler. The
hardware guarantees that eventually we'll run out of events. Of
course, if you're on a slow machine, and packets are arriving
faster than you can read them off, you're screwed. Hasta la
vista, baby! */
while ((status = readword(dev->base_addr, ISQ_PORT))) {
if (net_debug > )printk("%s: event=%04x\n", dev->name, status);
handled = ;
switch(status & ISQ_EVENT_MASK) {
case ISQ_RECEIVER_EVENT: /* 判断是否为接收中断 */
/* Got a packet(s). */
net_rx(dev); /* 进入net_rx(dev)函数,将接收的数据交给上层 */
break;
case ISQ_TRANSMITTER_EVENT: /* 判断是否为发送中断 */
lp->stats.tx_packets++;
netif_wake_queue(dev); /* Inform upper layers. */
if ((status & ( TX_OK |
TX_LOST_CRS |
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缓冲区变化如下图:

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

  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-网卡驱动介绍以及制作虚拟网卡驱动(详解)

Linux网卡驱动框架及制作虚拟网卡的更多相关文章

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

    1.描述 网卡的驱动其实很简单,它还是与硬件相关,主要是负责收发网络的数据包,它将上层协议传递下来的数据包以特定的媒介访问控制方式进行发送, 并将接收到的数据包传递给上层协议. 网卡设备与字符设备和块 ...

  2. Linux设备驱动框架设计

    引子 Linux操作系统的一大优势就是支持数以万计的芯片设备,大大小小的芯片厂商工程师都在积极地向Linux kernel提交设备驱动代码.能让这个目标得以实现,这背后隐藏着一个看不见的技术优势:Li ...

  3. Linux Framebuffer 驱动框架之一概念介绍及LCD硬件原理【转】

    本文转载自:http://blog.csdn.net/liuxd3000/article/details/17464779 一.基本概念 帧缓冲(Framebuffer)是Linux系统为显示设备提供 ...

  4. 【原创】Linux PCI驱动框架分析(三)

    背 景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本 ...

  5. 更新新网卡驱动,修复win7雷凌网卡Ralink RT3290在电脑睡眠时和启动网卡时出现蓝屏netr28x.sys驱动文件错误

    更新新网卡驱动,修复win7雷凌网卡Ralink RT3290在电脑睡眠时和启动网卡时出现蓝屏netr28x.sys驱动文件错误 我的本本是win7,雷凌网卡Ralink RT3290   802.1 ...

  6. Linux USB驱动框架分析 【转】

    转自:http://blog.chinaunix.net/uid-11848011-id-96188.html 初次接触与OS相关的设备驱动编写,感觉还挺有意思的,为了不至于忘掉看过的东西,笔记跟总结 ...

  7. linux驱动基础系列--linux spi驱动框架分析

    前言 主要是想对Linux 下spi驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.设备模型等也不进行详细说明原理.如果有任何错误地方,请指出,谢谢! spi ...

  8. linux驱动基础系列--linux spi驱动框架分析(续)

    前言 这篇文章是对linux驱动基础系列--linux spi驱动框架分析的补充,主要是添加了最新的linux内核里设备树相关内容. spi设备树相关信息 如之前的文章里所述,控制器的device和s ...

  9. Linux USB驱动框架分析【转】

    转自:http://blog.csdn.net/jeffade/article/details/7701431 Linux USB驱动框架分析(一) 初次接触和OS相关的设备驱动编写,感觉还挺有意思的 ...

随机推荐

  1. GoldenGate V11.1数据复制限制

    以下对goldengate数据复制的限制情况进行说明. 不支持文件等非结构化数据复制 GoldenGate依赖对于数据库日志的解析获取数据变化,因此只能支持数据库中的数据变化复制,无法支持文件等非结构 ...

  2. 逻辑学总结x

    逻辑学是研究事实联系: 肯定.否定: 条件 结论: 联系  规则: 的学问.

  3. 洛谷1440 求m区间的最小值 单调队列

    题目描述 一个含有n项的数列(n<=2000000),求出每一项前的m个数到它这个区间内的最小值.若前面的数不足m项则从第1个数开始,若前面没有数则输出0. 输入格式: 第一行两个数n,m. 第 ...

  4. Java实现断点续传。

    http://www.cnblogs.com/liaojie970/p/5013790.html

  5. 关于Windows7下创建Cocos2D-X项目的小问题

    "新版的Cocos2D-X"已经不支持用上述脚本来创建工程了,而是改为用create-project.py来创建...命令格式: python create-project.py ...

  6. Codeforces Round #313 C. Gerald&#39;s Hexagon(放三角形)

    C. Gerald's Hexagon time limit per test 2 seconds memory limit per test 256 megabytes input standard ...

  7. iOS App 上架流程

                                                             iPhone App 上架流程 1.  申请 App ID (1)  连到 Devel ...

  8. ios svn repository

    xcode默认自带Git和svn,首先讲下xcode4.6.3下配置svn: 1.检測你的mac中是否安装了svn: (1) 打开终端,输入 svn --version 假设出现下图信息,则说明已经安 ...

  9. BZOJ1306: [CQOI2009]match循环赛

    [传送门:BZOJ1306] 简要题意: 有n个队伍,每个队伍都要和其他队伍比一场,赢了的队得3分,输了的队不得分,打平两队各得一分,给出每个队伍的得分,求出对战方案数 题解: DFS暴搜!!一眼就觉 ...

  10. Gallery滑动一页(一个Item)效果

    本文主要介绍如何使用Gallery只滑动一页以及其实现原理. Demo APK 可以方便的查看效果,在各大应用商店搜索 trinea android 下载即可,如:Google Play. 可运行代码 ...