一、使用传统协议栈的问题
https://blog.csdn.net/u011426247/article/details/113374288
传统的协议栈结构如下: 其存在的不足如下:
- 异步模式的弊端
在没有请求到来的时候,线程将会休眠,当数据到来时,将由操作系统唤醒对应的线程,也就是说内核需要负责线程间频繁的上下文切换,我们是在依靠操作系统调度系统来服务网络包的调度。 - 协议栈的扩展性
协议栈中嵌入了大量用于对接的接口,如果能让应用程序直接接管网络数据包处理、内存管理以及CPU调度,那么性能可以得到一个质的提升。为了达到这个目标,第一个要解决的问题就是绕过Linux内核协议栈 - 多核的可扩展性
在多个CPU核心上平行扩展:尽量让每个核维护独立数据结构;使用原子操作来避免冲突;使用无锁数据结构避免线程间相互等待;设置CPU亲缘性,将操作系统和应用进程绑定到特定的内核上,避免CPU资源竞争;在NUMA架构下尽量避免远端内存访问 - 内存的可扩展性
内存的访问速度永远也赶不上cache和cpu的频率,为了能让性能平行扩展,最好是少访问。
二、解决方案:DPDK
DPDK应用程序是运行在用户空间上利用自身提供的数据平面库来收发数据包,绕过了Linux内核协议栈对数据包处理过程。
如下图所示: 此外,DPDK还具有以下优点:
-
大页内存:传统的页大小是4Kb,如果进程要使用64G内存,则64G/4KB=16000000页,所有在页表项中占用16000000 * 4B=62MB;但是TLB缓存的空间是有限的,不可能存储这么多页面的地址映射关系,所以可能导致TLB miss;如果改成2MB的huge Page,所需页面减少到64G/2MB=2000个。在TLB容量有限的情况下尽可能地多在TLB存放地址映射,极大减少了TLB miss。 -
内存池:dpdk 在用户空间实现了一套精巧的内存池技术,内核空间和用户空间的内存交互不进行拷贝,只做控制权转移,当收发数据包时,就减少了内存拷贝的开销。 -
ringbuffer:dpdk基于 Linux 内核的无锁环形缓冲实现了自己的一套无锁机制,支持多消费者或单消费者出队、多生产者或单生产者入队。 -
CPU 亲和性:dpdk 利用 CPU 的亲和性将一个线程或多个线程绑定到一个或多个 CPU 上,这样在线程执行过程中,就不会被随意调度,一方面减少了线程间的频繁切换带来的开销,另一方面避免了 CPU L1、L2、TLB等缓存的局部失效性,增加了 CPU cache的命中率。
|