赞
踩
Linux 内核在收包时有两种方式可供选择,一种是中断方式,另外一种是轮询方式。
从哲学的角度来说,中断是外界强加给你的信号,你必须被动应对,而轮询则是你主动地处理事情。前者最大的影响就是打断你当前工作的连续性,而后者则不会,事务的安排自在掌握。
在 x86 体系结构中,一次中断处理需要:
将 CPU 的状态寄存器保存到堆栈;
运行中断服务程序;
再将保存的状态寄存器信息从堆栈中恢复。
整个过程需要至少 300 个处理器时钟周期。
所以:
网络中大量数据包到来时,会频繁产生中断请求,频繁的中断会造成上下文的切换产生时延,产生较高的性能开销。
网卡收到报文后,可以借助 DDIO(Direct Data I/O)技术直接将报文保存到 CPU 的 Cache 中,或者保存到内存中(没有 DDIO 技术的情况下),并设置报文到达的标志位。应用程序则可以周期性地轮询报文到达的标志位,检测是否有新报文需要处理。整个过程中完全没有中断处理过程,因此应用程序的网络报文处理能力得以极大提升。
轮询模式,会一直占用CPU去查询是否有包达到,即使在没有包到达的情况下。当前的Linux的CPU,不仅仅是收发包,还要为应用程序预留计算资源。所以轮询模式,适用于仅仅进行包转发,没有到应用层的的情况下。
当前内核协议栈可以使用 NAPI (中断+轮询)的方式,但依旧没有根本上解决问题。
这种PMD驱动并不适用于一般的应用场景,因为需要有专门的CPU给PMD一直做轮询操作,对应的CPU就会一直占用100%,可能严重影响了其他任务的运行。
当网络处于空闲状态的时候,CPU占用100%的问题会带来额外的不必要的功耗,因此dpdk还推出了一个interrupt dpdk模式,即当网卡中没有数据包处理的时候进入类似睡眠模式的状态,然后改为传统的中断方式通知,这个时候被100%占用的核心利用率就会降低,可以和其他的进程共享,但是DPDK仍然拥有高优先级,而当有数据包进来的时候还是可以优先处理。
PMD是Poll Mode Driver的缩写,即基于用户态的轮询机制的驱动。
参见:dpdk pmd 介绍
- PMD 包含 PMD 应用程序(DPDK程序) + PMD KMOD(pmd kmod: 比如:igb_uio/uio_pci_generic/vfio_pci)
- PMD应用程序「DPDK程序」在用户态调用API 接口对网卡进行配置,获取统计,收发包。
用户态部分实现真正的业务处理,内核态部分主要是接管硬件资源提供给用户态部分使用。
如上:我将整个框架分了四层,用户层、接口层、内核层、硬件层。网卡开始是与e1000等网卡驱动绑定,当用户层程序执行解绑以及绑定命令,通过sysfs将消息发送至内核层后,网卡与igb_uio驱动进行了绑定。此时内核层uio接管了网卡,并为用户层pmd提供了服务接口。到此我们知道pmd的使用是需要内核层支持的。
1> DPDK程序 通过-w/-a 参数指定 pci
2> 基于pci 查询 该PCI上设备网卡对应的 Vendor-ID, Device-ID,进而可以知道对应的驱动。
3> 注册对应驱动的 DPDK API接口。
比如:识别为 ixgbe 驱动,rte_eth_dev_filter_ctrl 赋值为 ixgbe_dev_filter_ctrl。
linux 下基于 pci查询 vendor-id, device-id:
8086 为 vendor-id, 代表 intel.
10fb 为 device-id, 代表 82599ES.
参见:intel 8259x 系列网卡的 vendor-id,device-id 对照表
查看某个DPDK版本支持的驱动、网卡:
./dpdk-pmdinfo.py -t ./dpvs
由于 dpvs 是通过制定版本的 dpdk 编译而成。
PMD驱动包含了各种API,在用户态配置网卡和网卡的收发队列。
PWD驱动最大的优势就是无需任何中断操作就可以直接访问网卡队列中的RX/TX描述符(除了网卡的链接状态变化),借助这个优势,就可以绕过内核和同样运行在用户态的应用程序快速地进行数据传输。
这种绕过系统内核的做法最直接的代价就是驱动程序必须一直通过轮询poll的操作来保证能够及时接收到网卡的信息和数据,导致对应的cpu核心会一直处于100%的占用状态。同时这个时候网卡已经不再归于系统内核管理,常规的ip和ifconfig等命令已经没办法查看网卡的详细信息
- 中断处理:
当网络中大量数据包到来时,会频繁产生中断请求,频繁的中断会产生较高的性能开销、并造成上下文的切换产生时延。
- 内存拷贝
网络数据包到来时,网卡通过 DMA 等拷贝到内核缓冲区,内核协议栈再从内核空间拷贝到用户态空间,在 Linux 内核协议栈中,这个耗时操作甚至占到了数据包整个处理流程的 57.1%。
- 局部性失效
目前主流处理器都是多个CPU核心的,这意味着一个数据包的处理可能跨多个 CPU 核心。比如:一个数据包可能中断在 cpu0,内核态处理在 cpu1,用户态处理在 cpu2,这样跨多个核心,容易造成 CPU 缓存失效,造成局部性失效。
PMD Driver 从网卡上接收到数据包后,会直接通过 DMA 方式传输到预分配的内存中,同时更新无锁环形队列中的数据包指针,不断轮询的应用程序很快就能感知收到数据包,并在预分配的内存地址上直接处理数据包(减少内存拷贝),这个过程非常简洁。
- 内核bypass
通过UIO(Userspace I/O)旁路数据包,实现用户态数据包收发,减少内存拷贝;另外轮询方式收包代替中断方式,减少上下文切换。
- CPU 的亲和性
设置 CPU 的亲和性,将线程和 CPU 核进行一比一绑定,减少彼此之间调度切换,减少cache miss.
- 使用大页内存代替普通的内存
减少 cache-miss。
- 采用无锁技术
解决资源竞争问题。
为了支持Userspace IO,DPDK可以选择如下三种类型的驱动:
- uio_pci_generic
- igb_uio
- vfio-pci
参见:Userspace IO
注:
igb_uio 完全可以代替 uio_pci_generic;
Mellanox 网卡比较特殊,不需要igb_uio等内核模块。
PMD是在用户态提供的API来操作,但还是依赖于内核提供的策略。
igb_uio/uio_pci_generic 是一种 pci 驱动,将网卡绑定到 igb_uio 隔离了网卡的内核驱动,同时 igb_uio 完成网卡中断内核态初始化并将中断信号映射到用户态。
比如:网卡在内核中使用 ice/ixgbe 内核驱动,如果通过 dpdk-devbind 解绑定内核驱动 ice/ixgbe, 然后使用 igb_uio进行接管网卡。这样dpdk应用程序会使用dpdk的 ice/ixgbe pmd ,底层是igb_uio /uio 来进行收发包。
dpdk的 ice/ixgbe pmd 代码可以在dpdk 源码中查看。
注:一旦dpdk应用使用 ice/ixgbe pmd + igb_uio + uio 进行收发包,那么和内核原生的驱动 ixgbe/ice 就没有关系了。
- 配置设备
让其选择驱动:向 /sys/bus/pci/devices/{pci id}/driver_override 写入指定驱动的名称。- 配置驱动
让其支持新的 PCI 设备:向 /sys/bus/pci/drivers/igb_uio/new_id 写入要 bind 的网卡设备的 PCI ID(e.g. 8086 10f5,格式为:设备厂商号 设备号)。
按照内核的文档 https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-bus-pci 中提到,这两个动作都会促使驱动程序 bind 新的网卡设备,而 DPDK 显然是使用了第 2 种方式。
- 调用 igbuio_setup_bars,设置 uio_info 的 uio_mem 和 uio_port。
- 设置 uio_info 的其他成员。
- 调用 uio_register_device,注册 UIO 设备。
- 打开 UIO 设备并注册中断。
- 调用 uio_event_notify,将注册的 UIO 设备的 “内存空间” 映射到用户态的应用空间。
其 mmap 的函数为 uio_mmap。至此,UIO 就可以让 PMD 驱动程序在用户态应用层访问设备的大部分资源了。- 应用层 UIO 初始化。同时,DPDK 还需要把 PCI 设备的 BAR 映射到应用层。
在 pci_uio_map_resource 函数中会调用 pci_uio_map_resource_by_index 做资源映射。- 在 PMD 驱动程序中,DPDK 应用程序,会调用 rte_eth_rx_burst 读取数据报文。如果网卡接收 Buffer 的描述符表示已经完成一个报文的接收(e.g. 有 E1000_RXD_STAT_DD 标志),则 rte_mbuf_raw_alloc 一个 mbuf 进行处理。
- 对应 RTC 模型的 DPDK 应用程序来说,就是不断的调用 rte_eth_rx_burst 去询问网卡是否有新的报文。如果有,就取走所有的报文或达到参数 nb_pkts 的上限。然后进行报文处理,处理完毕,再次循环。
uio_pci_generic是内核原生的一种uio驱动,该驱动提供了uio功能,直接使用如下命令加载:
sudo modprobe uio_pci_generic
sudo modprobe uio
sudo insmod kmod/igb_uio.ko
从DPDK release 1.7开始,DPDK对VFIO进行了支持,因此VFIO Driver成了新的可选项:
sudo modprobe vfio-pci
特别注意:
对于使用VFIO驱动来使用DPDK的场景,必须保证:
1.硬件上支持支持VT-x、VT-d,BIOS中需要打开相关特性
2.对于物理机的内核中需要支持IOMMU特性(在启动参数添加 iommu=pt, intel_iommu=on)
使用分叉驱动程序的PMD与设备内核驱动程序共存。 在这种模式下,NIC由内核控制,而数据路径则由PMD直接在设备上执行。
注:使用分叉驱动程序的PMD不应从其内核驱动程序中解除绑定。
参见:Mellanox Bifurcated DPDK PMD
如果正在使用的PMD使用UIO或VFIO驱动程序,那么DPDK应用程序要使用的所有端口都必须在应用程序运行之前绑定到uio_pci_generic、igb_uio或vfio-pci模块。 对于此类PMD,Linux 控制下的任何网络端口都将被忽略,并且不能被应用程序使用。
# 查看 Network devices using DPDK-compatible driver ============================================ 0000:82:00.0 '82599EB 10-GbE NIC' drv=uio_pci_generic unused=ixgbe 0000:82:00.1 '82599EB 10-GbE NIC' drv=uio_pci_generic unused=ixgbe Network devices using kernel driver =================================== 0000:04:00.0 'I350 1-GbE NIC' if=em0 drv=igb unused=uio_pci_generic *Active* 0000:04:00.1 'I350 1-GbE NIC' if=eth1 drv=igb unused=uio_pci_generic 0000:04:00.2 'I350 1-GbE NIC' if=eth2 drv=igb unused=uio_pci_generic 0000:04:00.3 'I350 1-GbE NIC' if=eth3 drv=igb unused=uio_pci_generic Other network devices ===================== # 绑定: ./usertools/dpdk-devbind.py --bind=uio_pci_generic 04:00.1
在物理机上使用DPDK,需要内核中加载DPDK PMD Driver,那么需要使用如下命令加载DPDK的驱动:
modprobe uio
insmod igb_uio
usertools/dpdk-devbind.py --bind=igb_uio bb:ss.f
注:这里我们也可以使用上面介绍过的其他类型的内核模块:uio_pci_generic 或者 vfio-pci 。
对于支持SR-IOV的网卡来说,比如Intel的X710/XL710网卡,在虚拟化的环境中使用,网卡可以进行透传,本文以透传的方式来进行实践介绍,对于支持SR-IOV的网卡来说,它分为PF和VF模块,在宿主机中需要加载对应的PF Driver和VF Driver来驱动这两个子模块。
在宿主机上可以直接使用Linux kernel官方的intel PF驱动,比如i40e,也可以使用DPDK专用的 PMD PF驱动。
如果使用了DPDK PMD PF 驱动,那么这个宿主机网络的管理权就完全交给DPDK了。
rmmod i40e (To remove the i40e module)
insmod i40e.ko max_vfs=2,2 (To enable two Virtual Functions per port)
通过重新加载intel提供的i40e驱动,并指定max_vfs参数来创建VF功能,对于该网卡的VF功能内核默认使用的驱动为 i40evf,因此在使用dpdk之前,还需要在Host上将VF与i40evf驱动解绑,重新绑定到vfio-pci驱动上:
modprobe vfio-pci
usertools/dpdk-devbind.py --bind=vfio-pci bb:ss.f
1> 此时该VF已经由vfio驱动接管,如果在宿主机上使用VFIO DPDK,那么此时就已经满足了条件;
2> 如果为了演示虚拟机中使用DPDK,此时不能启动DPDK去使用该VF,而需要在虚拟机中透传该设备来使用。
宿主机中需要使用vfio_pci这个内核模块来对需要分配给客户机的设备进行隐藏, 从而让宿主机和未被分配该设备的客户机都无法使用该设备, 达到隔离和安全使用的目的。而在客户机不需要使用该设备后, 让宿主机使用该设备, 则需要将其恢复到使用原本的驱动。
modprobe uio
insmod kmod/igb_uio.ko
usertools/dpdk-devbind.py --bind=igb_uio bb:ss.f
echo 2 > /sys/bus/pci/devices/0000\:bb\:ss.f/max_vfs (To enable two VFs on a specific PCI device)
对于虚拟机来说,透传过来的VFIO网卡对于虚拟机来说就相当于是一个常规的物理网卡,默认就会使用该物理网卡对应的驱动,比如i40e driver。
那么如果要在虚拟机中使用DPDK,就需要把虚拟网卡重新绑定到igb_uio驱动,这样就可以在虚拟机中使用DPDK了。实际上操作还是与宿主机中一样:
modprobe uio
insmod kmod/igb_uio.ko
usertools/dpdk-devbind.py --bind=igb_uio bb:ss.f
https://www.daimajiaoliu.com/daima/60f53a20b369801
https://zhuanlan.zhihu.com/p/71563242
https://www.codeleading.com/article/69134740056/
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。