分类: LINUX
2012-05-14 10:13:46
标准NetXen网卡驱动接收流程是这样子的:
在网卡驱动中有三个由struct sk_buff数据结构构造的接收环形缓冲区(Normal、Jumbo、Lro),fw会将收到的数据包DMA到相应的环形缓冲区中;网卡驱动则从环形缓冲区中取出数据包,利用netif_receive_skb函数将数据包向内核网络处理层递交。
通过分析代码,可以看出三种环形缓冲区的如下数据:
数据包类型 缓冲区单元的个数 缓冲区单元的大小(字节)
Normal 16384 1760
Jumbo 1024 8062
LRO 64 (48*1024)-512 = 47.5KB
可以看出,针对不同的数据包类型,缓冲区单元的个数以及大小是不同的,在此,我改造网卡驱动原有的三个环形接收缓冲区,并利用mmap将其直接向用户态暴露,这样数据包的生产者就是网卡的firmware,而消费者则成为用户态读包程序;另外,用户态数据包读取程序直接在三个环形缓冲区上轮询。
在内核中实现内存映射,通常被影射的内存是由一整块连续的内存构成的,可以直接映射到用户空间;而netxen网卡中环形缓冲区(一共三个,这里以一个为例进行讨论)则是由多个离散的struct sk_buff构成的,映射的方法就需要有所改变。
后来经过分析发现,映射离散缓冲区并不成问题,问题在于内核中进行mmap必须以页为单位,而构成网卡环形缓冲区的sk_buff是在系统的skb slab池中获取的,其内存格局不为我们所控制,而且显然不服从页边界,这就否定了离散映射的方案。
现在我考虑的思路是这样子的:
(1) 为每一种数据包事先分配一个足够大(单元大小*个数)的连续内核缓冲区,自己控制sk_buff的分配:也就是在驱动调用dev_alloc_skb等内核中类似分配sk_buff的地方,修改其行为,改成从我们的连续内核缓冲区中分配。这样就解决了页边界的问题。
(2) 将分配的连续内核缓冲区映射到用户空间。
– user & kernel
环形缓冲区在用户空间和内核空间的同步,可以采用如下的方案:
(1) 在驱动中实现poll函数,在必要时阻塞用户进程
(2) 用户态读包程序利用poll系统调用轮询数据包
零拷贝的要点包含两个:DMA和内存映射. 本文以内核树中netxen网卡驱动程序为例,讨论了零拷贝的实现方案,抛砖引玉,希望对您有所帮助.