热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

双网口驱动实现流程

1网络设备驱动的结构Linux网络设备驱动程序体系结构如下图,从上到下依次划分为4层,依次为网路协议接口层、网络设备接口层,提供实际功能的设备驱动功能层以及网络设备与媒介层。

1网络设备驱动的结构

    Linux网络设备驱动程序体系结构如下图,从上到下依次划分为4层,依次为网路协议接口层、网络设备接口层,提供实际功能的设备驱动功能层以及网络设备与媒介层。

 

 

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

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

(3) 设备驱动功能层各个函数是网络设备接口层定义的net_device结构体的具体成员,是驱动网络设备硬件完成相关功能的层序,到达该层的网络数据通过hard_start_xmit()函数发送到网络设备与媒介层,并通过网络设备上的中断触发接收从网络设备与媒介发送过来的网络帧。

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

   

在设计具体的网络设备驱动程序时,我们要完成的主要工作是编写设备驱动功能层的相关函数以填充net_device结构体的内容并将net_device结构体注册到linux内核。

 

2 设备驱动功能层的代码实现

根据3.2.1的理论基础,找到设备驱动功能层所在的路径为

“/home/seen/pdb/uClinux-dist/linux-2.4.x/drivers/net”

2.2.1网络设备驱动的注册与注销

    网络设备驱动的注册与注销使用成对出现的register_netdev()和unregister_netdev()函数完成。这两个函数的原型为:

int register_netdev(struct net_device *dev);//注册

void unregister_netdev(struct net_device *dev);//注销

这两个函数都接收一个net_device结构体指针为参数,可见net_device数据结构在网络设备驱动中的核心地位。

2.2.2网络设备驱动程序的模块加载函数

    net_device结构体的分配和网络设备驱动注册需要在网络设备驱动程序的模块加载函数中进行,而net_device结构体的释放和网络设备的注销则需要在模块卸载函数中完成,代码如下所示.

int init_module(void)  //模块加载函数

{

int ret;

 

memset((void *)xxx_netdevice[0].name ,0 ,IFNAMSIZ);

ret=register_netdev((struct net_device *)&xxx_netdevice[0]);//注册

if(ret!=0)

{

printk("Regiter EMC 0 xxx FAILED\n");

TRACE_ERROR("Regiter EMC 0 xxx FAILED\n");

return  -ENODEV;

}

 

return 0;

}

 

void cleanup_module(void)  //模块注销函数

{        

   unregister_netdev((struct net_device *)&xxx_netdevice[0]);//注销

}

 

2.2.3网络设备的初始化

网络设备的初始化主要完成如下几个方面的工作:

(1) 进行硬件上的准备工作,检查网络设备是否存在,如果存在,则检查设备所使用的硬件资源。

(2) 进行软件接口上的准备工作,分配net_device结构体并对其数据和函数指针成员赋值。

(3) 获得设备的私有信息指针并初始化其各成员的值。如果私有信息中包括自旋锁或信号量等并发或同步操作,则需对其进行初始化。

 

网络设备初始化代码如下:

int xxx_init(struct net_device *dev)

{

static int which=0 ;     //Only one mac for NUC700

struct xxx_priv *priv; //设备私有信息结构体

 

ether_setup(dev);       //初始化以太网设备的公用成员

dev->open=xxx_open;//网络设备的打开

dev->stop=xxx_close;//网络设备的关闭

dev->do_ioctl=xxx_do_ioctl;

dev->hard_start_xmit=xxx_start_xmit;//send packets which were received from up layer

dev->tx_timeout=xxx_timeout;  //发送超时函数

dev->get_stats=xxx_get_stats;  //设备状态和信息统计

dev->watchdog_timeo =TX_TIMEOUT; //HZ

dev->irq=INT_EMCTXINT0+which;

dev->set_multicast_list=xxx_set_multicast_list;

dev->set_mac_address=xxx_set_mac_address;//设置设备MAC地址

dev->priv=(void*)(((unsigned long)kmalloc(sizeof(struct xxx_priv),GFP_KERNEL))|NON_CACHE_FLAG);

if(dev->priv == NULL)

return -ENOMEM;

memset(dev->priv, 0, sizeof(struct xxx_priv));

   memcpy(xxx_mac_address0,(char*)(MAC_ADDR),ETH_ALEN);

    memcpy(dev->dev_addr,xxx_mac_address0,ETH_ALEN);

    priv=(struct xxx_priv *)dev->priv;

    priv->which=which;

    priv->cur_tx_entry=0;

    priv->cur_rx_entry=0;

    printk("%s initial ok!\n",dev->name);

return 0;

 

}

2.2.4网络设备open函数

  网络设备打开函数主要完成以下工作:

(1) 使能设备的硬件资源,申请I/O区域,激活DMA通道等。

(2) 调用linux内核提供的netif_start_queue()函数,激活设备发送队列。

int xxx_open(struct net_device *dev)

{

申请端口、IRQ

#if HUBMODEL  //hub model.

Hub_Init(dev,which);//hub相关寄存器使能。

#else //router model

Router_Init(dev,which);//router相关寄存器使能

#endif…

request_irq(INT_EMCTXINT0+which,&tx_interrupt,SA_INTERRUPT,"",dev)   //发包中断

irequest_irq(INT_EMCRXINT0+which,&rx_interrupt,SA_INTERRUPT,"",dev)  //收包中断,当有数据包到达网口时,调用该中断函数处理接   收数据包

 netif_start_queue(dev);    //激活发送队列

}

2.2.5  安全芯片HUB模式相关寄存器使能

void Hub_Init(struct net_device *dev,int num)

{

}

1.2.5 安全芯片ROUTER模式相关寄存器使能

void  Router_Init(struct net_device *dev,int num)

{

}

2.2.6发送网络包

   如4.1网络设备驱动结构的流程图所示,网络设备驱动完成数据包的发送流程如下:

   (1) 网络设备驱动层序从上层协议传递过来的sk_buff参数中获得数据包的有效数据和长度,将有效数据放入临时缓冲区。

 (2)对于以太网,如果有效数据有效长度小于以太网冲突检测所要求数据帧的最小长度ETH_ZLEN,则给临时缓冲区的末尾填0.

 (3)设置硬件的寄存器,驱使网络设备进行数据发送操作。

对于本工程而言,发送数据包的函数原型为:

int xxx_start_xmit(struct sk_buff *skb, struct net_device *dev)

该函数对应于dev->hard_start_xmit

 

static int xxx_init(struct net_device *dev)

{

.......

 

//发送数据包

dev->hard_start_xmit=nuc700_start_xmit;

 

.......

}

    对于发送数据包函数xxx_start_xmit()来说,此时的数据包是最完整的ip网络数据包,通过解析数据包的目标ip地址,来决定数据包最终向wan口转发,还是向lan口转发。需要向数据包中加入2个字节的flagsflag值为0x8004表示向lan口转发数据,flag值为0x80A0表示向wan口转发数据。

 

2.2.7 接收网络包

     如4.1网络设备驱动结构的流程图所示,当设备的中断处理程序rx_interrupt()判断中断类型为数据包接收中断时,则调用函数netdev_rx()完成更深入的数据包接收工作。

      void netdev_rx(struct net_device *dev)

netdev_rx()做了三件事情:

首先,从硬件读取到接收数据包有效数据的长度;

然后,读取硬件上接收的数据并放入数据缓冲区;

最后,将数据包上交给上层协议。

接收网络包部分的代码比较易懂,这里我就不再列出。可以阅读网络驱动内核源码相关部分。

 

2.2.8网线插拔状态监测

    顾名思义,驱动层能够要知道当前2个网口的网线是否连接正常。

通过一个定时器,每经过若干个时钟周期检测一下网口的硬件连接状态。

xxx_autodetect()具体实现了这一功能。具体见源码,这里不再说明。

 

3应用层与设备驱动层通信机制

     通过http设置RouterHub模式的切换,通过ioctl来告知kernel层这一变化。Kernel层根据这一变化决定数据包如何转发。

当通过http方式“Enable”路由模式时,协议层通过html_para()获知这一信息。html_para()调用ioctlioclt通过SIOICCONFIGROUTER命令字将这一消息告诉kernel层。

对于kernel层来说是通过xxx_do_ioctl()这一函数来实现,该函数包含一个switch语句,用来解析协议层传递过来的命令字,当发现命令字为SIOICCONFIGROUTER时,会把全局变量router_model1,然后kernel层就开始以Router的方式对网络包经行转发。

 

skb_put(skb,len)skb_pushskb,len)的区别

skb_put() 增长数据区的长度来为memcpy准备空间许多的网络操作需要加入一些桢头这可以使用skb_push来将数据区向后推为头留出空间. 

 

请参见下图: 

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

| head | data | | 

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

 

skb_put 

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

| head | data | put_data | | 

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

 

skb_push 

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

| head | push_data | data | | 

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

 


推荐阅读
  • MySQL数据库锁机制及其应用(数据库锁的概念)
    本文介绍了MySQL数据库锁机制及其应用。数据库锁是计算机协调多个进程或线程并发访问某一资源的机制,在数据库中,数据是一种供许多用户共享的资源,如何保证数据并发访问的一致性和有效性是数据库必须解决的问题。MySQL的锁机制相对简单,不同的存储引擎支持不同的锁机制,主要包括表级锁、行级锁和页面锁。本文详细介绍了MySQL表级锁的锁模式和特点,以及行级锁和页面锁的特点和应用场景。同时还讨论了锁冲突对数据库并发访问性能的影响。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 一、死锁现象与递归锁进程也是有死锁的所谓死锁:是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作 ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • 标题: ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 一、什么是闭包?有什么作用什么是闭包闭包是定义在一个函数内部的函数,它可以访问父级函数的内部变量。当一个闭包被创建时,会关联一个作用域—— ... [详细]
  • STL迭代器的种类及其功能介绍
    本文介绍了标准模板库(STL)定义的五种迭代器的种类和功能。通过图表展示了这几种迭代器之间的关系,并详细描述了各个迭代器的功能和使用方法。其中,输入迭代器用于从容器中读取元素,输出迭代器用于向容器中写入元素,正向迭代器是输入迭代器和输出迭代器的组合。本文的目的是帮助读者更好地理解STL迭代器的使用方法和特点。 ... [详细]
  • 深入解析Linux下的I/O多路转接epoll技术
    本文深入解析了Linux下的I/O多路转接epoll技术,介绍了select和poll函数的问题,以及epoll函数的设计和优点。同时讲解了epoll函数的使用方法,包括epoll_create和epoll_ctl两个系统调用。 ... [详细]
  • 现象:[root@localhost~]#dockerrun-d-p9000:80centos:httpdbinsh-cusrlocalbinstart.shd5b2bd5a7bc ... [详细]
  • 安装goget-ugithub.comgomoduleedigoedis连接var(redisHost127.0.0.1:6379redisPassroot)创建redis ... [详细]
  • ! Configuration File for keepalivedglobal_defs {   notification_email {     ... [详细]
  • 上图是InnoDB存储引擎的结构。1、缓冲池InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可以看作是基于磁盘的数据库系统。在数据库系统中,由于CPU速度 ... [详细]
  • HashMap的相关问题及其底层数据结构和操作流程
    本文介绍了关于HashMap的相关问题,包括其底层数据结构、JDK1.7和JDK1.8的差异、红黑树的使用、扩容和树化的条件、退化为链表的情况、索引的计算方法、hashcode和hash()方法的作用、数组容量的选择、Put方法的流程以及并发问题下的操作。文章还提到了扩容死链和数据错乱的问题,并探讨了key的设计要求。对于对Java面试中的HashMap问题感兴趣的读者,本文将为您提供一些有用的技术和经验。 ... [详细]
author-avatar
sprout--_557
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有