Linux 网络设备驱动开发(三) —— 网络设备驱动基本原理和框架

一、协议栈层次对比


二、Linux网络子系统

    Linux网络子系统的顶部是系统调用接口层。它为用户空间提供的应用程序提供了一种访问内核网络子系统的方法(socket)。位于其下面是一个协议无关层,它提供一种通用的方法来使用传输层协议。然后是具体协议的实现,在Linux中包括内核的协议TCP,UDP,当然还有IP。然后是设备无关层,它提供了协议与设备驱动通信的通用接口,最下面是设备的驱动程序。



    设备无关接口将协议与各种网络驱动连接在一起,这一层提供一组通用函数供底层网络设备驱动使用,让它们可以对高层协议栈进行操作。需要从协议层向设备发生数据,需要调用dev_queue_xmit函数,这个函数对数据进行列队,然后交由底层驱动程序的hard_start_xmit方法最终完成传输。接收通常是使用netif_rx执行的。当底层设备程序接收到一个报文(发生中断)时,就会调用netif_rx将数据上传至设备无关层。


三、设备无关层到驱动层的体系结构

下图为设备无关层到驱动层的体系结构


1)、网络协议接口层向网络层协议提供提供统一的数据包收发接口,不论上层协议为ARP还是IP,都通过dev_queue_xmit()函数发送数据,并通过netif_rx()函数接受数据。这一层的存在使得上层协议独立于具体的设备。
2)、网络设备接口层向协议接口层提供统一的用于描述具体网络设备属性和操作的结构体net_device,该结构体是设备驱动功能层中各函数的容器。实际上,网络设备接口层从宏观上规划了具体操作硬件的设备驱动功能层的结构。
3)、设备驱动功能层各函数是网络设备接口层net_device数据结构的具体成员,是驱使网络设备硬件完成相应动作的程序,他通过hard_start_xmit()函数启动发送操作,并通过网络设备上的中断触发接受操作。
4)、网络设备与媒介层是完成数据包发送和接受的物理实体,包括网络适配器和具体的传输媒介,网络适配器被驱动功能层中的函数物理上驱动。对于Linux系统而言,网络设备和媒介都可以是虚拟的。


1、网络协议接口层:

这里主要进行数据包的收发,使用函数原型为:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. dev_queue_xmit(struct sk_buff *skb);int netif_rx(struct sk_buff *skb);    
这里使用了一个skb_buff结构体,定义于include/linux/skbuff.h中,它的含义为“套接字缓冲区”,用于在Linux网络子系统各层间传输数据。他是一个双向链表,在老的内核中会有一个list域指向sk_buff_head也就是链表头,但是在我研究的linux2.6.30.4内核中已经不存在了,如下图:

sk_buff中重要的数据成员

struct device *dev;正在处理该包的设备

__u32 sadd;r//IP元地址

__u32 daddr;//IP目的地址

__u32 raddr;//IP路由器地址

unsigned char *head;//分配空间的开始

unsigned char *data;//有效数据的开始

unsigned char *tail;//有效数据的结束

unsigned char *end;//分配空间的结束

unsigned long len;//有效数据的长度


sk_buff操作
a -- 分配:分配一个sk_buff结构,供协议栈代码使用

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. struct sk_buff *alloc_skb(unsigned int len, int priority);  
  2. struct sk_buff *dev_alloc_skb(unsigned int len);    

  分配一个缓冲区。alloc_skb函数分配一个缓冲区并初始化skb->data和skb->tail为skb->head。参数len为数据缓冲区的空间大小,通常以L1_CACHE_BYTES字节(对ARM为32)对齐,参数priority为内存分配的优先级。dev_alloc_skb()函数以GFP_ATOMIC优先级进行skb的分配。

b -- 释放:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. void kfree_skb(struct sk_buff *skb);  
  2. void dev_kfree_skb(struct sk_buff *skb);   

Linux内核内部使用kfree_skb()函数,而网络设备驱动程序中则最好使用dev_kfree_skb()。

sk_buff中比较重要的成员是指向数据包中数据的指针,如下图所示:


用于寻址数据包中数据的指针,head指向已分配空间开头,data指向有效的octet开头,tail指向有效的octet结尾,而end指向tail可以到达的最大地址。如果不这样做而分配一个大小固定的缓冲区,如果buffer不够用,则要申请一个更大的buffer,拷贝进去再增加,这样降低了性能。


3)变更

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. unsigned char *skb_put(struct sk_buff *skb, int len);将taill指针向后移动len长度,并返回tail移动之前的值。用于向skb有效数据区域末尾添加数据。  
  2. unsigned char *skb_push(struct sk_buff *skb, int len);将data指针向前移动len长度。并返回移动之后的值。用于向skb有效数据区域前端添加数据(包头)。  
  3. unsigned char *skb_pull(struct sk_buff *skb, int len);  
  4. void skb_reserve(struct sk_buff ×skb, int len);   
下图分别对应了这四个函数,看了这张图应该对这4个函数的作用了然于胸。
 


2、网络设备接口层:

 网络设备接口层的主要功能是为千变万化的网络设备定义了统一,抽象的数据结构net_device结构体,以不变应万变,实现多种硬件在软件层次上的统一。

    每一个网络设备都由struct net_device来描述,该结构可使用如下内核函数进行动态分配

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. struct net_device *alloc_netdev(int sizeof_priv, const char *mask, void(*setup)(struct net_device *))  

sizeof_priv是私有数据区大小;mask是设备名,setup是初始化函数,在注册该设备时,该函数被调用。也就是net_deivce的init成员。

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. struct net_device *alloc_etherdev(intsizeof_priv)  
这个函数和上面的函数不同之处在于内核知道会将该设备做一个以太网设备看待并做一些相关的初始化。

net_device结构可分为全局成员、硬件相关成员、接口相关成员、设备方法成员和公用成员等五个部分

a -- 主要全局成员

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. char name[INFAMSIZ]    设备名,如:eh%d  
  2. unsigned long state  设备状态  
  3. unsigned long base_addr  I/O基地址  
  4. unsigned int irq   中断号  

b -- 主要设备方法

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. //首先看打开和关闭网络设备的函数:  
  2.   
  3. int (*open)(struct net_device *dev);  
  4. //打开接口。ifconfig激活时,接口将被打开  
  5.   
  6. int (*stop)(struct net_device *dev);    
  7. //停止接口,ifconfig eth% down时调用  
  8. //要注意的是ifconfig是interface config的缩写,通常我们在用户空间输入:  
  9. //ifconfig eth0 up  会调用这里的open函数。  
  10. //在用户空间输入:  
  11. //ifconfig eth0 down  会调用这里的stop函数。  
  12. //在使用ifconfig向接口赋予地址时,要执行两个任务。首先,它通过ioctl(SIOCSIFADDR)(Socket I/O Control Set Interface Address)赋予地址,然后通过ioctl(SIOCSIFFLAGS)(Socket I/O Control Set Interface Flags)设置dev->flag中的IFF_UP标志以打开接口。这个调用会使得设备的open方法得到调用。类似的,在接口关闭时,ifconfig使用ioctl(SIOCSIFFLAGS)来清理IFF_UP标志,然后调用stop函数。  
  13.   
  14. int  (*init)(struct  net_device *dev)  
  15. //初始化函数,该函数在register_netdev时被调用来完成对net_device结构的初始化  
  16.   
  17. int (*hard_start_xmit)(struct sk_buf*skb,struct net_device *dev)  
  18. //数据发送函数  
  19.   
  20. int (*hard_header)(struct sk_buff *skb, struct net_device *dev, unsigned short type, void *daddr, void *saddr, unsigned len);   
  21. //该方法根据先前检索到的源和目的硬件地址建立硬件头  
  22.   
  23. int (*rebuild_header)(struct sk_buff *skb);  
  24. //以太网的mac地址是固定的,为了高效,第一个包去询问mac地址,得到对应的mac地址后就会作为cache把mac地址保存起来。以后每次发包不用询问了,直接把包的地址拷贝出来。  
  25.   
  26. void (*tx_timeout)(struct net_device *dev);    
  27. //如果数据包发送在超时时间内失败,这时该方法被调用,这个方法应该解决失败的问题,并重新开始发送数据。  
  28.   
  29. struct net_device_stats *(*get_stats)(struct net_device *dev);    
  30. //当应用程序需要获得接口的统计信息时,这个方法被调用。  
  31.   
  32. int (*set_config)(struct net_device *dev, struct ifmap *map);    
  33. //改变接口的配置,比如改变I/O端口和中断号等,现在的驱动程序通常无需该方法。  
  34.   
  35. int (*do_ioctl)(struct net_device *dev, struct ifmap *map);    
  36. //用来实现自定义的ioctl命令,如果不需要可以为NULL。  
  37.   
  38. void (*set_multicast_list)(struct net_device *dev);    
  39. //当设备的组播列表改变或设备标志改变时,该方法被调用。  
  40.   
  41. int (*set_mac_address)(struct net_device *dev, void *addr);    
  42. //如果接口支持mac地址改变,则可以实现该函数。</span>  

3、设备驱动接口层:

net_device结构体的成员(属性和函数指针)需要被设备驱动功能层的具体数值和函数赋予。对具体的设置xxx,工程师应该编写设备驱动功能层的函数,这些函数型如xxx_open(),xxx_stop(),xxx_tx(),xxx_hard_header(),xxx_get_stats(),xxx_tx_timeout()等。


4、网络设备与媒介层:

网络设备与媒介层直接对应于实际的硬件设备。


网络设备的注册

网络设备注册方式与字符驱动不同之处在于它没有主次设备号,并使用下面的函数注册

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. int register_netdev(struct net_deivce*dev)  
网络设备的注销
[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. void unregister_netdev(struct net_device*dev)  

四、驱动的实现

1)初始化(init)

设备探测工作在init方法中进行,一般调用一个称之为probe方法的函数

初始化的主要工作时检测设备,配置和初始化硬件,最后向系统申请这些资源。此外填充该设备的dev结构,我们调用内核提供的ether_setup方法来设置一些以太网默认的设置。

 

2)打开(open)

open这个方法在网络设备驱动程序里是网络设备被激活时被调用(即设备状态由down变成up)

实际上很多在初始化的工作可以放到这里来做。比如说资源的申请,硬件的激活。如果dev->open返回非0,则硬件状态还是down,
注册中断、DMA等;设置寄存器,启动设备;启动发送队列

 一般注册中断都在init中做,但在网卡驱动程序中,注册中断大部分都是放在open中注册,因为要经常关闭和重启网卡


3)关闭(stop)

stop方法做和open相反的工作

可以释放某些资源以减少系统负担

stop是在设备状态由up转为down时被调用


4)发送(hard_start_xmit)

在系统调用的驱动程序的hard_start_xmit时,发送的数据放在一个sk_buff结构中。一般的驱动程序传给硬件发出去。也有一些特殊的设备比如说loopback把数据组成一个接收数据在传送给系统或者dummy设备直接丢弃数据。

如果发送成功,hard_start_xmit方法释放sk_buff。如果设备暂时无法处理,比如硬件忙,则返回1。


5)接收

驱动程序并存在一个接受方法。当有数据收到时驱动程序调用netif_rx函数将skb交交给设备无关层。

一般设备收到数据后都会产生一个中断,在中断处理程序中驱动程序申请一块sk_buff(skb)从硬件中读取数据位置到申请号的缓冲区里。

接下来填充sk_buff中的一些信息。

中断有可能是收到数据产生也可能是发送完成产生,中断处理程序要对中断类型进行判断,如果是收到数据中断则开始接收数据,如果是发送完成中断,则处理发送完成后的一些操作,比如说重启发送队列。
接收流程:
1、分配skb=dev_alloc_skb(pkt->datalen+2)
2、从硬件中读取数据到skb
3、调用netif_rx将数据交给协议栈

中断处理

网络接口通常支持3种类型的中断:新报文到达中断、报文发送完成中断和出错中断。中断处理程序可通过查看网卡的中断状态寄存器,来分辨出中断类型。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/401874.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

国产杀毒软件也开始支持虚拟化

今天不小心看到一个较老的“新”闻&#xff0c;国产安全软件厂商瑞星已经推出支持VMware vshield Endpoint技术的防病毒产品&#xff0c;也就是说如果客户今天使用的是VMware的虚拟化软件&#xff0c;上面运行的所有虚拟机就不需要安装传统的防病毒软件程序&#xff0c;而可以直…

VRP平台基本操作

一、显示系统信息 <Huawei>display version 图上所示可以知道VRP平台信息&#xff0c;运行的版本&#xff0c;运行的时间 二、修改和查看设备系统时间参数 1.查看时间 <Huawei>display clock 2.修改系统日期和时间 三、进入系统视图界面 <Huawei>system-view…

Android中获取应用程序(包)的信息-----PackageManager的使用(一)

本节内容是如何获取Android系统中应用程序的信息&#xff0c;主要包括packagename、label、icon、占用大小等。具体分为两个 部分&#xff0c;计划如下&#xff1a; 第一部分&#xff1a; 获取应用程序的packagename、label、icon等 &#xff1b; 第二部分&#xff1a; 获取应用…

VRP平台总体介绍及基础配置

前言 1、VRP软件系统基础 VRP系统在启动时需要加载“系统软件”和“配置文件”两部分&#xff0c;这与其它品牌网络交换机的操作系统是一样的。如果指定了下次启动的补丁文件&#xff0c;还需加载补丁文件。修改VRP系统启动的场景一般有以下几种&#xff1a; a-- 对交换机进行升…

小强的HTML5移动开发之路(43)——JqueryMobile页眉、工具栏和标签栏导航

一、页眉1、添加页眉和页脚<div data-role"header"><h1>第 1 页</h1></div><div data-role"footer"><h4>页面脚注</h4></div>默认的页眉在屏幕的顶部边缘显示&#xff0c;而且在在屏幕滚动时&#xff0c;…

交换机开发(二)—— 三层交换机报文转发过程

如图所示&#xff0c;假如主机A想访问主机B&#xff0c;首先主机A会将自己的IP地址和子网掩码做与操作,得出网路地址(如:Host-A的IP地址100.1.1.2与自身掩码255.255.255.0做与操作后,得到的网络号是100.1.1.0).然后判断目的IP地址(即Host-B的IP地址)与自己的网络地址是不是在同…

分布式搜索elasticsearch配置文件详解

2019独角兽企业重金招聘Python工程师标准>>> elasticsearch的config文件夹里面有两个配置文件&#xff1a;elasticsearch.yml和logging.yml&#xff0c;第一个是es的基本配置文件&#xff0c;第二个是日志配置文件&#xff0c;es也是使用log4j来记录日志的&#xff…

交换机开发(三)—— 深入分析三层网络交换机的原理和设计

引言传统路由器在网络中起到隔离网络、隔离广播、路由转发以及防火墙的作业&#xff0c;并且随着网络的不断发展&#xff0c;路由器的负荷也在迅速增长。其中一个重要原因是出于安全和管理方便等方面的考虑&#xff0c;VLAN(虚拟局域网)技术在网络中大量应用。VLAN技术可以逻辑…

XML 命名空间(XML Namespaces)

为什么80%的码农都做不了架构师&#xff1f;>>> XML 应用程序 XML CDATA XML 命名空间提供避免元素命名冲突的方法。 命名冲突 在 XML 中&#xff0c;元素名称是由开发者定义的&#xff0c;当两个不同的文档使用相同的元素名时&#xff0c;就会发生命名冲突。 这个…

Linux 下挂载新硬盘方法

Linux的硬盘识别: 一般使用”fdisk -l”命令可以列出系统中当前连接的硬盘 设备和分区信息.新硬盘没有分区信息,则只显示硬盘大小信息. 1.关闭服务器加上新硬盘 2.启动服务器&#xff0c;以root用户登录 3.查看硬盘信息 #fdisk -l [cpp] view plaincopy Disk /dev/sda: 42.9 GB…

C++ 学习基础篇(一)—— C++与C 的区别

编程的学习学无止境&#xff0c;只掌握一门语言是远远不够的&#xff0c;现在我们开始C的学习之路&#xff0c;下面先看下C 与C 的区别 一、C概述 1、发展历史 1980年&#xff0c;Bjarne Stroustrup博士开始着手创建一种模拟语言&#xff0c;能够具有面向对象的程序设计特色。在…

C++学习基础篇 —— 引用()的用法和应用

一、引用简介 引用就是某一变量&#xff08;目标&#xff09;的一个别名&#xff0c;对引用的操作与对变量直接操作完全一样。 引用的声明方法&#xff1a;类型标识符&引用名目标变量名&#xff1b; 【例1】&#xff1a; [cpp] view plaincopy int a; int &raa; //定义…

C++基础知识(二)—— 变量和数据类型

你可能觉得这个“Hellow World”程序用处不大。我们写了好几行代码&#xff0c;编译&#xff0c;然后执行生成的程序只是为了在屏幕上看到一句话。的确&#xff0c;我们直接在屏幕上打出这句话会更快。但是编程并不仅限于在屏幕上打出文字这么简单的工作。为了能够进一步写出可…

C++基础知识(四)—— 操作符/运算符

前面已经学习了变量和常量&#xff0c;我们可以开始对它们进行操作&#xff0c;这就要用到C的操作符。有些语言&#xff0c;很多操作符都是一些关键字&#xff0c; 比如add, equals等等。C的操作符主要是由符号组成的。这些符号不在字母表中&#xff0c;但是在所有键盘上都可以…

Java中如何克隆集合——ArrayList和HashSet深拷贝

2019独角兽企业重金招聘Python工程师标准>>> 编程人员经常误用各个集合类提供的拷贝构造函数作为克隆List&#xff0c;Set&#xff0c;ArrayList&#xff0c;HashSet或者其他集合实现的方法。需要记住的是&#xff0c;Java集合的拷贝构造函数只提供浅拷贝而不是深拷…

C++ 控制结构和函数(一) —— 控制结构

一个程序的语句往往并不仅限于线性顺序结构。在程序的执行过程中它可能被分成两支执行&#xff0c;可能重复某些语句&#xff0c;也可能根据一些判断结果而执行不同的语句。因此C 提供一些控制结构语句 (control structures) 来实现这些执行顺序。 为了介绍程序的执行顺序&…

C++ 控制结构和函数(二) —— 函数I(Functions I)

通过使用函数(functions)我们可以把我们的程序以更模块化的形式组织起来&#xff0c;从而利用C所能提供的所有结构化编程的潜力。 一个函数(function)是一个可以从程序其它地方调用执行的语句块。以下是它的格式&#xff1a; type name ( argument1, argument2, ...) statement…

条件概率的几何解释 由定义计算条件概率 由条件概率公式计算条件概率

A发生&#xff0c;则去掉圈A以外的区域&#xff0c;形成新的样本空间 &#xff08;如果是概率质量函数&#xff0c;则称为归一化&#xff09;然后在A发生的前提下&#xff0c;B的概率为 圈A与圈B的公共区域/圈A 这就是条件概率的几何解释~~~ 甲乙两人各抛一个骰子&#xff0c…

C++ 控制结构和函数(三)—— 函数II(Functions II)

参数按数值传递和按地址传递(Arguments passed by value and by reference) 到目前为止&#xff0c;我们看到的所有函数中&#xff0c;传递到函数中的参数全部是按数值传递的(by value)。也就是说&#xff0c;当我们调用一个带有参数的函数时&#xff0c;我们传递到函数中的是变…

oracle XMLType字段使用方法

2019独角兽企业重金招聘Python工程师标准>>> 刚才研究了一下XMLType字段使用方法 &#xff0c;现在给大家介绍一下。 主要是新增、查询、修改XMLType字段 表结构&#xff1a; 建表sql&#xff1a; -- Create table create table T_BOOK ( ID VARCHAR2(32)…