热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

DPDK在Linux用户级执行环境中执行EAL

目录在Linux用户级执行环境中进行EAL初始化和核心启动关机和清理多进程支持内存映射发现和内存保留支持外部分配的内存每个核心和共享变量日志CPU功能识别用户空

目录

在Linux用户级执行环境中进行EAL

初始化和核心启动

关机和清理

多进程支持

内存映射发现和内存保留

支持外部分配的内存

每个核心和共享变量

日志

CPU功能识别

用户空间中断事件

黑名单

杂项功能

IOVA模式检测

IOVA模式配置




在Linux用户级执行环境中进行EAL

在Linux用户空间环境中,DPDK应用程序使用pthread库作为用户空间应用程序运行。

EAL使用hugetlbfs中的mmap()执行物理内存分配(使用巨大的页面大小来提高性能)。此内存公开给DPDK服务层,例如Mempool库。

此时,将初始化DPDK服务层,然后通过pthread setaffinity调用,将每个执行单元分配给特定的逻辑核心,以作为用户级线程运行。

时间参考由CPU时间戳计数器(TSC)或HPET内核API通过mmap()调用提供。


初始化和核心启动

初始化的一部分由glibc的start函数完成。在初始化时也会执行检查,以确保CPU支持在配置文件中选择的微体系结构类型。然后,调用main()函数。核心初始化和启动在rte_eal_init()中完成(请参阅API文档)。它包含对pthread库的调用(更具体地说,是pthread_self(),pthread_create()和pthread_setaffinity_np())。

Linux应用程序环境中的EAL初始化

 

对象的初始化,例如内存区域,ring,内存池,lpm表和哈希表,应作为主lcore上整个应用程序初始化的一部分进行。这些对象的创建和初始化函数不是多线程安全的。但是,一旦初始化,对象本身就可以安全地同时在多个线程中使用。

关机和清理

在EAL初始化期间,核心组件可以分配诸如大页支持的内存之类的资源。rte_eal_init() 通过调用该rte_eal_cleanup()函数可以释放在分配期间分配的内存。有关详细信息,请参阅API文档。

多进程支持

Linux EAL允许多进程以及多线程(pthread)部署模型。有关更多详细信息,请参见多进程支持一章 。

内存映射发现和内存保留

大型连续物理内存的分配是使用hugetlbfs内核文件系统完成的。EAL提供了一个API,可以在此连续内存中保留命名的内存区域。该存储区的保留内存的物理地址也由存储区保留API返回给用户。

DPDK内存子系统可以在两种模式下运行:动态模式和旧模式。两种模式在下面说明。

使用rte_malloc提供的API进行的内存保留也由hugetlbfs文件系统的页面支持。

  • 动态记忆模式

当前,仅在Linux上支持此模式。

在这种模式下,DPDK应用程序对大页面的使用将根据应用程序的请求而增加和减少。任何内存分配通过rte_malloc(), rte_memzone_reserve()或其它方法,可能潜在地导致更大页面正在从系统预留。同样,任何内存重新分配都可能导致大页面释放回系统。

不能保证在此模式下分配的内存是IOVA连续的。如果需要大块的IOVA连续块(“大”块定义为“一页以上”),建议对所有物理设备使用VFIO驱动程序(这样IOVA和VA地址可以相同,从而绕过)完整的物理地址),或使用旧版内存模式。

对于必须是IOVA连续的内存块,建议使用 rte_memzone_reserve()带有RTE_MEMZONE_IOVA_CONTIG指定标志的函数。这样,内存分配器将确保无论使用哪种内存模式,保留的内存都将满足要求,否则分配将失败。

无需在启动时使用-m或 --socket-mem命令行参数预分配任何内存,但是仍然可以这样做,在这种情况下,预分配内存将被“固定”(即,应用程序永远不会释放回系统) 。可以分配更多的大页面,然后将其取消分配,但是任何预分配的页面都不会被释放。如果既未指定也-m--socket-mem指定,则不会预先分配内存,并且将根据需要在运行时分配所有内存。

在动态内存模式下使用的另一个可用选项是 --single-file-segments命令行选项。此选项会将页面放在单个文件中(每个memseg列表),而不是每页创建一个文件。通常不需要这样做,但是对于诸如userspace vhost之类的用例很有用,在这些用例中,可以传递给VirtIO的页面文件描述符数量有限。

如果应用程序(或DPDK内部代码,例如设备驱动程序)希望接收有关新分配的内存的通知,则可以通过rte_mem_event_callback_register()函数注册内存事件回调。每当DPDK的内存映射已更改时,它将调用回调函数。

如果应用程序(或DPDK内部代码,例如设备驱动程序)希望收到有关指定阈值以上的内存分配的通知(并有机会拒绝它们),则也可以通过rte_mem_alloc_validator_callback_register()函数使用分配验证程序回调 。

EAL提供了一个默认的验证程序回调,可以通过--socket-limit命令行选项启用它 ,以一种简单的方式来限制DPDK应用程序可以使用的最大内存量。

内存子系统内部使用DPDK IPC,因此不能将内存分配/回调和IPC混合使用:在与内存相关的或IPC回调中分配/释放内存是不安全的,并且在与内存相关的回调中使用IPC是不安全的。有关DPDK IPC的更多详细信息,请参见“ 多进程支持”一章 。

  • 旧版内存模式

通过将--legacy-mem命令行开关指定为EAL 启用此模式。此开关对FreeBSD无效,因为无论如何FreeBSD仅支持传统模式。

此模式模仿EAL的历史行为。也就是说,EAL将在启动时保留所有内存,将所有内存分类为大的IOVA连续块,并且不允许在运行时从系统获取或释放大页面。

如果既未指定也-m--socket-mem指定,则整个可用的大页内存将被预先分配。

  • 大页分配匹配

通过指定--match-allocations到EAL 的命令行开关来启用此行为。此开关仅适用于Linux,并且--legacy-memnor 不支持 --no-huge

某些使用内存事件回调的应用程序可能需要完全释放分配的大页。这些应用程序可能还要求来自malloc堆的任何分配都不能跨越与两个不同的内存事件回调相关联的分配。这些类型的应用程序可以使用大页分配匹配来满足这两个要求。这可能会导致某些内存使用量增加,这在很大程度上取决于应用程序的内存分配模式。

  • 32位支持

在32位模式下运行时,存在其他限制。在动态内存模式下,默认情况下最多将预分配2 GB的VA空间,并且所有空间都将位于主lcore NUMA节点上,除非使用了--socket-mem标志。

在传统模式下,只会为请求的段预分配VA空间(加上填充以保持IOVA连续性)。

  • 最大内存量

在启动时会预先分配可用于DPDK进程中的大页映射的所有可能的虚拟内存空间,从而对DPDK应用程序可以拥有的内存量设置上限。DPDK内存存储在段列表中,每个段严格是一个物理页。通过编辑以下配置变量,可以更改启动时预分配的虚拟内存量:

  • CONFIG_RTE_MAX_MEMSEG_LISTS 控制DPDK可以拥有多少段列表
  • CONFIG_RTE_MAX_MEM_MB_PER_LIST 控制每个段列表可以处理多少兆字节的内存
  • CONFIG_RTE_MAX_MEMSEG_PER_LIST 控制每个段可以有多少段
  • CONFIG_RTE_MAX_MEMSEG_PER_TYPE 控制每种内存类型可以具有多少段(其中“类型”定义为“页面大小+ NUMA节点”组合)
  • CONFIG_RTE_MAX_MEM_MB_PER_TYPE 控制每种内存类型可以寻址多少兆字节的内存
  • CONFIG_RTE_MAX_MEM_MB 将全局最大值放在DPDK可以保留的内存量上

通常,这些选项不需要更改。

不要将预分配的虚拟内存与预分配的大页内存混淆!所有DPDK进程在启动时都会预分配虚拟内存。以后可以将大页映射到该预分配的VA空间(如果启用了动态内存模式),并且可以选择在启动时映射到它。

  • 段文件描述符

在Linux上,大多数情况下,EAL会将段文件描述符存储在EAL中。当使用较小的页面大小时,由于glibc库的基本限制,这可能会成为问题。例如,Linux API调用之类的select()可能无法正常工作,因为glibc它不支持超过一定数量的文件描述符。

有两种可能的解决方案。推荐的解决方案是使用--single-file-segments模式,因为该模式将不会在每个页面上使用文件描述符,并且它将与带有vhost-user后端的Virtio保持兼容性。使用--legacy-mem 模式时此选项不可用。

另一种选择是使用更大的页面尺寸。由于需要较少的页面来覆盖相同的内存区域,因此EAL将在内部存储较少的文件描述符。

支持外部分配的内存

可以在DPDK中使用外部分配的内存。使用外部分配的内存有两种工作方式:malloc堆API和手动内存管理。

  • 使用堆API来为外部分配的内存

建议使用一组malloc堆API,以在DPDK中使用外部分配的内存。这样,通过重载套接字ID来实现对外部分配的内存的支持-外部分配的堆将具有套接字ID,在正常情况下,这些套接字ID将被视为无效。请求从指定的外部分配内存中进行分配是一个直接(例如通过调用rte_malloc)或间接(通过特定于数据结构的分配API) 向DPDK分配器提供正确的套接字ID的问题rte_ring_create。使用这些API还可以确保为添加到DPDK malloc堆的任何内存段也执行DMA的外部分配内存的映射。

由于DPDK无法验证内存是否可用或有效,因此该责任由用户承担。所有多进程同步也是用户的责任,并确保所有添加/附加/分离/删除内存的调用均以正确的顺序进行。不需要在所有过程中都附加到存储区-仅在需要时附加到存储区。

预期的工作流程如下:

  • 获取指向存储区的指针

  • 创建一个命名堆

  • 将内存区域添加到堆

    • 如果未指定IOVA表,则将假定IOVA地址不可用,并且将不执行DMA映射
    • 其他进程必须先附加到内存区域,然后才能使用它
  • 获取用于堆的套接字ID

  • 使用提供的套接字ID使用正常的DPDK分配过程

  • 如果不再需要内存区域,则可以将其从堆中删除

    • 必须先从此内存区域分离其他进程,然后才能将其删除
  • 如果不再需要堆,请将其删除

    • 套接字ID将变为无效,并且将不会重用

有关更多信息,请参阅rte_mallocAPI文档,特别rte_malloc_heap_*是函数调用系列。

  • 使用没有DPDK API的外部分配的内存

建议使用堆API,在DPDK中使用外部分配的内存,但在某些用例中,DPDK堆API的开销是不希望的-例如,在外部分配的区域执行手动内存管理时。为了支持在外部DPDK工作流中不使用外部分配的内存的用例,rte_extmem_*命名空间下还有另一组API 。

这些API(顾名思义)旨在允许向DPDK的内部页表注册或从DPDK的内部页表中注销外部分配的内存,以允许API rte_mem_virt2memseg等与外部分配的内存一起使用。以这种方式添加的内存将不适用于任何常规DPDK分配器;DPDK将保留此内存供用户应用程序管理。

预期的工作流程如下:

  • 获取指向存储区的指针

  • 在DPDK中注册内存

    • 如果未指定IOVA表,则将假定IOVA地址不可用
    • 其他进程必须先附加到内存区域,然后才能使用它
  • rte_dev_dma_map根据需要执行DMA映射

  • 在应用程序中使用存储区

  • 如果不再需要存储区,则可以取消注册

    • 如果该区域是为DMA映射的,则必须在取消注册内存之前执行取消映射
    • 其他进程必须与存储区分离,然后才能取消注册

由于这些外部分配的存储区域将不受DPDK的管理,因此,由用户应用程序决定如何注册它们以及如何使用它们。

 

每个核心和共享变量

lcore指处理器的逻辑执行单元,有时也称为硬件线程

共享变量是默认行为。每线程核心变量是使用线程本地存储(TLS)实现的,以提供每线程本地存储。

日志

EAL提供了一个日志记录API。默认情况下,在Linux应用程序中,日志既发送到syslog,也发送到控制台。但是,用户可以覆盖日志功能以使用其他日志记录机制。

跟踪和调试功能

有一些调试功能可以将堆栈转储到glibc中。rte_panic()函数可以自动引发SIG_ABORT,这可以触发gdb可读的核心文件的生成。

CPU功能识别

EAL可以在运行时查询CPU(使用rte_cpu_get_features()函数)以确定哪些CPU功能可用。

用户空间中断事件

  • 主机线程中的用户空间中断和警报处理

EAL创建一个主机线程以轮询UIO设备文件描述符以检测中断。回调可以由EAL函数针对特定的中断事件进行注册或取消注册,并在主机线程中异步调用。EAL还允许以与NIC中断相同的方式使用定时回调。

注意在DPDK PMD中,专用主机线程处理的唯一中断是那些用于链接状态更改(链接打开和链接关闭通知)和突然移除设备的中断。

  • RX中断事件

每个PMD提供的接收和发送例程不会限制自己以轮询线程模式执行。为了以极小的吞吐量缓解空闲轮询,暂停轮询并等待唤醒事件发生是很有用的。RX中断是发生此类唤醒事件的首选,但可能并非唯一。

EAL为此事件驱动的线程模式提供了事件API。以Linux为例,其实现依赖于epoll。每个线程可以监视一个epoll实例,在该实例中添加了所有唤醒事件的文件描述符。根据UIO / VFIO规范创建事件文件描述符并将其映射到中断向量。从FreeBSD的角度来看,kqueue是替代方法,但尚未实现。

EAL初始化事件文件描述符和中断向量之间的映射,而每个设备初始化中断向量和队列之间的映射。这样,EAL实际上不知道特定向量上的中断原因。eth_dev驱动程序负责编程后面的映射。

每个队列的RX中断事件仅在支持多个MSI-X向量的VFIO中允许。在UIO中,RX中断与其他中断原因共享相同的向量。在这种情况下,当同时启用RX中断和LSC(链接状态更改)中断(intr_conf.lsc == 1 && intr_conf.rxq == 1)时,仅前者才有能力。

RX中断由ethdev API-'rte_eth_dev_rx_intr_ *'控制/启用/禁用。如果PMD还不支持它们,它们将返回失败。intr_conf.rxq标志用于打开每个设备的RX中断功能。

  • 设备移除事件

该事件由在总线级别卸下设备触发。它的基础资源可能已变得不可用(即,未映射PCI映射)。PMD必须确保在这种情况下,应用程序仍可以安全地使用其回调。

可以按照订阅链接状态更改事件的相同方式来订阅此事件。因此,执行上下文是相同的,即它是专用的中断主机线程。

考虑到这一点,应用程序很可能希望关闭已发出设备移除事件的设备。在这种情况下,调用 rte_eth_dev_close()可以触发它取消注册自己的设备删除事件回调。必须注意不要从中断处理程序上下文中关闭设备。必须重新计划这种关闭操作。

黑名单

EAL PCI设备黑名单功能可用于将某些NIC端口标记为黑名单,因此DPDK将忽略它们。使用PCIe *描述(Domain:Bus:Device.Function)标识要列入黑名单的端口。

杂项功能

锁和原子操作是基于体系结构的(i686和x86_64)。

IOVA模式检测

通过考虑系统上当前可用的设备需要和/或支持的内容来选择IOVA模式。

在FreeBSD上,RTE_IOVA_PA始终是默认值。在Linux上,基于下面详细介绍的两步启发式检测IOVA模式。

第一步,EAL向每个总线询问有关IOVA模式的要求,然后确定首选的IOVA模式。

  • 如果所有总线都报告RTE_IOVA_PA,则首选的IOVA模式为RTE_IOVA_PA,
  • 如果所有总线都报告RTE_IOVA_VA,则首选的IOVA模式为RTE_IOVA_VA,
  • 如果所有总线都报告RTE_IOVA_DC,则没有总线表示优先,则首选模式为RTE_IOVA_DC,
  • 如果总线不同意(至少一个总线需要RTE_IOVA_PA,至少一个总线需要RTE_IOVA_VA),则首选的IOVA模式为RTE_IOVA_DC(请参阅下面的“物理地址可用性”检查),

如果总线未表示要选择哪种IOVA模式,则使用以下逻辑选择默认值:

  • 如果物理地址不可用,则使用RTE_IOVA_VA模式
  • 如果/ sys / kernel / iommu_groups不为空,则使用RTE_IOVA_VA模式
  • 否则,使用RTE_IOVA_PA模式

如果总线在其首选的IOVA模式上存在分歧,则由于该决定,部分总线将无法工作。

第二步检查首选模式是否符合“物理地址”可用性,因为这些仅适用于最新内核中的root用户。即,如果首选模式是RTE_IOVA_PA,但是无法访问物理地址,则EAL初始化会尽早失败,因为以后对设备的探测仍然会失败。

在大多数情况下,由于以下原因,首选使用RTE_IOVA_VA模式作为默认模式:

  • 无论物理地址是否可用,所有驱动程序均应以RTE_IOVA_VA模式工作。
  • 默认情况下,内存池首先使用来请求IOVA连续内存 RTE_MEMZONE_IOVA_CONTIG。在RTE_IOVA_PA模式下,这很慢,并且可能会影响应用程序的启动时间。
  • 在VA模式下使用IOVA轻松启用大量IOVA连续内存用例。

预期所有PCI驱动程序都可以在RTE_IOVA_PA和RTE_IOVA_VA模式下工作。

如果PCI驱动程序不支持RTE_IOVA_PA模式,则该 RTE_PCI_DRV_NEED_IOVA_AS_VA标志用于指示该PCI驱动程序只能在RTE_IOVA_VA模式下工作。

当检测到KNI内核模块时,首选RTE_IOVA_PA模式,因为在RTE_IOVA_VA模式下预期会降低性能。

IOVA模式配置

当存在未直接连接到总线的虚拟设备时,基于探测总线和IOMMU配置的IOVA模式自动检测可能不会报告所需的寻址模式。为了便于将IOVA模式强制为特定值,--iova-mode可以使用EAL命令行选项来选择物理寻址('pa')或虚拟寻址('va')。

 



推荐阅读
author-avatar
耗子很傻爱钻洞
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有