网卡驱动程序
一、网卡驱动程序框架
相关驱动特点
字符设备驱动
设置主设备号
填充file_operations结构体
用register_chrdev(主设备号,name,file_operations结构体)注册驱动
入口函数
出口函数
块符设备驱动
分配geadisk结构体(利用alloc_disk函数)
设置
构造队列queue=blk_init_queue(处理队列的函数)
其他属性(主设备号、容量)
注册geadisk结构体(利用add_disk函数)
上述字符设备驱动和块符设备驱动的共同特点:
都有设备节点
字符设备需要先open、块设备需要先格式化然后挂接(mount)
网卡驱动
不需要打开某设备,直接使用socket即可编程
网卡的驱动与硬件相关,主要是负责收发网络的数据包,它将上层协议传递下来的数据包以特定的媒介访问控制方式进行发送, 并将接收到的数据包传递给上层协议。
网卡设备与字符设备和块设备不同, 网络设备并不对应于**/dev目录下的文件,不过会存放在 /sys/class/net**目录下
Linux网络设备驱动的4层模型
上图就是经典的OSI 7层模型,Linux的网卡驱动程序处于OSI模型中的数据链路层,他的职责就是将上上层的协议栈传过来的信息通过网卡发送出去,Linux的网络驱动模型采用4层结构:
1)网络协议接口层:
实现统一的数据包收发的协议,该层主要负责调用dev_queue_xmit()函数发送数据,netif_rx()函数接收数据
2)网络设备接口层:
通过net_device结构体来描述一个具体的网络设备的信息,实现不同的硬件的统一
3)设备驱动功能层:
用来负责驱动网络设备硬件来完成各个功能, 它通过hard_start_xmit() 函数启动发送操作, 并通过网络设备上的中断触发接收操作,
4)网络设备与媒介层:
用来负责完成数据包发送和接收的物理实体, 设备驱动功能层的函数都在这物理上驱动的
网卡驱动初始化
我们的网卡驱动程序,只需要编写网络设备接口层,填充net_device数据结构的内容并将net_device注册入内核,设置硬件相关操作,使能中断处理等
net_device结构体的重要成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 struct net_device { char name[IFNAMSIZ]; unsigned long mem_end; unsigned long mem_start; unsigned long base_addr; unsigned int irq; unsigned char if_port; unsigned char dma; unsigned long state; struct net_device_stats * (*get_stats )(struct net_device *dev ); struct net_device_stats stats ; unsigned long features; unsigned int flags; unsigned mtu; unsigned short type; unsigned short hard_header_len; unsigned char dev_addr[MAX_ADDR_LEN]; unsigned long last_rx; unsigned long trans_start; unsigned char dev_addr[MAX_ADDR_LEN]; int (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev); void (*tx_timeout) (struct net_device *dev); ... ... }
上面讲到的统计信息net_device_stats结构体,其中重要成员如下所示:
1 2 3 4 5 6 7 8 9 10 struct net_device_stats { unsigned long rx_packets; unsigned long tx_packets; unsigned long rx_bytes; unsigned long tx_bytes; unsigned long rx_errors; unsigned long tx_errors; ... ... }
初始化网卡步骤
1)使用alloc_netdev()来分配一个net_device结构体
2)设置网卡硬件相关的寄存器
3)设置net_device结构体的成员
4)使用register_netdev()来注册net_device结构体
网卡驱动发包过程
在内核中,当上层要发送一个数据包时, 就会调用网络设备层里net_device数据结构的成员**hard_start_xmit()**将数据包发送出去。
**hard_start_xmit()**发包函数需要我们自己构建,该函数原型如下所示:
1 int (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);
在这个函数中需要涉及到sk_buff结构体,即套接字缓冲区(socket buffer),用来网络各个层次之间传递数据。
sk_buff结构体及其重要成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 struct sk_buff { struct sk_buff *next ; struct sk_buff *prev ; ... ... unsigned int len, data_len, mac_len; __u32 priority; __be16 protocol; ... ... sk_buff_data_t transport_header; sk_buff_data_t network_header; sk_buff_data_t mac_header; sk_buff_data_t tail; sk_buff_data_t end; unsigned char *head, *data; ... ... }
可以看出该结构体为一个双向链表
其中sk_buff结构体的空间,如下图所示:
其中sk_buff-> data数据包格式如下图所示:
即:
hard_start_xmit()发包的处理步骤
1)把数据包发出去之前,需要使用**netif_stop_queue()**来停止上层传下来的数据包,
2)设置寄存器,通过网络设备硬件,来发送数据
3)当数据包发出去后, 再调用**dev_kfree_skb()**函数来释放sk_buff,该函数原型如下:
1 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) ;
1 void netif_stop_queue (struct net_device *dev) ;
网卡驱动收包过程
而接收数据包主要是通过中断函数处理,来判断中断类型,如果等于ISQ_RECEIVER_EVENT ,表示为接收中断,然后进入接收数据函数,通过**netif_rx()**将数据上交给上层。
如下所示,参考的内核中自带的网卡驱动:/drivers/net/cs89x0.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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 = 0 ; ioaddr = dev->base_addr; lp = netdev_priv(dev); while ((status = readword(dev->base_addr, ISQ_PORT))) { if (net_debug > 4 )printk("%s: event=%04x\n" , dev->name, status); handled = 1 ; switch (status & ISQ_EVENT_MASK) { case ISQ_RECEIVER_EVENT: net_rx(dev); break ; case ISQ_TRANSMITTER_EVENT: ......
该函数就是通过获取的status标志来判断是什么中断,如果是接收中断,就进入net_rx()
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()**函数原型如下所示:
1 2 static inline unsigned char *skb_put (struct sk_buff *skb, unsigned int len) ;
使用skb_put()函数后,其中sk_buff缓冲区变化如下图:
二、编写虚拟网卡驱动
这里写一个简单的虚拟网卡驱动,也就是说不需要硬件相关操作,所以就没有中断函数,我们通过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()**来唤醒被阻塞的上层,
在收包函数中:
该部分可以参考LDD3(linux device drivers,third edition)
首先修改发送的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成员指向的地址处:
1 memcpy (skb_put(rx_skb, skb->len), skb->data, skb->len);
8)设置新的sk_buff 其它成员
9)使用**eth_type_trans()**来获取上层协议,将返回值赋给sk_buff的protocol成员里
10)然后更新接收统计信息,最后使用**netif_rx()**来将sk_fuffer传递给上层协议中
驱动具体代码分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 #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 ; 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); ih = (struct iphdr *)(skb->data + sizeof (struct ethhdr)); saddr = &ih->saddr; daddr = &ih->daddr; tmp = *saddr; *saddr = *daddr; *daddr = tmp; ih->check = 0 ; ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl); type = skb->data + sizeof (struct ethhdr) + sizeof (struct iphdr); *type = 0 ; rx_skb = dev_alloc_skb(skb->len + 2 ); skb_reserve(rx_skb, 2 ); memcpy (skb_put(rx_skb, skb->len), skb->data, skb->len); rx_skb->dev = dev; rx_skb->ip_summed = CHECKSUM_UNNECESSARY; rx_skb->protocol = eth_type_trans(rx_skb, dev); 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) { netif_stop_queue(dev); virt_rs_packet(skb,dev); dev_kfree_skb(skb); dev->stats.tx_packets++; dev->stats.tx_bytes+=skb->len; dev->trans_start = jiffies; netif_wake_queue(dev); return 0 ; } static int virt_net_init (void ) { virt_net= alloc_netdev(sizeof (struct net_device), "virt_eth0" , ether_setup); 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; 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:NU-LL" );
三、测试运行
挂载驱动,如下图所示,可以看到net类下就有了这个网卡设备
开始测试,首先设置这个网卡设备的ip,然后去ping一下其它的ip,如下图所示:
上图的ping,之所以成功,是因为我们在发包函数中,伪造了一个来收包,通过netif_rx()来将收包上传给上层
使用ifconfig,可以看到这个网卡设备的统计信息共收发了6个包,以及收发的总数据