浅谈 Linux 内核开发之网络装配驱动

网络整理 - 06-28

  网络配备是计算机体系构造中必不可少的一局部,处置器假设想与外界通讯,通常都会挑选网络配备作为通讯接口。众所周知,在 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 表构造

浅谈 Linux 内核开发之网络装配驱动[多图]图片1


数据发送流程
网络配备议决 DMA 举行数据发送的流程如 图 2所示。
图 2. 数据发送流程

浅谈 Linux 内核开发之网络装配驱动[多图]图片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. 数据接纳流程

浅谈 Linux 内核开发之网络装配驱动[多图]图片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 三个硬件模块,开发者须要辨别对这三个模块举行原始化。
原始化 PHY 模块,包含配置双工 / 半双工运转方式、配备运转速率和自协商方式等。
原始化 MAC 模块,包含配置配备接口方式等。
原始化 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. 数据发送流程图

浅谈 Linux 内核开发之网络装配驱动[多图]图片5


用户最先创立一个 socket,然后调用 write 之类的写函数议决 socket 访问网络配备,同时将数据保管在 sk_buff 类型的缓冲区中。
socket 接口调用网络配备发送函数(hard_start_xmit),hard_start_xmit 曾经在原始化流程中被挂接成类似于 xx_tx 的详细的发送函数,xx_tx 首要完成如下步骤。
从发送 BD 表中取出一个空闲的 BD。
依据 sk_buff 中保管的数据修正 BD 的属性,一个是数据长度,另一个是数据包缓存指针。值得留意的是,数据包缓存指针对应的必需是物理地址,这是由于 DMA 在获取 BD 中对应的数据时只好识别储存该数据缓存的物理地址。
bd_p->length = skb_p->len; 
bd_p->bufptr = virt_to_phys(skb_p->data); 
修正该 BD 的形态为就绪态,DMA 模块将自动发送处于就绪态 BD 中所对应的数据。
挪动发送 BD 表的指针指向下一个 BD。
DMA 模块开端将处于就绪态 BD 缓存内的数据发送至网络中,当发送完成后自动恢复该 BD 为空闲态。
2. 数据接纳流程
当网络配备接纳到数据时,DMA 模块会自动将数据保管起来并通知处置器来取,处置器议决中缀或许轮询方式发觉有限据接纳进来后,再将数据保管到 sk_buff 缓冲区中,并议决 socket 接口读出来。流程图如下所示。
图 6. 数据接纳流程图

浅谈 Linux 内核开发之网络装配驱动[多图]图片6


网络配备接纳到数据后,DMA 模块搜索接纳 BD 表,取出空闲的 BD,并将数据自动保管到该 BD 的缓存中,修正 BD 为就绪态,并同时触发中缀(该步骤可选)。
处置器能够议决中缀或许轮询的方式检验接纳 BD 表的形态,无论采用哪种方式,它们都须要完成以下步骤。
从接纳 BD 表中取出一个空闲的 BD。
假设现在 BD 为就绪态,检验现在 BD 的数据形态,更新数据接纳统计。
从 BD 中取出数据保管在 sk_buff 的缓冲区中。
更新 BD 的形态为空闲态。
挪动接纳 BD 表的指针指向下一个 BD。
用户调用 read 之类的读函数,从 sk_buff 缓冲区中读出数据,同时释放该缓冲区。
中缀和轮询
Linux 内核在接纳数据时有两种方式可供挑选,一种是中缀方式,另外一种是轮询方式。
中缀方式
假设挑选中缀方式,最先在运用该驱动之前,须要将该中缀对应的中缀类型号和中缀处置顺序注册进去。网络配备驱动在原始化时会将详细的 xx_open 函数挂接在驱动的 open 接口上,xx_open 函数挂接中缀的步骤如下。
request_irq(rx_irq, xx_isr_rx, …… ); 
request_irq(tx_irq, xx_isr_tx, …… ); 
网络配备的中缀普通会分为两种,一种是发送中缀,另一种是接纳中缀。内核须要辨别对这两种中缀类型号举行注册。
发送中缀处置顺序(xx_isr_tx)的任务首要是监控数据发送形态、更新数据发送统计等。
接纳中缀处置顺序(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 网络配备驱动与详细的配备关联很大,因而在实践编程中须要结合详细配备来写驱动代码,我们在开发流程中要格外留意驱动的优化,由于网络配备驱动的好坏将直接影响到整个系统的功用。