《Linux驱动:网络设备驱动》

一,前言

前面学习分析过字符设备驱动、块设备驱动的基本框架和构建流程。本文接着学习网络设备驱动,网络设备面向数据的接收和发送设计,它不对应文件系统的设备节点。内核和网络设备的通信与内核和字符设备、块设备的通信方式完全不同,网络设备主要还是使用套接字接口。在这里引入网络设备驱动的基本结构,以及构建一个网络设备驱动的基本流程。

二,网络设备驱动的结构

在构建具体的网络设备驱动程序时,我们需要完成的主要工作是编写设备驱动功能层中的相关函数以填充net_device数据结构注册到内核。
在这里插入图片描述

2.1 网络协议接口层

向网络层协议提供统一的数据包收发接口,不论上层协议是ARP还是IP都通过dev_queue_xmit()函数发送数据,并通过netif_rx()函数接收数据。这一层的存在使得上层协议独立于具体的设备。

2.2 网络设备接口层

向协议接口层提供统一的用于描述具体网络设备属性和操作的结构体net_device,该结构体是设备驱动功能层中各函数的容器。

2.3 设备驱动功能层

该层设计的各函数接口是网络设备接口层net_device数据结构的具体成员,是驱使网络设备硬件完成相应动作的程序,它通过hard_start_xmit() 函数启动发送操作,并通过网络设备上的中断触发接收操作。

2.4 网络设备与媒介层

完成数据包发送和接收的物理实体,包括网络适配器和具体的传输媒介,网络适配器被设备驱动功能层中的各函数在物理上驱动。

三,构建虚拟网卡驱动

向内核注册一个虚拟网卡设备,手动给该虚拟设备设置IP后,可以随意ping通一个同网段的ip。


/*
 * 参考 drivers\net\cs89x0.c
 */

#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 *vnet_dev;

static void emulator_rx_packet(struct sk_buff *skb, struct net_device *dev)
{
    
    
	/* 参考LDD3 */
	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;
		
	/*
	这里是使用了发送出去的sk_buff构建假的接收到的数据,即原来的是源mac发送给目的mac,现在调换一下
	就是原先的源mac变成目的mac,目的mac变成源mac,下面的IP也要调换
	*/
	// 从硬件读出/保存数据
	/* 
	对调"源/目的"的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);

	/* 对调"源/目的"的ip地址 */    
	ih = (struct iphdr *)(skb->data + sizeof(struct ethhdr));
	saddr = &ih->saddr;
	daddr = &ih->daddr;

	tmp = *saddr;
	*saddr = *daddr;
	*daddr = tmp;
	
	//((u8 *)saddr)[2] ^= 1; /* change the third octet (class C) */
	//((u8 *)daddr)[2] ^= 1;
	type = skb->data + sizeof(struct ethhdr) + sizeof(struct iphdr);
	//printk("tx package type = %02x\n", *type);
	// 修改类型, 原来0x8表示ping
	*type = 0; /* 0表示reply */
	
	ih->check = 0;		   /* and rebuild the checksum (ip needs it) */
	ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl);
	
	// 构造一个sk_buff
	rx_skb = dev_alloc_skb(skb->len + 2);
	skb_reserve(rx_skb, 2); /* align IP on 16B boundary */	
	memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len);

	/* Write metadata, and then pass to the receive level */
	rx_skb->dev = dev;
	rx_skb->protocol = eth_type_trans(rx_skb, dev);
	rx_skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
	dev->stats.rx_packets++;
	dev->stats.rx_bytes += skb->len;

	// 提交sk_buff
	netif_rx(rx_skb);
}

static int virt_net_send_packet(struct sk_buff *skb, struct net_device *dev)
{
    
    
	static int cnt = 0;
	printk("virt_net_send_packet cnt = %d\n", ++cnt);

	/* 对于真实的网卡, 把skb里的数据通过网卡发送出去 */
	netif_stop_queue(dev); /* 停止该网卡的队列 */
    /* ...... */           /* 把skb的数据写入网卡 */

	/*  
	由于这里是一个假的网络设备,没有收包能力,直接在这里构造一个假的sk_buff,上报。
	真实的网络设备驱动应该在一个网卡中断中,接收并处理数据后,再发送给上层处理。
	*/
	emulator_rx_packet(skb, dev);

	dev_kfree_skb (skb);   /* 释放skb */
	netif_wake_queue(dev); /* 数据全部发送出去后,唤醒网卡的队列 */

	/* 更新统计信息 */
	dev->stats.tx_packets++;
	dev->stats.tx_bytes += skb->len;
	
	return 0;
}


static int virt_net_init(void)
{
    
    
	/* 1. 分配一个net_device结构体 */
	vnet_dev = alloc_netdev(0, "vnet%d", ether_setup);;  /* alloc_etherdev */

	/* 2. 设置 */
	vnet_dev->hard_start_xmit = virt_net_send_packet;

	/* 设置MAC地址 */
    vnet_dev->dev_addr[0] = 0x08;
    vnet_dev->dev_addr[1] = 0x89;
    vnet_dev->dev_addr[2] = 0x89;
    vnet_dev->dev_addr[3] = 0x89;
    vnet_dev->dev_addr[4] = 0x89;
    vnet_dev->dev_addr[5] = 0x11;

    /* 设置下面两项才能ping通 */
	// 因为在真实的环境中,ping通其他的网络设备前需要先通过arp协议获取其MAC地址。
	// 而现在ping的设备是不存在的,所以通过设置该参数,默认对方存在,直接发送出数据
	// 即直接调用hard_start_xmit接口。
	vnet_dev->flags           |= IFF_NOARP;  
	vnet_dev->features        |= NETIF_F_NO_CSUM;	

	/* 3. 注册 */
	//register_netdevice(vnet_dev);
	register_netdev(vnet_dev);
	
	return 0;
}

static void virt_net_exit(void)
{
    
    
	unregister_netdev(vnet_dev);
	free_netdev(vnet_dev);
}

module_init(virt_net_init);
module_exit(virt_net_exit);

MODULE_LICENSE("GPL");


四,总结

4.1 数据传输过程

发包:网络协议层调用网络协议接口层dev_queue_xmit( )接口发包;设备驱动功能层给网络设备接口层的net_device结构体的hard_start_xmit成员赋值,即提供发包函数;dev_queue_xmit( )函数调用该发包函数将数据通过网卡传输给其他网络设备。
收包:一般是在设备驱动功能层中,创建一个中断,在中断中接收网卡传输过来的数据,然后通过网络协议接口层接口netif_rx发送给网络协议层处理->传输层处理->应用层处理。

4.2 构建网络设备驱动程序的一般流程(设备驱动功能层)

  • 调用alloc_netdev接口申请分配一个net_device结构体。
  • 设置net_device结构体,至少提供一个发包函数。
  • 根据硬件电路申请一个网卡相关中断,在中断中接收网卡接收到的其他设备发送过来的数据,通过netif_rx函数发送给上层处理。
  • 调用register_netdev接口注册该驱动。

猜你喜欢

转载自blog.csdn.net/qq_40709487/article/details/127141333