网络设备驱动介绍(浅析)

一、网络设备驱动概述

1、什么是网络设备

(1)网络设备(指的是软件),驱动框架中使用结构体虚拟出一个个设备,而驱动程序本身也是软件
在这里插入图片描述
(2)物理网卡: 真正的硬件网卡设备,可上网的芯片

2、网络设备接口

(1)/dev下没有设备文件,也不通过/sys下的属性文件访问。与字符设备、块设备不同。

  直观看来,应用层都是通过一些特殊的命令(如ifconfig、ping等)来访问网卡硬件(调用驱动)的。本质上应用调用驱动的方法可以通过分析ping、ifconfig等命令的实现来得知。实际就是通过:socket、bind、listen、connect、send 、recv等API来实现的。

(2)网络设备被抽象成一个能够发送和接收数据包的“网络接口”网卡有真实的也有虚拟的。共享文件夹本质还是通过网络设备实现的。

(3)struct net_device来管理所有网络接口

ndo:net_device_ops
struct net_device_ops {
	int			(*ndo_init)(struct net_device *dev);//初始化硬件网卡
	void			(*ndo_uninit)(struct net_device *dev);
	int			(*ndo_open)(struct net_device *dev);//打开网卡
	int			(*ndo_stop)(struct net_device *dev);//关闭网卡
	netdev_tx_t		(*ndo_start_xmit) (struct sk_buff *skb,
						   struct net_device *dev);//发送数据包
	u16			(*ndo_select_queue)(struct net_device *dev,
						    struct sk_buff *skb);
	void			(*ndo_change_rx_flags)(struct net_device *dev,
						       int flags);
	void			(*ndo_set_rx_mode)(struct net_device *dev);//设置接收模式
	void			(*ndo_set_multicast_list)(struct net_device *dev);
	int			(*ndo_set_mac_address)(struct net_device *dev,
						       void *addr);//设置mac地址
	int			(*ndo_validate_addr)(struct net_device *dev);
	int			(*ndo_do_ioctl)(struct net_device *dev,
					        struct ifreq *ifr, int cmd);
	int			(*ndo_set_config)(struct net_device *dev,
					          struct ifmap *map);
	int			(*ndo_change_mtu)(struct net_device *dev,
						  int new_mtu);
	int			(*ndo_neigh_setup)(struct net_device *dev,
						   struct neigh_parms *);
	void			(*ndo_tx_timeout) (struct net_device *dev);

	struct net_device_stats* (*ndo_get_stats)(struct net_device *dev);

	void			(*ndo_vlan_rx_register)(struct net_device *dev,
						        struct vlan_group *grp);
	void			(*ndo_vlan_rx_add_vid)(struct net_device *dev,
						       unsigned short vid);
	void			(*ndo_vlan_rx_kill_vid)(struct net_device *dev,
						        unsigned short vid);
#ifdef CONFIG_NET_POLL_CONTROLLER
	void                    (*ndo_poll_controller)(struct net_device *dev);
	void			(*ndo_netpoll_cleanup)(struct net_device *dev);
#endif
	int			(*ndo_set_vf_mac)(struct net_device *dev,
						  int queue, u8 *mac);
	int			(*ndo_set_vf_vlan)(struct net_device *dev,
						   int queue, u16 vlan, u8 qos);
	int			(*ndo_set_vf_tx_rate)(struct net_device *dev,
						      int vf, int rate);
	int			(*ndo_get_vf_config)(struct net_device *dev,
						     int vf,
						     struct ifla_vf_info *ivf);
	int			(*ndo_set_vf_port)(struct net_device *dev,
						   int vf,
						   struct nlattr *port[]);
	int			(*ndo_get_vf_port)(struct net_device *dev,
						   int vf, struct sk_buff *skb);
#if defined(CONFIG_FCOE) || defined(CONFIG_FCOE_MODULE)
	int			(*ndo_fcoe_enable)(struct net_device *dev);
	int			(*ndo_fcoe_disable)(struct net_device *dev);
	int			(*ndo_fcoe_ddp_setup)(struct net_device *dev,
						      u16 xid,
						      struct scatterlist *sgl,
						      unsigned int sgc);
	int			(*ndo_fcoe_ddp_done)(struct net_device *dev,
						     u16 xid);
#define NETDEV_FCOE_WWNN 0
#define NETDEV_FCOE_WWPN 1
	int			(*ndo_fcoe_get_wwn)(struct net_device *dev,
						    u64 *wwn, int type);
#endif
};

网卡接收数据使用了中断模式,异步事件。

3、学习方法

(1)注意网络设备的访问方法和前两种不同

(2)2个数据结构

net_device:可看成一个实际的网卡设备
sk_buff:内核开辟出的一个缓冲区,存储发送、接收的数据包

(3)一个虚拟网卡案例代码分析 + DM9000驱动源码分析

二、虚拟网卡驱动分析

使用到的文件:
链接:https://pan.baidu.com/s/1yCzzzoiHYJmQS-XvHKIp_w
提取码:leb1
–来自百度网盘超级会员V5的分享

1、虚拟网卡安装、卸载、打开、关闭、设置IP地址等实践

将上面提供的文件使用Makefile进行编译,生成驱动并在开发板上装载驱动。

insmod xx.ko //安装网卡驱动,安装该驱动就如同装载了一块网卡
ifconfig -a //查看所有的网卡
ifconfig 网卡名 up/down  //关闭/启动网卡
ifup/down 网卡名//新命令
ifconfig 网卡名 IP地址 //设置IP地址

这个网卡是无法真正使用的,因为其是虚拟出来的,没有实际的网卡硬件。

2、虚拟网卡驱动分析

  将虚拟网卡驱动文件kernel/drivers/net/net_demo/net.c添加到sourceinsight软件中,与内核源码一起进行分析,便于查看一些宏定义,变量类型等等。

#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");

该代码只有大致框架,真正的网卡驱动是有很多细节设置的。

字符设备:struct file_operations
块设备:struct block_device_operations
网络设备:struct net_device_ops

3、DM9000(真实网卡)源码分析

在这里插入图片描述
源码文件:kernel/drivers/net/dm9000.c

static int __init dm9000_init(void)
{
	/* disable buzzer */
	s3c_gpio_setpull(S5PV210_GPD0(2), S3C_GPIO_PULL_UP);//设置上拉
	s3c_gpio_cfgpin(S5PV210_GPD0(2), S3C_GPIO_SFN(1));//设置输出模式
	gpio_set_value(S5PV210_GPD0(2), 0);//设置输出值为0

	dm9000_power_int(); 
	printk(KERN_INFO "%s Ethernet Driver, V%s\n", CARDNAME, DRV_VERSION);

	return platform_driver_register(&dm9000_driver);
}

static void __exit dm9000_cleanup(void)
{
	platform_driver_unregister(&dm9000_driver);
}

module_init(dm9000_init);
module_exit(dm9000_cleanup);
static struct platform_driver dm9000_driver = {
	.driver	= {
		.name    = "dm9000",
		.owner	 = THIS_MODULE,
		.pm	 = &dm9000_drv_pm_ops,
	},
	.probe   = dm9000_probe,
	.remove  = __devexit_p(dm9000_drv_remove),
};

搜索dm9000查找device在那个文件。kernel/arch/arm/plat-s5p/devs.c

struct platform_device s5p_device_dm9000 = {
	.name		= "dm9000",
	.id		=  0,
	.num_resources	= ARRAY_SIZE(s5p_dm9000_resources),
	.resource	= s5p_dm9000_resources,
	.dev		= {
		.platform_data = &s5p_dm9000_platdata,
	}
};

  该驱动的注册流程和方式与之前的文章讲述的驱动十分相似,大家只要按照以前学习的思路自己去sourceinsight软件进行追踪分析就可以了。重点去分析dm9000_probe函数。

工作队列(work queue)是Linux内核中将操作延期执行的一种机制。INIT_DELAYED_WORK()
是一个宏,中断下半部实现的一种策略——work queue

INIT_DELAYED_WORK(&db->phy_poll, dm9000_poll_work);//可以被延时的work

静态创建 
DECLARE_WORK(name,function); //定义正常执行的工作项
DECLARE_DELAYED_WORK(name,function);//定义延后执行的工作项

动态创建
INIT_WORK(_work, _func) //创建正常执行的工作项
INIT_DELAYED_WORK(_work, _func)//创建延后执行的工作项

调度默认工作队列
int schedule_work(struct work_struct *work)

//对正常执行的工作进行调度,即把给定工作的处理函数提交给缺省的工作队列和工作者线程。工作者线程本质上是一个普通的内核线程,在默认情况下,每个CPU均有一个类型为“events”的工作者线程,当调用schedule_work时,这个工作者线程会被唤醒去执行工作链表上的所有工作。

系统默认的工作队列名称是:keventd_wq,默认的工作者线程叫:events/n,这里的n是处理器的编号,每个处理器对应一个线程。比如,单处理器的系统只有events/0这样一个线程。而双处理器的系统就会多一个events/1线程。
默认的工作队列和工作者线程由内核初始化时创建:
start_kernel()-->rest_init-->do_basic_setup-->init_workqueues

调度延迟工作
int schedule_delayed_work(struct delayed_work *dwork,unsigned long delay)

刷新缺省工作队列
void flush_scheduled_work(void)
//此函数会一直等待,直到队列中的所有工作都被执行。

取消延迟工作
static inline int cancel_delayed_work(struct delayed_work *work)
//flush_scheduled_work并不取消任何延迟执行的工作,因此,如果要取消延迟工作,应该调用cancel_delayed_work。

注:本资料大部分由朱老师物联网大讲堂课程笔记整理而来并且引用了部分他人博客的内容,如有侵权,联系删除!水平有限,如有错误,欢迎各位在评论区交流。

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小嵌同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值