正文
linux 系统使用中断方式来通知 CPU 来处理数据包,在大流量的场景下,中断会占据大量的时间,影响数据包的转发效率,在 DPVS 中使用了 DPDK 提供的 PMD 驱动通过轮询方式收发包,避免了在大流量场景下的中断开销。同时由于 PMD 驱动的存在,使得数据包不再经过内核协议栈,这对于提高包转发效率也至关重要。
Memory pool 即为内存池,也被称为动态内存分配,内存池能够有效的避免内存碎片,并且 mempool 提供了一些其他可选服务,例如核本地缓存和内存对齐辅助器,内存对齐辅助器可确保对象被均匀地填充到所有 DRAM 或 DDR3 通道上(对象被均匀的放在通道上,会提高存取速度)。
无锁化队列
DPDK 实现了 ring library 来提供一个无锁的、有限大小的环形 FIFO 队列,它支持多消费者或单消费者出队和多生产者或单生产者入队,同时也支持批量、爆发 (burst) 出、入队。相比于链表,每次的入队或出队只需比较 void * 大小的数据并且只需执行一次 CAS(
Compare-And-Swap
)操作且该操作是原子的。
session 表无锁化
对于多队列网卡,RSS 能够根据数据报的元信息将数据包分散到不同的网卡队列上,每个网卡对应的 CPU 会从网卡队列中读取数据包进行处理。但对于 FULLNAT 模式而言,其入向流量和出向流量在经过 RSS hash 后可能会分布在不同 CPU 绑定的网卡队列上,例如:入向数据包携带的元信息为(cip1,cport0,vip1,vport1),此时 RSS(cip1,cport0,vip1,vport1)= hash_value1 → cpu0;出向数据报携带的元信息为(rip1,rport1,lip1,lport1),此时 RSS(rip1,rport1,lip1,lport1)= hash_value2 → cpu1。在不同的 CPU 上处理意味着 session 表需要加锁(可能引发并发操作)。为了无锁化,需要使入向和会向数据包经过同一网卡队列即同一 CPU(同一 CPU 将串行处理两个方向的数据包,session 表无需加锁)。DPVS 的实现是利用 INTEL 网卡的对称 RSS,选择合适的 RSS_KEY 保证 RSS(rip1,rport1,lip1,lport1)→ cpu0,此时入向数据包和出向数据包都经过 cpu0 处理,而无需加锁。
在 NUMA 系统下,CPU 访问它自己的本地存储器比非本地存储器更快。在分配内存时,使用 DPDK 提供的 API 可以分配某个 CPU 所在 NUMA 的内存,在初始化网卡收发队列时,同样可以控制网卡和 CPU 和亲和性。
操作系统会对物理内存分成固定大小的页,按照页来进行分配和释放,编程时只使用虚拟内存进行编程,不用关系物理内存的映射,而处理器在寄存器收到虚拟地址之后,需要根据页表把虚拟地址转换成真正的物理地址。TLB(
translation lookaside buffer
)是一种小、专用、快速的硬件缓冲,它只包括页表中的一小部分条目,利用虚拟地址查找物理地址时,先根据页号在 TLB 查找,如果命中则得到页内地址,访问内存;否则则在内存中的页表中得到页内地址,将其存入 TLB,访问内存。从虚拟地址到物理地址的转换逻辑我们知道:
如果一个程序使用了 512 个内容页也就是 2MB(512*4KB=2048KB=2MB)大小,那么需要 512 个页表表项才能保证不会出现 TLB 不命中的情况。
通过上面的介绍,TLB 是有限的,随着程序的变大或者程序使用内存的增加,势必会增加 TLB 的使用项,最后导致 TLB 出现不命中的情况。因此,大页的优势就显现出来了。如果采用 2MB 作为分页的基本单位,那么只需要一个表项(2MB/2MB=1)就可以保证不出现 TLB 不命中的情况;对于消耗内存以 GB 为单位的大型程序,可以采用 1GB 为单位作为分页的基本单位,减少 TLB 不命中的情况。