目录
什么是dpdk
内核协议栈 vs dpdk
内核收包的两种方式
中断模式
轮询模式
内核协议栈收包流程
DPDK收包
1、UIO框架
2、用户态驱动pmd轮询与uio中断的关系
3、 mellanox dpdk
混和中断轮询模式
DPDK核心部件库
DPDK 内存结构
hugetlb
TLB
为什么要使用大页
Mbuf
Mempool
rte_mbuf和rte_mempool关系
DPDK运行demo
DPDK转发模型
run to completion
pipeline
什么是dpdk
DPDK是INTEL公司开发的一款高性能的网络驱动组件,旨在为数据面应用程序提供一个简单方便的,完整的,快速的数据包处理解决方案,主要技术有用户态、轮询取代中断、零拷贝、网卡RSS、访存DirectIO等。
内核协议栈 vs dpdk
内核收包的两种方式
中断模式
网卡收到包提醒CPU发生中断处理包
轮询模式
CPU主动持续检查网卡是否有数据包到来
内核协议栈收包流程
TX/RX rings
● Circular queue
● Shared between NIC and NIC driver
● Content: Length + packet buffer pointer
- 数据包从外面的网络进入物理网卡。如果目的地址不是该网卡,且该网卡没有开启混杂模式,该包会被网卡丢弃。(混杂模式:一台机器的网卡能够接收所有经过它的数据流,而不论其目的地址是否是它)
- 网卡将数据包通过DMA的方式写入到指定的内存地址,即图中所示的RX ring,该地址由网卡驱动分配并初始化。
- 驱动模块中断处理:
top-half interrupt processing
- 网卡通过硬件中断(IRQ)通知CPU,告诉它有数据来了。这时CPU会中断正在进行的工作从用户态切换到内核态
- CPU根据中断表,调用已经注册的中断函数,这个中断函数会调到驱动程序(NIC Driver)中相应的函数
Bottom-half processing
4. L3/L4 层处理
5. 应用层处理
缺陷:
- 中断开销突出,大量数据到来会触发频繁的中断(softirq)开销导致系统无法承受
- 需要把包从内核缓冲区拷贝到用户缓冲区,带来系统调用和数据包复制的开销
- 对于很多网络功能节点来说,TCP/IP协议并非是数据转发环节所必需的
- NAPI/Netmap等虽然减少了内核到用户空间的数据拷贝,但操作系统调度带来的 cache替换也会对性能产生负面影响
具体缺陷参考:DPDK 完全内核旁路技术实现
DPDK收包
DPDK重载了网卡驱动,将数据包的控制平面和数据平面分离,驱动在收到数据包后不再硬中断通知CPU,而是让数据包通过内核旁路协议栈绕过Linux内核协议栈,并通过零拷贝技术存入内存,应用层的程序可以通过DPDK提供的接口读取数据包。
1、UIO框架
dpdk通过linux的UIO技术byapass内核,避免了内核中断爆炸和大量数据拷贝的方法,在用户空间能够直接和硬件进行交互。传统的收发数据包方式,首先网卡通过中断方式通知Linux内核协议栈对数据包进行处理,内核协议栈先会对数据包进行合法性进行必要的校验,然后判断数据包目标是否为本机的Socket,满足条件则会将数据包拷贝一份向上递交到用户态Socket来处理。
为了使得网卡驱动(PMD Driver)运行在用户态,实现内核旁路,Linux提供了UIO(User Space IO)机制。使用UIO可以通过 read感知中断,通过 mmap实现和网卡设备的通讯。
UIO是用户态的一种IO技术,是DPDK能够绕过内核协议栈,提供用户态PMD Driver支持的基础。DPDK架构在Linux内核中安装了IGB_UIO(igb_uio.ko和kni.ko.IGB_UIO)模块,以此借助UIO 技术来截获中断,并重设中断回调行为,从而绕过内核协议栈后续处理流程,并且IGB_UIO会在内核初始化的过程中将网卡硬件寄存器映射到用户态
dpdk一般用的都是igb_uio驱动, 在系统加载igb_uio驱动后,每当有网卡和igb_uio驱动进行绑定时, 就会在/dev目录下创建一个uio设备,例如/dev/uio1。uio设备是一个接口层,用于将pci网卡的内存空间以及网卡的io空间暴露给应用层。通过这种方式,应用层访问uio设备就相当于访问网卡。具体来说,当有网卡和uio驱动绑定时,被内核加载的igb_uio驱动, 会将pci网卡的内存空间,io空间保存在uio目录下,例如/sys/class/uio/uio1/maps文件中,同时也会保存到pci设备目录下的uio文件中。这样应用层就可以访问这两个文件中的任意一个文件里面保存的地址空间,然后通过mmap将文件中保存网卡的物理内存映射成虚拟地址, 应用层访问这个虚拟地址空间就相当于访问pci设备。
注:因此前期的dpdk 使用的时候必须先有网卡和igb_uio驱动进行绑定
用户态驱动PMD通过轮询模式直接bypass内核从网卡收包; 内核态驱动igb_uio,用于将pci网卡的内存空间,io空间暴露给应用层,供应用层访问,同时会处理在网卡的硬件中断(控制中断而不是数据中断)。linux uio框架提供了一些给igb_uio驱动调用的接口,例如uio_open打开uio; uio_release关闭uio; uio_read从uio读取数据; uio_write往uio写入数据。
2、用户态驱动pmd轮询与uio中断的关系
针对 Intel 网卡,DPDK 实现了基于轮询方式的 PMD(Poll Mode Drivers)网卡驱动。该驱动由用户态的 API 以及 PMD Driver 构成,内核态的 UIO Driver 屏蔽了网卡发出的中断信号,然后由用户态的 PMD Driver 采用主动轮询的方式。除了链路状态通知仍必须采用中断方式以外,均使用无中断方式直接操作网卡设备的接收和发送队列。
PMD Driver 从网卡上接收到数据包后,会直接通过 DMA 方式传输到预分配的内存中,同时更新无锁环形队列中的数据包指针,不断轮询的应用程序很快就能感知收到数据包,并在预分配的内存地址上直接处理数据包,这个过程非常简洁。
UIO+PMD,前者旁路了内核,后者主动轮询避免了硬件中断,DPDK 从而可以在用户态进行收发包的处理。带来了零拷贝(Zero Copy)、无系统调用(System call)的优化。同时,还避免了软中断的异步处理,也减少了上下文切换带来的 Cache Miss。
PMD用户态驱动是通过轮询的方式,直接从网卡收发报文,将内核旁路了,绕过了协议栈。那为什么还要实现uio呢? 在某些情况下应用层想要知道网卡的状态信息之类的,就需要网卡硬件中断的支持。因为硬件中断只能在内核上完成, 目前dpdk的实现方式是在内核态igb_uio驱动上实现小部分硬件中断,例如统计硬件中断的次数, 然后唤醒应用层注册到epoll中的/dev/uiox中断,进而由应用层来完成大部分的中断处理过程,例如获取网卡状态等。
3、 mellanox dpdk
Mellanox轮询模式驱动程序(PMD)是嵌入式dpdk.org版本中的开源上游驱动程序,旨在通过提供内核绕过接收,发送并避免中断处理性能开销来实现快速数据包处理和低延迟
Mellanox DPDK在用户空间使用PMD驱动,与网卡之间有两条路径,控制路径使用user verbs,经过内核,用于对象的创建、初始化、修改、查询和释放。数据路径之间访问网卡,进行数据的收发。
前面提到虽然PMD采用轮询的方式直接从网卡收包,但是仍然需要UIO去屏蔽网卡的硬件中断,并获取网卡的一些具体信息。所以Mellanox仅仅提供PMD驱动是不满足需求的,因此Mellanox提供了内核的驱动,用于获取网卡的硬件信息和屏蔽中断。mlx5内核驱动提供了netlink接口,dpdk驱动可以从netlink接口获取基础的设备信息,比如当前有多少个端口、ib设备信息等等。
混和中断轮询模式
由于实际网络应用中可能存在的潮汐效应,在某些时间段网络数据流量可能很低,甚至完全没有需要处理的包,这样就会出现在高速端口下低负荷运行的场景,而完全轮询的方式会让处理器一直全速运行,明显浪费处理能力和不节能。因此在DPDK R2.1和R2.2陆续添加了收包中断与轮询的混合模式的支持。
应用程序开始就是轮询收包,这时候收包中断是关闭的。但是当连续多次收到的包的个数为0的时候,应用程序定义了一个简单的策略来决定是否以及什么时候让对应的收包线程进入休眠模式,并且在休眠之前使能收包中断。休眠之后对应的核的运算能力就被释放出来,完全可以用于其他任何运算,或者干脆进入省电模式,取决于内核怎么调度。当后续有任何包收到的时候,会产生一个收包中断,并且最终唤醒对应的应用程序收包线程。线程被唤醒后,就会关闭收包中断,再次轮询收包。
DPDK核心部件库
EAL(Environment Abstraction Layer)即环境抽象层,为应用提供了一个通用接 口,隐藏了与底层库与设备打交道的相关细节。EAL实现了DPDK运行的初始化工 作,基于大页表的内存分配,多核亲缘性设置,原子和锁操作,并将PCI设备地址 映射到用户空间,方便应用程序访问。
EAL通过对hugetlb使用mmap接口来实现物理内存的分配。这部分内存暴露给DPDK服务层,如 Mempool Library。初始化完成后通过设置线程亲和性调用,每个执行单元将会分配给特定的逻辑核,以一个user-level等级的线程来运行。
DPDK 内存结构
hugetlb
TLB
查询虚拟地址对应的物理地址时,需要多次查找物理内存。为了加速进行虚拟地址到物理地址的映射, 减少直接查询物理内存的次数,需要将部分页表信息放到 cpu 高速缓存中,也就是 TLB,本质上是内存中页表的一份快照。 当 CPU 收到应用程序发来的虚拟地址后,首先到 TLB 中查找相应的页表数据,如果 TLB 中正好存放着所需的页表项,则称为 TLB 命中(TLB Hit)。如果 TLB 中没有所需的页表项,则称为 TLB 未命中(TLB Miss),接下来就必须访问物理内存中存放的多级页表,同时更新 TLB 的页表数据。
每个进程都有属于各自的多级页表, 而 tlb 表只有一个,位于 cpu 高速缓存中。 那 cpu 怎么知道 tlb 表中存放的是哪个进程对应的虚拟地址转换信息呢? 这里会引入一个 cr3 页表寄存器,存放的是某个进程的一级页表的地址。当 cpu 对某个进程提供的虚拟地址进行转换时,会将进程的一级页表地址加载到 cr3 页表寄存器, tlb 中存放这个进程对应的地址转换信息。这样 tlb 与某个进程关联起来了。
为什么要使用大页
避免使用 swap
所有大页以及大页表都以共享内存存放在共享内存中,永远都不会因为内存不足而导致被交换到磁盘 swap 分区中。而 linux 系统默认的 4K 大小页面,是有可能被交换到 swap 分区的, 大页则永远不会。通过共享内存的方式,使得所有大页以及页表都存在内存,避免了被换出内存会造成很大的性能抖动
减少页表开销
由于所有进程都共享一个大页表,减少了页表的开销,无形中减少了内存空间的占用, 使系统支持更多的进程同时运行。
减轻 TLB 的压力
我们知道 TLB 是直接缓存虚拟地址与物理地址的映射关系,用于提升性能,省去查找 page table 减少开销,但是如果出现的大量的 TLB miss,必然会给系统的性能带来较大的负面影响,尤其对于连续的读操作。使用 hugepages 能大量减少页表项的数量,也就意味着访问同样多的内容需要的页表项会更少,而通常 TLB 的槽位是有限的,一般只有 512 个,所以更少的页表项也就意味着更高的 TLB 的命中率
减轻查内存的压力
每一次对内存的访问实际上都是由两次抽象的内存操作组成。如果只要使用更大的页面,自然总页面个数就减少了,那么原本在页表访问的瓶颈也得以避免,页表项数量减少,那么使得很多页表的查询就不需要了。例如申请 2M 空间,如果 4K 页面,则一共需要查询 512 个页面,现在每个页为 2M,只需要查询一个页就好了
DPDK大页内存原理_Linux_赖猫_InfoQ写作社区
Mbuf
为了高效访问数据,DPDK将内存封装在Mbuf(struct rte_mbuf)结构体内。 Mbuf主要用来封装网络帧缓存,也可用来封装通用控制信息缓存(缓存类型需使 用CTRL_MBUF_FLAG来指定)。
网络帧元数据的一部分内容由DPDK的网卡驱动写入。这些内容包括VLAN标签、 RSS哈希值、网络帧入口端口号以及巨型帧所占的Mbuf个数等。对于巨型帧,网络 帧元数据仅出现在第一个帧的Mbuf结构中,其他的帧该信息为空。
单帧结构
巨型帧结构
Mempool
1、在DPDK中,数据包的内存操作对象被抽象化为Mbuf结构,而有限的rte_mbuf结 构对象则存储在内存池中。内存池使用环形缓存区来保存空闲对象。当一个网络帧被网卡接收时,DPDK的网卡驱动将其存储在一个高效的环形缓存区 中,同时在Mbuf的环形缓存区中创建一个Mbuf对象。
当然,两个行为都不涉及向系统申请内存,这些内存已经在内存池被创建时就申请 好了。Mbuf对象被创建好后,网卡驱动根据分析出的帧信息将其初始化,并将其和实际帧对象逻辑相连。对网络帧的分析处理都集中于Mbuf,仅在必要的时候访 问实际网络帧。这就是内存池的双环形缓存区结构。为增加对Mbuf的访问效率, 内存池还拥有内存通道/Rank对齐辅助方法。内存池还允许用户设置核心缓存区大 小来调节环形内存块读写的频率。
rte_mbuf和rte_mempool关系
DPDK运行demo
DPDK转发模型
run to completion
在DPDK
的轮询模式中主要通过一些DPDK
中eal
中的参数-c、-l、-l core s
来设置哪些核可以被DPDK
使用,最后再把处理对应收发队列的线程绑定到对应的核上。每个报文的整个生命周期都只可能在其中一个线程中出现。目前抗D检测设备用的就是这个模型。
pipeline
pipeline
的主要思想就是不同的工作交给不同的模块,而每一个模块都是一个处理引擎,每个处理引擎都只单独处理特定的事务,每个处理引擎都有输入和输出,通过这些输入和输出将不同的处理引擎连接起来,完成复杂的网络功能。
转载:DPDK系列之三:Linux UIO技术在DPDK的应用_cloudvtech的博客-CSDN博客_dpdk uio
dpdk uio驱动实现_老王不让用的博客-CSDN博客_dpdk uio
https://python.iitter.com/other/170943.html