Linux 网卡驱动学习(五)(收发包具体过程)

函数接口

设备初始化函数

网络设备驱动在 Linux 内核中是以内核模块的形式存在的,对应于模块的初始化,需要提供一个初始化函数来初始化网络设备的硬件寄存器、配置 DMA 以及初始化相关内核变量等。设备初始化函数在内核模块被加载时调用,它的函数形式如下:

 static int __init xx_init (void) {
   ……
 }
 module_init(xx_init);   // 这句话表明模块加载时自动调用 xx_init 函数

设备初始化函数主要完成以下功能:

1. 硬件初始化

因为网络设备主要分为 PHY、MAC 和 DMA 三个硬件模块,开发者需要分别对这三个模块进行初始化。

  1. 初始化 PHY 模块,包括设置双工 / 半双工运行模式、设备运行速率和自协商模式等。
  2. 初始化 MAC 模块,包括设置设备接口模式等。
  3. 初始化 DMA 模块,包括建立 BD 表、设置 BD 属性以及给 BD 分配缓存等。

2. 内核变量初始化

初始化并注册内核设备。内核设备是属性为 net_device 的一个变量,开发者需要申请该变量对应的空间(通过 alloc_netdev 函数)、设置变量参数、挂接接口函数以及注册设备(通过 register_netdev 函数)。

常用的挂接接口函数如下:

 net_device *dev_p;
 dev_p->open              = xx_open;   // 设备打开函数
 dev_p->stop              = xx_stop;   // 设备停止函数
 dev_p->hard_start_xmit = xx_tx;     // 数据发送函数
 dev_p->do_ioctl          = xx_ioctl; // 其它的控制函数
……

数据收发函数

数据的接收和发送是网络设备驱动最重要的部分,对于用户来说,他们无需了解当前系统使用了什么网络设备、网络设备收发如何进行等,所有的这些细节对于用户都是屏蔽的。Linux 使用 socket 做为连接用户和网络设备的一个桥梁。用户可以通过 read / write 等函数操作 socket,然后通过 socket 与具体的网络设备进行交互,从而进行实际的数据收发工作。

Linux 提供了一个被称为 sk_buff 的数据接口类型,用户传给 socket 的数据首先会保存在 sk_buff 对应的缓冲区中,sk_buff 的结构定义在 include/linux/skbuff.h 文件中。它保存数据包的结构示意图如下所示。

图 4. sk_buff 数据结构图

1. 数据发送流程

当用户调用 socket 开始发送数据时,数据被储存到了 sk_buff 类型的缓存中,网络设备的发送函数(设备初始化函数中注册的 hard_start_xmit)也随之被调用,流程图如下所示。

图 5. 数据发送流程图

  1. 用户首先创建一个 socket,然后调用 write 之类的写函数通过 socket 访问网络设备,同时将数据保存在 sk_buff 类型的缓冲区中。
  2. socket 接口调用网络设备发送函数(hard_start_xmit),hard_start_xmit 已经在初始化过程中被挂接成类似于 xx_tx 的具体的发送函数,xx_tx 主要实现如下步骤。
    1. 从发送 BD 表中取出一个空闲的 BD。
    2. 根据 sk_buff 中保存的数据修改 BD 的属性,一个是数据长度,另一个是数据包缓存指针。值得注意的是,数据包缓存指针对应的必须是物理地址,这是因为 DMA 在获取 BD 中对应的数据时只能识别储存该数据缓存的物理地址。

       bd_p->length = skb_p->len;
       bd_p->bufptr = virt_to_phys(skb_p->data);
    3. 修改该 BD 的状态为就绪态,DMA 模块将自动发送处于就绪态 BD 中所对应的数据。
    4. 移动发送 BD 表的指针指向下一个 BD。
  3. DMA 模块开始将处于就绪态 BD 缓存内的数据发送至网络中,当发送完成后自动恢复该 BD 为空闲态。

2. 数据接收流程

当网络设备接收到数据时,DMA 模块会自动将数据保存起来并通知处理器来取,处理器通过中断或者轮询方式发现有数据接收进来后,再将数据保存到 sk_buff 缓冲区中,并通过 socket 接口读出来。流程图如下所示。

图 6. 数据接收流程图

  1. 网络设备接收到数据后,DMA 模块搜索接收 BD 表,取出空闲的 BD,并将数据自动保存到该 BD 的缓存中,修改 BD 为就绪态,并同时触发中断(该步骤可选)。
  2. 处理器可以通过中断或者轮询的方式检查接收 BD 表的状态,无论采用哪种方式,它们都需要实现以下步骤。
    1. 从接收 BD 表中取出一个空闲的 BD。
    2. 如果当前 BD 为就绪态,检查当前 BD 的数据状态,更新数据接收统计。
    3. 从 BD 中取出数据保存在 sk_buff 的缓冲区中。
    4. 更新 BD 的状态为空闲态。
    5. 移动接收 BD 表的指针指向下一个 BD。
  3. 用户调用 read 之类的读函数,从 sk_buff 缓冲区中读出数据,同时释放该缓冲区。

中断和轮询

Linux 内核在接收数据时有两种方式可供选择,一种是中断方式,另外一种是轮询方式。

中断方式

如果选择中断方式,首先在使用该驱动之前,需要将该中断对应的中断类型号和中断处理程序注册进去。网络设备驱动在初始化时会将具体的 xx_open 函数挂接在驱动的 open 接口上,xx_open 函数挂接中断的步骤如下。

 request_irq(rx_irq, xx_isr_rx, …… );
 request_irq(tx_irq, xx_isr_tx, …… );

网络设备的中断一般会分为两种,一种是发送中断,另一种是接收中断。内核需要分别对这两种中断类型号进行注册。

  1. 发送中断处理程序(xx_isr_tx)的工作主要是监控数据发送状态、更新数据发送统计等。
  2. 接收中断处理程序(xx_isr_rx)的工作主要是接收数据并传递给协议层、监控数据接收状态、更新数据接收统计等。

对于中断方式来说,由于每收到一个包都会产生一个中断,而处理器会迅速跳到中断服务程序中去处理收包,因此中断接收方式的实时性高,但如果遇到数据包流量很大的情况时,过多的中断会增加系统的负荷。

轮询方式

如果采用轮询方式,就不需要使能网络设备的中断状态,也不需要注册中断处理程序。操作系统会专门开启一个任务去定时检查 BD 表,如果发现当前指针指向的 BD 非空闲,则将该 BD 对应的数据取出来,并恢复 BD 的空闲状态。

由于是采用任务定时检查的原理,从而轮询接收方式的实时性较差,但它没有中断那种系统上下文切换的开销,因此轮询方式在处理大流量数据包时会显得更加高效。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-10 22:58:13

Linux 网卡驱动学习(五)(收发包具体过程)的相关文章

Linux 网卡驱动学习(六)(应用层、tcp 层、ip 层、设备层和驱动层作用解析)

本文将介绍网络连接建立的过程.收发包流程,以及当中应用层.tcp层.ip层.设备层和驱动层各层发挥的作用. 1.应用层 对于使用socket进行网络连接的server端程序.我们会先调用socket函数创建一个套接字: fd = socket(AF_INET, SOCK_STREAM, 0); 以上指定了连接协议,socket调用返回一个文件句柄,与socket文件相应的inode不在磁盘上,而是存在于内存. 之后我们指定监听的port.同意与哪些ip建立连接,并调用bind完毕port绑定:

Linux 网卡驱动学习之(八)(基于 MAC 地址转发数据)

1.构建MAC地址表 交换机技术在转发数据前必须知道它的每一个端口所连接的主机的MAC地址,构建出一个MAC地址表.当交换机从某个端口收到数据帧后,读取数据帧中封装的目的地MAC地址信息,然后查阅事先构建的MAC地址表,找出和目的地地址相对应的端口,从该端口把数据转发出去,其他端口则不受影响,这样避免了与其它端口上的数据发生碰撞.因此构建MAC地址表是交换机的首要工作.下面举例说明交换机建立地址表的过程. [分析]假设主机A向主机C发送一个数据帧(每一个数据帧中都包含有源MAC地址和目的MAC地

Linux 网卡驱动学习(net_device 等数据结构)

[摘要]前文对网络驱动例子进行一个简单的梳理总结,本文贴出 net_device 的数据结构以及一些驱动中常用的数据结构. 1.网络设备驱动结构 下图摘自http://blog.chinaunix.net/uid-20672257-id-3147768.html 1).网络协议接口层向网络层协议提供提供统一的数据包收发接口,不论上层协议为ARP还是IP,都通过dev_queue_xmit()函数发送数据,并通过netif_rx()函数接受数据.这一层的存在使得上层协议独立于具体的设备. 2).网

Linux 网卡驱动学习(分析一个虚拟硬件的网络驱动例子)

在Linux,网络分为两个层,分别是网络堆栈协议支持层,以及接收和发送网络协议的设备驱动程序层.网络堆栈是硬件中独立出来的部分,主要用来支持TCP/IP等多种协议,网络设备驱动层是连接网络堆栈协议层和网络硬件的中间层. 网络设备驱动程序的主要功能是: (1)模块加载或内核启动相关的初始化处理 (2)清除模块时的处理 (3)网络设备的检索和探测 (4)网络设备的初始化和注册 (5)打开或关闭网络设备 (6)发送网络数据 (7)接收网络数据 (8)中断处理(在发送完数据时,硬件向内核产生一个中断,告

Linux 网卡驱动学习(一)(分析一个虚拟硬件的网络驱动样例)

在Linux,网络分为两个层,各自是网络堆栈协议支持层,以及接收和发送网络协议的设备驱动程序层. 网络堆栈是硬件中独立出来的部分.主要用来支持TCP/IP等多种协议,网络设备驱动层是连接网络堆栈协议层和网络硬件的中间层. 网络设备驱动程序的主要功能是: (1)模块载入或内核启动相关的初始化处理 (2)清除模块时的处理 (3)网络设备的检索和探測 (4)网络设备的初始化和注冊 (5)打开或关闭网络设备 (6)发送网络数据 (7)接收网络数据 (8)中断处理(在发送完数据时.硬件向内核产生一个中断.

Linux 网卡驱动学习(缓存描述符 Buffer Description)

[摘要]上文我们罗列出驱动中重要的数据结构,Linux 内核中对网络设备进行描述的核心结构类型叫做 net_device,net_device 结构定义在 include/linux/netdevice.h 文件中.该结构的字段可以分为以下几类. 1.net_device 结构分类 (1)全局信息 该类中包含了设备名(name 字段).设备状态(state 字段).设备初始化函数(init 字段)等. (2)硬件信息 该类中包含了设备内存使用情况(mem_end 和 mem_start 字段).

Linux 网卡驱动学习(二)(网络驱动接口小结)

[摘要]前文我们分析了一个虚拟硬件的网络驱动样例.从中我们看到了网络设备的一些接口.事实上网络设备驱动和块设备驱动的功能比較相似,都是发送和接收数据包(数据请求). 当然它们实际是有非常多不同的. 1.引言 首先块设备在/dev文件夹下有设备节点.而网络设备没有这种设备入口. read,write等常规的文件接口在网络设备下也没有意义. 最大的差别在于:块设备仅仅响应内核的数据请求:而网络设备驱动要异步地接收来自外部的数据包.简单地说.块设备驱动是被要求数据传输而网络设备是主动请求数据传输.网络

Linux 网卡驱动学习(七)(MAC 与 IP)

IP 地址是服务商给你的,MAC地址是你的网卡物理地址. 1.MAC 地址 在OSI(Open System Interconnection,开放系统互连)7层网络协议(物理层,数据链路层,网络层,传输层,会话层,表示层,应用层)参考模型中,第二层为数据链路层(Data Link).它包含两个子层,上一层是逻辑链路控制(LLC:Logical Link Control),下一层即是我们前面所提到的MAC(Media Access Control)层,即介质访问控制层.所谓介质(Media),是指

linux内核驱动学习(八)----驱动分类 | 学习方法 | 硬件访问

驱动分类: 对于驱动,我们一般按两种方法进行分类:常规分类法和总线分类法. 按照常规分类法,可以分为以下三类: 1.字符设备: 以字节为最小访问单位的设备.一般通过字符设备文件来访问字符设备驱动程序.字符驱动程序则负责驱动字符设备, ,这样的驱动通常支持open.close.read.write系统调用,应用程序可以通过设备文件(比如/dev/ttySAC0等)来访问字符设备(串口).例如:串口\led\按键 2.块设备: 以块(一般512字节)为最 小传输单位的设备.大多数UNIX系统中,块设