浅谈 Linux 内核开发之网络设备驱动

Linux内核协议栈的详解完整版 详解Linux协议栈的数据流向,SOCKET的操作流程,unicast multicast等等的区别。 立即下载
 

网络设备介绍

网络设备是计算机体系结构中必不可少的一部分,处理器如果想与外界通信,通常都会选择网络设备作为通信接口。众所周知,在 OSI(Open Systems Interconnection,开放网际互连)中,网络被划分为七个层次,从下到上分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。我们所讲的网络设备也包括两个层次,一层叫做 MAC(Media Access Control)层,对应于 OSI 的数据链路层;另一层叫做 PHY(Physical Layer)层,对应于物理层。

常用的网络设备有很多,比如 PPC85XX 的 TSEC、AMCC 440GX 的 EMAC、INTEL 的 82559 等,它们的工作原理基本相同。

DMA 介绍

网络设备的核心处理模块是一个被称作 DMA(Direct Memory Access)的控制器,DMA 模块能够协助处理器处理数据收发。对于数据发送来说,它能够将组织好的数据自动发出,无需处理器干预;对于数据接收来说,它能够将收到的数据以一定的格式组织起来,通知处理器,并等待处理器来取。

DMA 模块收发数据的单元被称为 BD(Buffer Description,缓存描述符),每个包都会被分成若干个帧,而每个帧则被保存在一个 BD 中。BD 结构通常包含有以下字段:

 typedef struct { 
    void *bufptr;    /* 保存当前 BD 对应缓存的起始地址  */ 
    int length;      /* 保存缓存中存储的数据包长度      */ 
    int sc;           /* 保存当前 BD 的状态信息          */ 
 } BD_STRUCT; 

所有的 BD 就组成了一张 BD 表,如图 1 所示,一般来说发送方向和接收方向的 BD 表是各自独立的。


图 1. BD 表结构
图 1. BD 表结构

数据发送流程

网络设备通过 DMA 进行数据发送的流程如 图 2所示。


图 2. 数据发送流程
图 2. 数据发送流程

图中各步骤的具体含义描述如下:

(1)协议层通知处理器开始发送数据;

(2)处理器从 BD 表中取出一个 BD,将需要发送的数据拷贝至当前 BD 对应的缓存内,并设置好 BD 的状态;

(3)处理器通知网络设备开始发送数据;

(4)MAC 模块通知 DMA 单元开始发送数据;

(5)DMA 模块操作 BD 表,取出当前有效 BD;

(6)DMA 模块将当前 BD 对应缓存内的数据发送至 MAC 模块;

(7)MAC 模块将这些数据发送到网络中;

(8)网络设备通知处理器数据发送完毕;

(9)处理器通知协议层发送下面一帧数据。

其中步骤(4)~(8)是硬件自动完成的,不需要软件的干预,如此可以节省处理器的工作量。

数据接收流程

网络设备通过 DMA 进行数据接收的流程如图 3 所示。


图 3. 数据接收流程
图 3. 数据接收流程

图中各步骤的具体含义描述如下:

(1)处理器初始化 BD 表;

(2)处理器初始化网络设备;

(3)MAC 模块从网络中接收数据;

(4)MAC 模块通知 DMA 模块来取数据;

(5)DMA 模块从 BD 表中取出合适的 BD;

(6)MAC 模块将数据发送至当前 BD 对应的缓存内;

(7)网络设备通知处理器开始接收数据(以中断方式或轮询方式);

(8)协议层从当前的 BD 缓存内取走数据。

其中步骤(3)~(6)是硬件自动完成的,不需要软件的干预,如此可以节省处理器的工作量。


Linux 网络设备驱动模型

数据结构

数据结构

Linux 内核中对网络设备进行描述的核心结构类型叫做 net_device,net_device 结构定义在 include/linux/netdevice.h 文件中。该结构的字段可以分为以下几类。

全局信息

该类中包含了设备名(name 字段)、设备状态(state 字段)、设备初始化函数(init 字段)等。

硬件信息

该类中包含了设备内存使用情况(mem_end 和 mem_start 字段)、中断号(irq 字段)、IO 基地址(base_addr 字段)等。

接口信息

该类中包含了 MAC 地址(dev_addr 字段)、设备属性(flag 字段)、最大传输单元(mtu 字段)等。

设备接口函数

该类中包含了当前设备所提供的所有接口函数,比如设备打开函数(open 字段),该函数负责打开设备接口,当用户使用 ifconfig 命令配置网络时,该函数默认被调用;设备停止函数(stop 字段),该函数负责关闭设备接口;数据发送函数(hard_start_xmit 字段),当用户调用 socket 开始写数据时,该函数被调用,并负责往网络设备中发送数据。

函数接口

设备初始化函数

网络设备驱动在 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 数据结构图
图 4. sk_buff 数据结构图

1. 数据发送流程

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


图 5. 数据发送流程图
图 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. 数据接收流程图
图 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 的空闲状态。

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


Linux 网络设备驱动优化

随着科技的不断发展,网络设备所能承载的速率在不断提升,当前流行的网络设备普遍都能支持 10Mbps / 100Mbps / 1Gbps 这三种速率。虽然网络设备的硬件性能在不断的提升,但是实际在 Linux 系统中其运行性能(收发包速率)真能达到多达 1Gbps 的水平吗?这和处理器的性能有关,一般来说我们运行的系统中报文的收发速率是达不到 1Gbps 的(因为我们不可能将所有处理器的资源都贡献给报文的收发),但是我们可以在有限的条件下尽可能的采取一些优化手段提高网络设备的运行性能。

Cache 的应用

Cache 位于存储系统金字塔的顶层(下面一层是内存),Cache 的容量不大(一级 Cache 一般是几十 KB,二级 Cache 一般是几 MB),但是它的访问速率却是内存的几十倍。因此如果处理器通过 Cache 来访问内存,将会极大的提高访问速率。在网络设备的数据收发中,恰当的应用 Cache 可以优化驱动的性能。下面列举几点 Cache 的优化措施。

合理设置内存属性

内存的页表有多种属性,其中有一项就是是否通过 Cache 访问。在给 BD 表配置内存时,这些被分配的内存属性需要支持 Cache 访问。

Cache 的访问还有两种方式:一种是写回操作(Write Back),处理器更新内存数据时,该数据首先保存在 Cache 中,Cache 并不及时将数据更新进内存,而是等到 Cache 需要再次更新时才会将数据写回到内存中。另一种是写穿操作(Write Through),处理器更新内存数据时,该数据首先保存在 Cache 中,Cache 随即将数据立刻更新进内存。显而易见,写回操作的性能比写穿操作更高,通常我们设置内存页表属性为写回方式。

数据收发时的 Cache 操作

在内存支持 Cache 且采用写回方式的情况下,当发送数据时,处理器先将数据写进 Cache,如果 DMA 模块直接从内存中取出数据发送的话,该数据将与 Cache 并不一致,因此在驱动程序中,需要将 Cache 中的数据更新到内存,然后再通知 DMA 进行发送。

当接收数据时,DMA 模块会将数据收到内存中,如果这时候处理器从该内存接收数据的话,处理器会从 Cache 中取数据,但是 Cache 并不知道内存已经被更新,这就会导致接收到的数据与实际不符,因此在驱动程序中,需要在接收数据之前刷新一下该 Cache,以保证 Cache 与内存的一致性。

需要说明的是,并不是所有处理器都需要以上操作,有的处理器所带的 DMA 控制器是能感知 Cache(IO-Cache Coherence)的,它们能够自动进行上述的 Cache 操作,因此对于这类处理器,驱动程序中无需关注 Cache。

中断还是轮询?

前面曾经介绍过,网络设备驱动支持两种接收数据的方式,一种是中断,另一种是轮询,在数据流量比较大的情况下,可以考虑采用轮询的方式以达到更高的效率。

当采用轮询方式时,还有一个不得不考虑的问题,那就是轮询任务优先级的选择,众所周知,当任务优先级高时,该任务不会被其他的低优先级任务所打断,从而可以保证处理器能够专心完成数据接收工作;但如果任务优先级低时,一旦发生了其他高优先级的任务,处理器会将当前的数据接收工作暂停,转而执行别的任务,如此会影响网络设备驱动的效率。因此驱动设计者需要结合实际情况,恰当的选择任务的优先级。

设备接口模式

有时候我们会发现虽然网络设备号称有 100Mbps 的速率,但是实际数据收发却非常慢,遇到这种情况,我们首先需要检查网络设备接口模式是否设置正确。

PHY 模块接口模式

PHY 模块的接口模式有两种,强制模式(强制 10M / 100M / 1G 等)和自协商模式。究竟选择哪种模式需要看当前 PHY 模块所连接的对端 PHY 状态才行,如果对端设置的是自协商模式,本端的 PHY 模块也需要相应设置成自协商,如此就能够保证协商出来的结果是当前链路所能支持的最大速率。反之,如果对端设置成强制模式,本端也需要设置成强制,且强制速率要与对端设置的强制速率相同。

MAC 模块接口模式

MAC 模块对于不同的速率(10M / 100M / 1G 等)也会有不同的接口模式选择,如果设置的模式与 PHY 模块所运行的速率不匹配的话,会极大的影响网络设备数据收发的速度。因此在初始化 MAC 模块时,需要检查 PHY 模块的运行速率,从而选择恰当的接口模式。

每个 PHY / MAC 模块设备的接口模式选择都不尽相同,因此在开发网络设备驱动时,需要明确所使用的设备,并在该设备初始化时正确配置其接口模式。


结束语

Linux 网络设备驱动与具体的设备关联很大,因此在实际编程中需要结合具体设备来写驱动代码,我们在开发过程中要格外注意驱动的优化,因为网络设备驱动的好坏将直接影响到整个系统的性能。


参考资料

NVIDIA DGX Spark实战指南:从开箱到部署200B参数大模型 本文详细介绍了NVIDIA DGX Spark桌面AI超级计算机的开箱体验与实战部署指南。文章深入解析了其Grace Blackwell架构与128GB统一内存的核心优势,并逐步演示了从系统初始化、运行200B参数大模型到进行QLoRA微调的全过程。该设备将高达1 PetaFLOP的AI算力浓缩于桌面,为开发者和研究团队提供了本地化、高性能的AI开发与部署一体化解决方案。 阅读详情

相关推荐

3分钟搞懂欧式距离变换(EDT):从暴力算法到Felzenszwalb的演进史

本文系统梳理了欧式距离变换(EDT)从暴力算法到高效线性算法的演进历程。重点解析了光栅扫描算法和Felzenszwalb算法的核心思想与实现,并探讨了其在机器人路径规划中生成欧几里得符号距离场(ESDF)的关键应用,为开发者提供了清晰的算法选型与实战指南。

fern8的博客 978

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

本文将介绍网络连接建立的过程、收发包流程,以及当中应用层、tcp层、ip层、设备层和驱动层各层发挥的作用。 1、应用层 对于使用socket进行网络连接的server端程序。我们会先调用socket函数创建一个套接字: fd = socket(AF_INET, SOCK_STREAM, 0); 以上指定了连接协议,socket调...

diandingyin9417的博客 330

谷粒商城篇章9 ---- P248-P261/P292-P294 ---- 消息队列【分布式高级篇六】

RabbitMQ消息队列、RabbitMQ延时队列。

weixin_48568302的博客 1550

网卡构造:MAC与PHY的关系,GMAC介绍

对于上述三部分,并不一定都是独立的芯片,主要有以下几种情况 CPU内部集成了MAC和PHY (难度较高) CPU内部集成MAC,PHY采用独立芯片 (主流方案) CPU不集成MAC和PHY,MAC和PHY采用独立芯片或者集成芯片 (高端采用) 在软件上对网口的操作通常分为下面几步: 为数据收发分配内存; 初始化MAC寄存器; 初始化PHY寄存器(通过MIIM); 启动收发;

学海无涯的专栏 1万+

【Ultrascale+ MPSOC学习记录】10、PS端以太网使用lwip

还是参考ALINX教程,学习PS端以太网如何使用,从教程中可以看到,开发板使用了一个xilinx默认不支持的phy JL2121,所以需要修改lwip的库源码。

ryo891012的博客 881

Linux平台CCS安装配置完整示例

手把手讲解在Linux系统中完成CCS安装与环境配置的全过程,涵盖关键步骤与常见问题解决,是查找ccs安装教程时不可多得的实战指南。

weixin_42601134的博客 515

网卡接收数据 及 操作系统内核收包过程

1.1 网络包通过物理介质传到接收端的phy芯片1.2 phy芯片通过协议传到MAC芯片rx queue中1.3 MAC芯片通过专用DMA将网络包搬运到网卡驱动程序预先分配好的rx ringbuffer中,当一个网络包搬运完后,给CPU触发中断1.4 CPU响应网卡中断,关DMA中断,执行网卡驱动程序的中断处理函数,触发NET_TX软中断1.5 将rx ringbuffer中的数据搬运到网络协议栈中处理,取完后DMA中断。

ql_gome的博客 646

攻克低照度检测难题:YOLOv11主干网络增强新方案PE-YOLO详解

编码器部分# 解码器部分self.dec_conv2 = nn.Conv2d(512, 128, 3, padding=1) # 跳跃连接self.dec_conv3 = nn.Conv2d(256, 64, 3, padding=1) # 跳跃连接self.dec_conv4 = nn.Conv2d(128, 32, 3, padding=1) # 跳跃连接# 编码路径# 解码路径(带跳跃连接)

FJN110的博客 106

一文讲透Linux网络设备驱动框架及编写步骤

一文讲透Linux网络设备驱动框架及编写步骤

qq_41483419的博客 5309

超详细教程YoloV8官方推荐免费数据集网站Roboflow一键导出Voc、COCO、Yolo、Csv等格式

YoloV8官方推荐了一个数据集网站,就是Roboflow。Roboflow是一个数据集管理平台,提供了免费的数据集,同时也支持上传自己的数据集进行格式转换。使用Roboflow,开发者可以方便地获取所需格式的数据集,无需手动转换格式。此外,Roboflow还提供了多种数据预处理、数据增强等功能,可帮助开发者更好地优化训练数据

Snu77的博客 2万+

手把手教你学Simulink——机器人力控场景实例:基于Simulink的永磁同步电机重力补偿力矩控制仿真

本文介绍了基于Simulink的机器人重力补偿力矩控制仿真方法。针对机器人悬停时电机发热、抖动等问题,提出通过前馈补偿重力转矩的解决方案。文章详细阐述了从机器人动力学建模、重力补偿计算、PMSM转矩控制到系统集成的完整实现流程,并以2-DOF机械臂为例验证了补偿效果。结果表明,重力补偿能显著降低电机电流和拖拽力,提高系统柔顺性。文中还提供了高级功能扩展方向,如参数辨识、摩擦补偿等,为实际工程应用提供了参考框架。

MHD0815的博客 154

linux网络流程分析(一)---网卡驱动

分析linux网络的书已经很多了,包括《追踪Linux TCP/IP代码运行》《Linux内核源码剖析——TCP/IP实现》,这里我只是从数据包在linux内核中的基本流程来分析,尽可能的展现一个主流程框架。 内核如何从网卡接收数据,传统的过程:1.数据到达网卡;2.网卡产生一个中断给内核;3.内核使用I/O指令,从网卡I/O区域中去读取数据; 我们在许多网卡驱动中(很老那些)...

weixin_30642267的博客 862

【WRF-Urban】URBPARM_LCZ.TBL 查找表解释及内容

URBPARM_LCZ.TBL 查找表解释及内容

WW、forever的博客 1754

Linux 内核开发之网络设备驱动浅谈

转载自:http://www.ibm.com/developerworks/cn/linux/l-cn-networkdriver/ 本文介绍了网络设备的基本概念,并从 Linux 内核的角度出发,介绍了网络设备驱动的开发方法和开发中的注意事项。   网络设备介绍   网络设备是计算机体系结构中必不可少的一部分,处理器如果想与外界通信,通常都会选择网络设备作为通信接口。众所周知,在 OS...

记不住就笔记 690

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

上一篇: Linux下ARM汇编
下一篇: Linux操作系统内核启动参数详细解析
david104
博客等级 码龄16年 1粉丝 6原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值