11.网络设备驱动

网络设备驱动概述

与字符设备和块设备不同,网络设备并不对应于/dev目录下的文件,应用程序最终使用套接字完成与网络设备的接口。
因而在网络设备身上并不能体现出“一切都是文件”的思想。

网络设备驱动4个层次:

  1. 网络协议接口层
  2. 网络设备接口层
  3. 提供实际功能的设备驱动功能层
  4. 网络设备与媒介层

 

1 Linux网络设备驱动的结构   ##########

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


2)网络设备接口层向协议接口层提供统一的用于描述具体网络设备属性和操作的结构体net_device,该结构体是设备驱动功能层中各函数的容器。实际上,网络设备接口层从宏观上规划了具体操作硬件的设备驱动功能层的结构。


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


4)网络设备与媒介层是完成数据包发送和接收的物理实体,包括网络适配器和具体的传输媒介,网络适配器被设备驱动功能层中的函数在物理上驱动。对于Linux系统而言,网络设备和媒介都可以是虚拟的。
 

1.1 网络协议接口层   @@@@@@

功能是给上层协议提供透明的数据包发送和接收接口。

当上层ARP或IP需要发送数据包时,它将调用网络协议接口层的dev_queue_xmit()函数发送该数据包,同时需传递给该函数一个指向struct sk_buff数据结构的指针。 dev_queue_xmit()函数的原型为:

int dev_queue_xmit(struct sk_buff *skb);

同样地,上层对数据包的接收也通过向netif_rx()函数传递一个struct sk_buff数据结构的指针来完成。 netif_rx()函数的原型为:

int netif_rx(struct sk_buff *skb);

sk_buff结构体非常重要,含义为“套接字缓冲区”,用于在Linux网络子系统中的各层之间传递数据,是Linux网络子系统数据传递的“中枢神经”。
当发送数据包时, Linux内核的网络处理模块必须建立一个包含要传输的数据包的sk_buff,然后将sk_buff递交给下层,各层在sk_buff中添加不同的协议头直至交给网络设备发送。同样地,当网络设备从网络媒介上接收到数据包后,它必须将接收到的数据转换为sk_buff数据结构并传递给上层,各层剥去相应的协议头直至交给用户。

struct sk_buff {
	/* These two members must be first. */
	struct sk_buff		*next;
	struct sk_buff		*prev;

	ktime_t			tstamp;

	struct sock		*sk;
	struct net_device	*dev;

	sk_buff_data_t		transport_header;
	sk_buff_data_t		network_header;
	sk_buff_data_t		mac_header;
	/* These elements must be at the end, see alloc_skb() for details.  */
	sk_buff_data_t		tail;
	sk_buff_data_t		end;
	unsigned char		*head,
				*data;
	unsigned int		truesize;
	atomic_t		users;
};

head和end指向缓冲区的头部和尾部

data和tail指向实际数据的头部和尾部

每一层会在head和data之间填充协议头,或者在tail和end之间添加新的协议数据。

下面我们来分析套接字缓冲区涉及的操作函数, Linux套接字缓冲区支持分配、释放、变更等功能函数。

(1)分配
Linux内核中用于分配套接字缓冲区的函数有:

struct sk_buff *alloc_skb(unsigned int len, gfp_t priority);
struct sk_buff *dev_alloc_skb(unsigned int len);

alloc_skb()函数分配一个套接字缓冲区和一个数据缓冲区,参数len为数据缓冲区的空间大小,通常以L1_CACHE_BYTES字节(对于ARM为32)对齐,参数priority为内存分配的优先级。

dev_alloc_skb()函数以GFP_ATOMIC优先级进行skb的分配,原因是该函数经常在设备驱动的接收中断里被调用。


(2)释放
Linux内核中用于释放套接字缓冲区的函数有:
 

void kfree_skb(struct sk_buff *skb);
void dev_kfree_skb(struct sk_buff *skb);
void dev_kfree_skb_irq(struct sk_buff *skb);
void dev_kfree_skb_any(struct sk_buff *skb);

上述函数用于释放被alloc_skb()函数分配的套接字缓冲区和数据缓冲区。Linux内核内部使用kree_skb()函数,而在网络设备驱动
程序中则最好用dev_kfree_skb()、 dev_kfree_skb_irq()或dev_kfree_skb_any()函数进行套接字缓冲区的释放。其中,
dev_kfree_skb()函数用于非中断上下文,
dev_kfree_skb_irq()函数用于中断上下文,而
dev_kfree_skb_any()函数在中断和非中断上下文中皆可采用,它其实是做一个非常简单的上下文判断,然后再调用
__dev_kfree_skb_irq()或者dev_kfree_skb(),这从其代码的实现中也可以看出:
 

void __dev_kfree_skb_any(struct sk_buff *skb, enum skb_free_reason reaso
{
    if (in_irq() || irqs_disabled())
        __dev_kfree_skb_irq(skb, reason);
    else
        dev_kfree_skb(skb);
}

(3)变更
在Linux内核中可以用如下函数在缓冲区尾部增加数据:

unsigned char *skb_put(struct sk_buff *skb, unsigned int len);

它会导致skb->tail后移len(skb->tail+=len),而skb->len会增加len的大小(skb->len+=len)。通常,在设备驱动的接收数据处理中会调用此函数。
在Linux内核中可以用如下函数在缓冲区开头增加数据:

unsigned char *skb_push(struct sk_buff *skb, unsigned int len)

它会导致skb->data前移len(skb->data-=len),而skb->len会增加len的大小(skb->len+=len)。与该函数的功能完成相反的函数是skb_pull(),它可以在缓冲区开头移除数据,执行的动作是skb->len-=len、 skb->data+=len。对于一个空的缓冲区而言,调用如下函数可以调整缓冲区的头部:

static inline void skb_reserve(struct sk_buff *skb, int len);

它会将skb->data和skb->tail同时后移len,执行skb->data+=len、 skb->tail+=len。内核里存在许多这样的代码:

  • skb=alloc_skb(len+headspace, GFP_KERNEL);
  • skb_reserve(skb, headspace);
  • skb_put(skb,len);
  • memcpy_fromfs(skb->data,data,len);
  • pass_to_m_protocol(skb);
     

上述代码先分配一个全新的sk_buff,接着调用skb_reserve()腾出头部空间,之后调用skb_put()腾出数据空间,然后把数据复制进来,最后把sk_buff传给协议栈。
 

1.2 网络设备接口层
网络设备接口层的主要功能是为千变万化的网络设备定义
统一、抽象的数据结构net_device结构体,以不变应万变,实
现多种硬件在软件层次上的统一。
net_device结构体在内核中指代一个网络设备,它定义于
include/linux/netdevice.h文件中,网络设备驱动程序只需通过填
充net_device的具体成员并注册net_device即可实现硬件操作函
数与内核的挂接。
net_device是一个巨大的结构体,定义于
include/linux/netdevice.h中,包含网络设备的属性描述和操作接
口,下面介绍其中的一些关键成员。
(1)全局信息
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


1.2、网络设备接口  @@@@@

(1)/dev下没有设备文件,也不通过/sys下的属性文件访问。直观看来,应用层都是通过一些特殊的命令(如ifconfig、ping等)来访问网卡硬件(调用驱动)的。本质上应用调用驱动的方法可以通过分析ping、ifconfig等命令的实现来得知。实际就是通过:socket、bind、listen、connect、send、recv等API来实现的。
(2)网络设备被抽象成一个能够发送和接收数据包的“网络接口”
(3)struct net_device来管理所有网络接口


1.3、学习方法   @@@@@

(1)注意网络设备的访问方法和前两种不同
(2)2个数据结构(net_device和sk_buff)
(3)一个虚拟网卡案例代码分析 + DM9000驱动源码分析


2.虚拟网卡驱动分析
 

代码:

#include <linux/module.h>
#include <linux/sched.h>
#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/errno.h>  /* error codes */
#include <linux/types.h>  /* size_t */
#include <linux/interrupt.h> /* mark_bh */
#include <linux/in.h>
#include <linux/netdevice.h>   /* struct device, and other headers */
#include <linux/etherdevice.h> /* eth_type_trans */
#include <linux/ip.h>          /* struct iphdr */
#include <linux/tcp.h>         /* struct tcphdr */
#include <linux/skbuff.h>
#include <linux/if_ether.h>
#include <linux/in6.h>
#include <asm/uaccess.h>
#include <asm/checksum.h>
#include <linux/platform_device.h>


// 如果需要随机MAC地址则定义该宏
#define  MAC_AUTO
static struct net_device *astonnet_devs;		

//网络设备结构体,作为net_device->priv
struct astonnet_priv {
    struct net_device_stats stats;		//有用的统计信息
    int status;							//网络设备的状态信息,是发完数据包,还是接收到网络数据包
    int rx_packetlen;					//接收到的数据包长度
    u8 *rx_packetdata;					//接收到的数据
    int tx_packetlen;					//发送的数据包长度
    u8 *tx_packetdata;					//发送的数据
    struct sk_buff *skb;				//socket buffer结构体,网络各层之间传送数据都是通过这个结构体来实现的
    spinlock_t lock;					//自旋锁
};

//网络接口的打开函数
int astonnet_open(struct net_device *dev)
{
	printk("astonnet_open\n");
	
#ifndef MAC_AUTO
	int i;
	for (i=0; i<6; i++)
	    dev->dev_addr[i] = 0xaa;
#else
    random_ether_addr(dev->dev_addr); //随机源地址
#endif
    netif_start_queue(dev);		//打开传输队列,这样才能进行数据传输
	
    return 0;
}

int astonnet_release(struct net_device *dev)
{
    printk("astonnet_release\n");
	//当网络接口关闭的时候,调用stop方法,这个函数表示不能再发送数据
    netif_stop_queue(dev);
	
    return 0;
}

//接包函数
void astonnet_rx(struct net_device *dev, int len, unsigned char *buf)
{   
    struct sk_buff *skb;
    struct astonnet_priv *priv = (struct astonnet_priv *) dev->ml_priv;
    skb = dev_alloc_skb(len+2);//分配一个socket buffer,并且初始化skb->data,skb->tail和skb->head
    if (!skb) {
        printk("gecnet rx: low on mem - packet dropped\n");
        priv->stats.rx_dropped++;
        return;
    }
    skb_reserve(skb, 2); /* align IP on 16B boundary */ 
    memcpy(skb_put(skb, len), buf, len);//skb_put是把数据写入到socket buffer
    /* Write metadata, and then pass to the receive level */
    skb->dev = dev;
    skb->protocol = eth_type_trans(skb, dev);//返回的是协议号
    skb->ip_summed = CHECKSUM_UNNECESSARY; //此处不校验
    priv->stats.rx_packets++;//接收到包的个数+1
    
    priv->stats.rx_bytes += len;//接收到包的长度
    printk("astonnet rx \n");
    netif_rx(skb);//通知内核已经接收到包,并且封装成socket buffer传到上层
    return;
}

//真正的处理的发送数据包
//模拟从一个网络向另一个网络发送数据包
void astonnet_hw_tx(char *buf, int len, struct net_device *dev)

{
    struct net_device *dest;//目标设备结构体,net_device存储一个网络接口的重要信息,是网络驱动程序的核心
    struct astonnet_priv *priv;

    if (len < sizeof(struct ethhdr) + sizeof(struct iphdr)) 
	{
		printk("astonnet: Hmm... packet too short (%i octets)\n", len);
        return;
    }

    dest = astonnet_devs;
    priv = (struct astonnet_priv *)dest->ml_priv;		//目标dest中的priv
    priv->rx_packetlen = len;
    priv->rx_packetdata = buf;

    printk("astonnet tx \n");
    dev_kfree_skb(priv->skb);
}

//发包函数
int astonnet_tx(struct sk_buff *skb, struct net_device *dev)
{
	int len;
    char *data;
    struct astonnet_priv *priv = (struct astonnet_priv *)dev->ml_priv;

    if (skb == NULL)
	{
		printk("net_device %p,  skb %p\n", dev,  skb);
		return 0;
    }

    len = skb->len < ETH_ZLEN ? ETH_ZLEN : skb->len;//ETH_ZLEN是所发的最小数据包的长度
    data = skb->data;//将要发送的数据包中数据部分
    priv->skb = skb;
    astonnet_hw_tx(data, len, dev);//真正的发送函数
	
	return 0; 
}


//设备初始化函数
int astonnet_init(struct net_device *dev)
{
	printk("astoncnet_init\n");

    ether_setup(dev);//填充一些以太网中的设备结构体的项
    
    /* keep the default flags, just add NOARP */
    dev->flags           |= IFF_NOARP;

//为priv分配内存
	dev->ml_priv = kmalloc(sizeof(struct astonnet_priv), GFP_KERNEL);
	if (dev->ml_priv == NULL)
		return -ENOMEM;
    memset(dev->ml_priv, 0, sizeof(struct astonnet_priv));
    spin_lock_init(&((struct astonnet_priv *)dev->ml_priv)->lock);
	
    return 0;
}


static const struct net_device_ops astonnet_netdev_ops = {
	.ndo_open		= astonnet_open,			// 打开网卡	对应 ifconfig xx up
	.ndo_stop		= astonnet_release,			// 关闭网卡	对应 ifconfig xx down
	.ndo_start_xmit	= astonnet_tx,				// 开启数据包传输
	.ndo_init       = astonnet_init,			// 初始化网卡硬件
};

static void	aston_plat_net_release(struct device *dev)
{
	printk("aston_plat_net_release\n");
}

static int __devinit aston_net_probe(struct platform_device *pdev)
{
	int result=0;

	astonnet_devs = alloc_etherdev(sizeof(struct net_device));
	astonnet_devs->netdev_ops = &astonnet_netdev_ops;

	strcpy(astonnet_devs->name, "astonnet0");
	if ((result = register_netdev(astonnet_devs)))
		printk("astonnet: error %i registering device \"%s\"\n", result,  astonnet_devs->name);
	
	return 0;
}

static int  __devexit  aston_net_remove(struct platform_device *pdev)   //设备移除接口
{
        kfree(astonnet_devs->ml_priv);
        unregister_netdev(astonnet_devs);
        return 0;
}


static struct platform_device aston_net= {
	.name	= "aston_net",
	.id		= -1,
    .dev	= {
		.release = aston_plat_net_release,
	},
};

static struct platform_driver aston_net_driver = {
    .probe  = aston_net_probe,                 	
    .remove  = __devexit_p(aston_net_remove),  	

	.driver  = {
		.name ="aston_net",
		.owner = THIS_MODULE,
	},
};


static int __init aston_net_init(void)
{
	printk("aston_net_init \n");
	
	platform_device_register(&aston_net);
	return platform_driver_register(&aston_net_driver );    	
}

static void __exit aston_net_cleanup(void)
{
	platform_driver_unregister(&aston_net_driver );    	
	platform_device_unregister(&aston_net);
}

module_init(aston_net_init);
module_exit(aston_net_cleanup);
MODULE_LICENSE("GPL");


4.DM9000驱动源码分析1

5.DM9000驱动源码分析2

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值