DPDK - CPU管理 - 01 EAL 1.1 EAL 在 Linux 用户态执行环境中的表现
EAL是什么
EAL:Environment Abstraction Layer,环境抽象层。EAL 是 DPDK 最核心的基础模块之一,它负责整个运行环境的初始化,包括 CPU、内存、设备的配置与绑定。通过提供统一的抽象接口,EAL 隐藏了操作系统与硬件的复杂性,为上层应用程序提供了一个干净、可移植、可配置的运行环境。
EAL 提供的典型服务包括:
- DPDK 加载与启动:DPDK 和其应用程序被链接为一个单独的应用程序,必须通过某种方式进行加载。
- 核心亲和性 / 分配机制:EAL 提供将执行单元绑定到特定 CPU 核心上的机制,并支持创建执行实例(如线程或进程)。
- 系统内存预留:EAL 支持预留不同的内存区域,例如为设备交互预留的物理内存。
- 跟踪与调试功能:如日志记录、堆栈转储(dump_stack)、程序崩溃信息(panic)等。
- 通用工具函数:提供
spinlock
(自旋锁) 和atomic counter
(原子计数器)等在标准 C 库(libc)中没有的功能。 - CPU 特性识别:运行时判断 CPU 是否支持特定特性(例如 Intel AVX),以及是否支持当前编译的二进制所需的特性集。
- 中断处理:提供接口注册/注销对特定中断源的回调处理函数。
- 定时器功能:提供接口设置或移除将在特定时间点运行的回调函数。
在 Linux 用户态执行环境中的 EAL
在 Linux 用户空间环境中,DPDK 应用作为一个用户态应用程序运行,并使用 pthread
库(POSIX 线程)来管理线程。
EAL 使用 mmap()
函数在 hugetlbfs
文件系统中分配物理内存(通过使用大页(huge page)来提升性能)。这些内存随后会暴露给 DPDK 的服务层,例如内存池库(Memory Pool Library)。
在这个阶段,DPDK 的服务层会被初始化。随后,通过 pthread_setaffinity
系统调用,每个执行单元(通常是一个线程)会被绑定到特定的逻辑 CPU 核心,以用户态线程的形式运行。
时间基准(time reference)由 CPU 的时间戳计数器(TSC) 或通过 mmap()
映射的 HPET(高精度定时器)内核 API 提供。
执行流程:
- 应用调用
rte_eal_init()
初始化 EAL; - EAL 映射 Hugepage 内存(
mmap()
+hugetlbfs
); - 初始化 DPDK 服务层(如内存池、定时器、设备抽象等);
- 创建 pthread 执行单元,绑定到特定逻辑核心;
- 执行主循环
lcore_main()
或等效逻辑; - 使用 TSC 或 HPET 计时器作为定时参考;
- 开始处理数据包或执行指定的业务逻辑。
初始化与核心启动
一部分初始化工作是由 glibc 的 start
函数完成的。在初始化阶段,还会进行一个检查,以确保配置文件中选择的微架构类型是当前 CPU 所支持的。随后会调用 main()
函数。
核心的初始化与启动由 rte_eal_init()
完成(参见 API 文档)。该过程包含对 pthread
库的调用,特别是:
pthread_self()
:获取当前线程句柄;pthread_create()
:创建新线程(即每个 lcore 执行单元);pthread_setaffinity_np()
:将线程绑定到特定的逻辑核心上。
DPDK 初始化流程概述(Linux 用户空间)
- 进程启动(glibc 的 start 函数)
- 在调用
main()
之前,glibc 执行一系列初始化操作,包括运行全局构造函数、环境变量设置等。 - DPDK 的 EAL 初始化也从这里开始做一些 CPU 特性检查,比如当前配置是否适配运行环境。
- 在调用
- 调用
main()
函数- 在
main()
中,DPDK 程序调用rte_eal_init(argc, argv)
来初始化整个运行环境。
- 在
- 内存初始化:mmap + hugepage
- EAL 使用
mmap()
映射位于hugetlbfs
的大页内存,将其作为 DPDK 的共享物理内存区域(例如供rte_mempool
、rte_ring
使用)。
- EAL 使用
- 线程创建与核心绑定:pthread
- 每个逻辑核心(lcore)由一个独立的 pthread 线程表示。
- 通过
pthread_create()
创建线程后,使用pthread_setaffinity_np()
将线程绑定到特定的 CPU 核心,以确保线程始终在指定核心运行,避免上下文切换带来的性能损耗。
- 时间参考初始化
- EAL 初始化 TSC(时间戳计数器)或 HPET 定时器,以用于之后的延迟控制、计时器事件等。
为什么要进行核心线程绑定:DPDK 是 轮询式架构(polling model),每个 lcore 上的线程通常运行一个主循环,这样可以:
- 减少 cache miss(缓存一致性问题);
- 降低调度器带来的上下文切换;
- 保证数据路径稳定和可预测性。
TSC 与 HPET 的选择:
TSC(Time Stamp Counter)
- 高速、低延迟,适合计时精度要求高的场景,是 DPDK 默认的计时器。
- 但在多 CPU 插槽或某些节能模式下,TSC 不一定可靠(跨核时钟可能不同步)。
HPET(High Precision Event Timer)
- 稳定精确,但访问成本高(读取开销远大于 TSC)。
- 通常只在 TSC 不可用或要求非常高时间精度时使用。
像内存区域(memory zones)、环形队列(rings)、内存池(memory pools)、LPM 表(最长前缀匹配表)以及哈希表(hash tables)这样的对象,应该在主逻辑核心(main lcore)上,作为整个应用程序初始化的一部分进行初始化。这些对象的创建和初始化函数不是线程安全的。
但是,一旦初始化完成,这些对象本身就可以在多个线程中安全地并发使用。
关闭与清理
在 EAL 初始化期间,诸如 Hugepage 支持的内存等资源可能会被核心组件分配。通过调用 rte_eal_init()
分配的内存资源,可以通过调用 rte_eal_cleanup()
函数来释放。具体细节请参考 API 文档。
为什么需要清理?
rte_eal_init()
在启动阶段会分配大量资源,包括:- Hugepage 内存
- 注册的 memzone、mempool、ring
- 线程、定时器、中断回调
- 内部结构体与全局状态信息
这些资源在程序退出时,如果不释放,会导致内存泄漏或资源残留,尤其是在 重复运行 DPDK 应用程序 时可能造成:
- Hugepage 无法重新分配;
- 共享对象名冲突(如
ring
,memzone
名字在/dev/hugepages
中残留); - VFIO 绑定失败等。
rte_eal_cleanup() 的作用:
- 清理 EAL 初始化过程中注册的全局资源;
- 释放 Hugepage 映射的内存;
- 注销 memzone、mempool、ring 等资源;
- 还原线程、中断、定时器等状态。
注意:不是所有资源都能完全自动释放,手动创建的 mempool、ring 等资源,仍然建议应用程序自行销毁。
多进程支持(Multi-process Support)
Linux 平台下的 EAL 同时支持 多进程(multi-process)和 多线程(pthread) 的部署模型。详情请参阅章节《Multi-process Support》。
内存映射发现与内存预留
大块连续物理内存的分配是通过 Hugepage 实现的。EAL 提供了一套 API,用于在这块连续内存中预留具名的内存区域(memory zone)。该内存预留 API 同时也会返回该 memory zone 对应的 物理地址 给用户。
DPDK 的内存子系统可以在两种模式下运行:
- 动态模式(dynamic mode)
- 传统模式(legacy mode)
动态模式
在此模式下,DPDK 应用对 hugepage 的使用量会根据应用的请求 动态增长或收缩。任何通过 rte_malloc()
、rte_memzone_reserve()
或其他方法的内存分配操作,都可能会导致系统中新增 hugepage;而任何内存释放操作,也可能会将 hugepage 归还给系统。
在此模式下分配的内存不保证 IOVA 连续(I/O 虚拟地址连续)。
如果你需要大块的 IOVA 连续内存(“大块”指的是“超过一页”),建议:
- 要么使用 VFIO 驱动绑定所有物理设备(IOVA = VA,绕过物理地址);
- 要么使用 legacy 模式。
对于确实要求 IOVA 连续的内存块,建议使用 rte_memzone_reserve()
并指定 RTE_MEMZONE_IOVA_CONTIG
标志。这样,内存分配器将确保无论当前使用哪种内存模式,要么分配满足要求,要么直接失败。
在该模式下,无需通过 -m
或 --socket-mem
命令行参数预分配内存,但仍可以这么做。预分配的内存会被 “锁定”(pinned),即不会被应用释放回系统。即使如此,仍然可以动态申请/释放更多内存,但预分配的部分是不可回收的。
如果没有指定 -m
或 --socket-mem
,那么不会预先分配任何内存,所有内存会在运行时按需分配。
此模式下,还可使用 --single-file-segments
命令行选项。该选项会将一个 memseg list 中的页放到一个文件里,而不是每页一个文件。虽然通常不需要,但在如 userspace vhost 这类场景下可能有用(此类应用对可传递的页文件描述符数量有限制)。
如果应用(或 DPDK 内部代码,例如设备驱动)希望在内存新增时收到通知,可以通过 rte_mem_event_callback_register()
注册回调函数。当 DPDK 的内存映射发生变化时,会调用该函数。
如果应用希望在内存分配 超过指定阈值前 被通知,并有机会拒绝该分配,可以使用 rte_mem_alloc_validator_callback_register()
注册验证器回调函数。
EAL 提供了默认的验证器回调函数,可以通过 --socket-limit
命令行参数启用,以限制 DPDK 应用可用的最大内存量。
警告:
DPDK 的内存子系统内部使用了 IPC,因此内存分配/回调 与 IPC 不能混用:
- 不可在内存相关的回调函数中执行内存分配或释放操作;
- 也不可在这些回调中使用 IPC;
- 同样,在 IPC 回调中执行内存操作也不安全。
传统模式
该模式通过向 EAL 添加命令行参数 --legacy-mem
来启用。该选项在 FreeBSD 上无效,因为 FreeBSD 仅支持传统模式。
此模式模拟了 EAL 的历史行为,即:
- 在启动时一次性预留所有内存;
- 将所有内存整理成大的 IOVA 连续块;
- 运行时不允许再从系统中申请或释放 hugepage 内存。
如果没有指定 -m
或 --socket-mem
参数,则会预分配 系统中所有可用的 hugepage 内存。
动态模式与传统模式对比
对比项 | 动态模式 | 传统模式 |
---|---|---|
Hugepage 分配时机 | 运行时按需分配 | 启动时一次性分配 |
内存释放 | 支持运行时释放 | 不可释放 |
IOVA 连续性 | 不保证(除非特殊指定) | 默认保证大块 IOVA 连续 |
多进程友好度 | 相对复杂(映射需一致) | 更稳定,适合多进程 |
灵活性 | 高 | 低 |
命令行启用方式 | 默认 | --legacy-mem |
是否需要预设 -m |
可选 | 可选,默认全分配 |
Hugepage 分配匹配
该行为通过在 EAL 启动参数中指定 --match-allocations
启用。此选项 仅在 Linux 上受支持,并且 与 --legacy-mem
或 --no-huge
参数不兼容。
某些应用程序在使用 内存事件回调(memory event callbacks) 时,可能需要 hugepage 在释放时的方式与其分配时完全一致。这些应用还可能要求,任何来自 malloc 堆的内存分配,不应跨越两个属于不同回调事件的分配区域。
对于此类应用,hugepage allocation matching 机制可以满足这两项需求。启用后,可能会增加内存使用量,这取决于应用的内存分配模式。
32bit支持
当以 32 位模式 运行时,会有一些额外的限制。
- 在 动态内存模式(dynamic memory mode)下,默认最多会预分配 2GB 的虚拟地址(VA)空间,并且这部分内存将 全部分配在主逻辑核心所处的 NUMA 节点上,除非通过
--socket-mem
参数指定了具体的 NUMA 分布。 - 在 传统内存模式(legacy mode)下,只有被明确请求的内存段(segment)才会被预分配 VA 空间(加上一些填充,以确保 IOVA 连续性)。
最大内存限制
DPDK 进程在启动时,会预先分配所有可能用于 hugepage 映射的虚拟内存空间,这就为 DPDK 应用程序可使用的内存量设定了一个上限。
DPDK 的内存是以“段列表(segment lists)”的形式进行管理的。每个“段(segment)”严格对应一个物理页。
你可以通过修改以下配置变量,来改变启动时预分配的虚拟内存数量:
配置变量 | 含义 |
---|---|
RTE_MAX_MEMSEG_LISTS |
控制 DPDK 最多可以拥有多少个段列表 |
RTE_MAX_MEM_MB_PER_LIST |
控制每个段列表最多能管理多少 MB 内存 |
RTE_MAX_MEMSEG_PER_LIST |
控制每个段列表中最多能包含多少个 segment(页) |
RTE_MAX_MEMSEG_PER_TYPE |
控制每种内存类型最多能拥有多少个 segment(其中“类型”是 “页大小 + NUMA 节点” 的组合) |
RTE_MAX_MEM_MB_PER_TYPE |
控制每种内存类型最多能管理多少 MB 内存 |
RTE_MAX_MEM_MB |
设置 DPDK 应用可预留的内存总量上限(全局) |
通常情况下,无需修改这些配置。
注意:
“预分配虚拟内存” 与 “预分配 hugepage 内存” 是两回事!
虚拟地址空间:每个 DPDK 进程在启动时都会预留一大片 VA 空间(用于未来映射 hugepage),即使你不立即分配物理页;
hugepage:指实际的物理内存页,它们会在运行时(如启用动态模式时)被映射到预分配的 VA 空间中,或在启动时直接映射(如使用 legacy 模式)。
巨页映射
Linux 支持以下映射方式组合(可以任意组合):
项目 | 默认行为 | 可选行为 |
---|---|---|
映射来源 | 使用 hugetlbfs 中的文件 |
或使用匿名映射(--in-memory ) |
文件组织方式 | 每个 hugepage 对应一个文件 | 或使用单个文件映射多个页(--single-file-segments ) |
hugetlbfs 映射(默认)
- 适用于多进程架构,因为 从进程必须映射与主进程相同的 hugepage 文件。
- EAL 在指定的
--huge-dir
目录(或对应 hugepage size 的挂载点)中创建名为rtemap_0
等文件。 - 文件前缀(如
rte
)可通过--file-prefix
修改,用于多主进程场景(同一挂载点避免冲突)。 - 每个 backing 文件通常对应一个 hugepage,使用期间始终保持打开与锁定状态。
如果 hugepage 太多,会导致打开文件数量过多,超出 ulimit -n
的 NOFILE 限制
动态内存模式下的行为(Linux)
- 当从某个 backing 文件映射的全部页都被释放时,EAL 会移除该文件;
- 但如果程序崩溃或内存未正确释放(如未调用
rte_free()
),文件会残留,导致:/sys/kernel/mm/hugepages/hugepages-*/free_hugepages
报告可用 hugepage 数减少;- 其他进程无法重新使用这些 hugepage。
为避免 hugetlbfs 污染,可使用 --huge-unlink
启动参数,令 EAL 在映射前就删除文件。
--huge-unlink=never
模式
- 用于加快 EAL 启动(通常在应用重启时使用);
- 跳过内核清零 hugepage 的步骤,直接 remap 旧文件,保留页中的“脏数据”;
- 此模式下映射的内存段会打上
RTE_MEMSEG_FLAG_DIRTY
标记; - 内存分配器会检测脏段,并在使用如
rte_zmalloc()
分配时自动清除内存; - 此模式下,即使所有页已被释放,EAL 也不会删除 backing 文件,以便 应用重启时复用。
匿名映射(--in-memory
)模式
- 不使用
hugetlbfs
,因此:- 不支持多进程(因为其他进程无法访问相同的匿名映射);
- 不需要 root 权限(但仍受限于
MEMLOCK
,即最大锁定内存); - 不会留下文件,也无文件名冲突问题;
- 如果系统支持
memfd_create(2)
(构建时与运行时都支持):- DPDK 内存管理器仍可为 memory segments 提供文件描述符;
- 适用于 vhost-user 使用 VirtIO 时传递 fd;
- 仍可能因打开文件数量过多而受限于
NOFILE
; - 可参考 Segment File Descriptors 部分优化。
段文件描述符
在 Linux 系统中,大多数情况下 EAL 会为每个内存段存储对应的 文件描述符(file descriptor,简称 FD)。
当使用较小页面(page size)时,这可能会引发问题,主要是由于 glibc 库的底层限制。例如,如果文件描述符数量太多,某些 Linux API(如 select()
)可能无法正常工作,因为 glibc 不支持超过一定数量的 FD。
为了解决这个问题,有两个可行方案:
- 推荐方案:使用
--single-file-segments
模式。- 此模式不会为每一页都分配一个文件描述符;
- 同时仍与 Virtio 的 vhost-user 后端保持兼容;
- 注意:该选项不能与
--legacy-mem
模式同时使用。
- 替代方案:使用更大的页大小(如 1GB hugepage)。
- 因为使用较大的页面可以用更少的页覆盖相同内存区域;
- 从而减少 EAL 内部存储的文件描述符数量。
Hugepage 工作线程栈
当指定了 --huge-worker-stack[=size]
EAL 启动参数时,工作线程的栈(stack)会从 NUMA 节点本地的 hugepage 内存中分配。
如果没有显式指定 size
参数,线程栈大小将默认为系统的 pthread 栈大小。
从 hugepage 分配的线程栈 不受保护页(guard pages)保护;
如果启用了此选项,必须确保线程栈大小足够,以防止栈溢出;
与普通线程栈一样,从 hugepage 分配的线程栈大小是 固定的,不会动态扩展。
支持外部分配内存
DPDK 支持使用外部分配的内存。主要有两种方式可将外部内存纳入 DPDK 的内存管理体系:
- 通过 malloc heap API 使用外部分配内存(推荐方式)
- 手动管理外部分配内存,不使用 DPDK 分配器
方式一:使用 malloc heap API 来管理外部分配内存(推荐)
这种方式将外部内存集成进 DPDK 的 malloc heap
管理机制中。其原理是:
- 为外部内存分配一个特殊的 socket ID(DPDK 默认认为无效的 ID);
- 在调用
rte_malloc()
或其他支持 socket ID 的接口时,指定这个 socket ID 即可使用外部分配内存; - 包括像
rte_ring_create()
等结构体创建 API 也可间接使用该内存; - 这样还能支持 DMA 映射,即自动为外部内存段建立 DMA 映射关系。
由于 DPDK 无法验证外部分配内存是否有效,正确性与同步由用户自己负责。多进程同步也需用户维护。
推荐流程如下:
- 获取外部内存区域的指针(如来自 mmap、hugetlbfs、共享内存等)
- 创建一个具名的 malloc heap:
rte_malloc_heap_create("my_ext_heap")
- 将内存区域加入该 heap:
rte_malloc_heap_add_memory(...)
- 若未提供 IOVA 表,则假定不可用于 DMA。
- 多进程使用时:其他进程需手动 attach 内存区域:
rte_malloc_heap_attach_memory(...)
- 获取该 heap 对应的 socket ID:
rte_malloc_heap_get_socket()
- 像使用普通 DPDK heap 一样使用该 socket ID:
rte_malloc("my_ext_heap", size, align, socket_id)
- 不再使用时:先让其他进程 detach,再从 heap 中移除内存段,最后销毁 heap。
优点:
- 使用 DPDK 原生分配器;
- 支持所有基于
socket ID
的 DPDK 数据结构; - 支持 DMA 映射;
- 更容易与现有代码集成。
方式二:不通过 DPDK 分配器,手动管理外部内存
有些应用不希望引入 DPDK 的 heap
系统(例如自己管理内存),这时可使用 rte_extmem_*
系列 API,仅将外部分配内存注册到 DPDK 页表中,以便部分 DPDK API(如地址转换)识别它。
这类内存不会参与 rte_malloc
,完全由用户管理。
流程如下:
- 获取外部内存区域指针
- 注册内存:
rte_extmem_register(...)
- 若不提供 IOVA 表,则假定无法用于 DMA
- 多进程中:其他进程需 attach 内存区域
- 需要 DMA 映射时:使用
rte_dev_dma_map()
显式映射设备 - 直接在应用中使用内存
- 不再使用时:
- 若映射了 DMA,先
rte_dev_dma_unmap()
; - 然后调用
rte_extmem_unregister()
; - 其他进程需先 detach。
- 若映射了 DMA,先
开发者须知:
- 你必须自己负责分配的内存是否有效、是否被多个进程正确 attach/detach、是否对 DMA 有效。
- 多进程间共享时,在主进程注册内存后,从进程需要使用 attach API 连接该内存区域,否则访问会失败。
- 不能随意释放外部分配的内存,除非已确保没有其他进程正在使用。
每个 lcore 和共享变量
默认情况下,静态变量、在 DPDK 堆上分配的内存块、以及其他类型的内存,会被所有 DPDK 线程共享。不过,DPDK 应用程序、库或 PMD(Poll Mode Driver)可以选择为每个线程(或每个 lcore)维护独立的状态信息。
管理每线程(lcore)数据的方式有三种主要方法:
- lcore 变量
- 线程本地存储(TLS,Thread-Local Storage)
- 静态数组(RTE_MAX_LCORE 元素的数组)
方法 1:lcore 变量(推荐)
- 使用
__rte_cache_aligned
修饰 +RTE_DECLARE_PER_LCORE()
定义; - 编译期静态分配;
- 每个带有 lcore ID 的线程(无论是 EAL 创建的线程,还是手动注册的)都可以独立访问;
- 生命周期独立于线程,可在线程创建前初始化;
- 适合小对象和性能敏感场景;
#include <rte_per_lcore.h>
RTE_DECLARE_PER_LCORE(int, my_lcore_var);
void init(void) {
RTE_PER_LCORE(my_lcore_var) = rte_lcore_id();
}
void worker(void) {
printf("Thread %d sees %d\n", rte_lcore_id(), RTE_PER_LCORE(my_lcore_var));
}
方法 2:线程本地存储(TLS)
- 使用
__thread
或__declspec(thread)
声明; - 在线程创建时分配,在线程结束时释放;
- 可用于所有类型的线程,不限于 lcore;
- 仅适用于非常小的对象:
- 大对象会导致线程创建变慢;
- 使用 TLS 线程多时,会极大地增加内存占用;
方法 3:静态数组 [RTE_MAX_LCORE](旧式做法)
-
声明形如:
static struct obj my_data[RTE_MAX_LCORE];
-
使用
rte_lcore_id()
作为索引访问; -
早期 DPDK 中常见,但 现在已不推荐,原因包括:
问题 描述 假共享(false sharing) 多核线程访问相邻数据时会影响彼此的缓存行 内存碎片严重 每个元素需加 RTE_CACHE_GUARD
+ 对齐,造成浪费访问不安全 如果未初始化 lcore ID,访问索引可能越界
static struct my_struct {
int val;
char name[32];
} __rte_cache_aligned data[RTE_MAX_LCORE];
void run() {
int id = rte_lcore_id();
data[id].val = id;
}
建议使用方式对比
方法 | 特点 | 优点 | 缺点 | 推荐场景 |
---|---|---|---|---|
lcore 变量 |
预编译绑定,每个 lcore 独立变量 | 快速、安全,生命周期独立 | 只适合 DPDK 线程 | 高性能数据路径中 |
TLS |
每线程变量 | 灵活、兼容 POSIX | 慢、资源占用大 | 小规模线程变量 |
RTE_MAX_LCORE 数组 |
通过 lcore ID 显式访问 | 简单易懂 | 假共享、碎片多 | 老代码兼容或调试用 |
日志(Logs)
虽然日志系统最初属于 EAL,现在已经由 Log Library(日志库) 提供。
CPU 特性识别
EAL 提供 rte_cpu_get_features()
接口在 运行时查询 CPU 支持的特性(如 AVX、AES-NI 等);
可用于判断当前 CPU 是否支持编译时目标特性集,确保运行兼容性。
用户态中断与事件处理
主线程轮询中断(Host Thread for Interrupt)
- EAL 创建一个 Host Thread 线程监听设备文件(UIO、VFIO);
- 可注册回调用于响应:
- 链路状态变更(Link up/down);
- PCI 设备热拔插。
定时器与定时回调
- 支持异步定时回调(可与中断线程机制复用);
- 例如:链路检测定时器、报文收发调度器。
RX 中断事件
- 默认收发是轮询模型,但在低吞吐时可启用 RX 中断做 event-driven 唤醒;
- 依赖
epoll
+ 中断 FD; - VFIO 支持每队列独立中断,UIO 不支持;
- 启用方式:
- 配置
intr_conf.rxq = 1
; - 使用
rte_eth_dev_rx_intr_enable()
等 API;
- 配置
设备移除事件
- 检测设备被热拔插或资源不可用;
- 应避免在中断上下文直接关闭设备,需异步延后处理。
PCI 设备阻止列表(Block List)
- EAL 支持通过 PCI 设备地址(Domain:Bus:Device.Function)屏蔽指定网卡端口;
- 被阻止的端口将不会被 DPDK 探测或使用。
杂项函数(Misc Functions)
- 锁与原子操作是架构相关的(例如 i686 和 x86_64 分别实现);
- 提供基础并发原语支持。
锁注解(Lock Annotations)
- 使用 clang 的 Thread Safety 检查机制为:
spinlock
、read/write lock
、sequence lock
添加线程安全注解;
- 可在
meson.build
中关闭注解检查; - 避免盲目使用
__rte_no_thread_safety_analysis
,若需关闭请先讨论
IOVA 模式检测
- IOVA 模式决定设备使用的地址类型:物理地址(PA)或虚拟地址(VA);
- 默认采用两步启发式判断:
第 1 步:总线报告需求
- 若所有总线支持 PA → 使用
RTE_IOVA_PA
; - 若所有支持 VA → 使用
RTE_IOVA_VA
; - 若有冲突 → 使用
RTE_IOVA_DC
(延后决策);
第 2 步:系统可用性判断
- 若最终选定
RTE_IOVA_PA
,但系统 无权限读取物理地址(如非 root 用户),则 初始化失败; /sys/kernel/iommu_groups
存在时 → 使用 VA 模式;- 否则默认尝试 PA 模式。
VA 模式更推荐:
所有驱动都应兼容 VA;
内存分配更快,减少启动时间;
支持 IOVA 连续内存(如大页需求)更灵活;
若驱动只支持 VA 模式,会设置 RTE_PCI_DRV_NEED_IOVA_AS_VA
标志。
IOVA 模式配置
- 如果自动检测不符合期望(如存在虚拟设备),可通过:
--iova-mode=va
强制使用虚拟地址;--iova-mode=pa
强制使用物理地址。
最大 SIMD 向量位宽配置(Max SIMD Bitwidth)
- 控制 DPDK 使用的最大 SIMD 指令集位宽,决定是否启用向量化路径(如 AVX2、AVX512);
- 设置方式:
- 启动前调用:
rte_vect_set_max_simd_bitwidth(uint16_t bitwidth)
; - 或使用启动参数:
--force-max-simd-bitwidth=256
;
- 启动前调用:
- 查询当前值:
rte_vect_get_max_simd_bitwidth()
;
支持值定义如下:
enum rte_vect_max_simd {
RTE_VECT_SIMD_DISABLED = 64,
RTE_VECT_SIMD_128 = 128,
RTE_VECT_SIMD_256 = 256,
RTE_VECT_SIMD_512 = 512,
RTE_VECT_SIMD_MAX = INT16_MAX + 1,
};
判断向量路径示例:
if (rte_vect_get_max_simd_bitwidth() >= RTE_VECT_SIMD_512)
// 使用 AVX-512 路径
else if (rte_vect_get_max_simd_bitwidth() >= RTE_VECT_SIMD_256)
// 使用 AVX2 路径