对应路线图:Phase 5 (Day 141-168) | DPDK 24.11.6 LTS | 内核对照 v6.6
代码出处:source/linux-stable/drivers/iommu/dma-iommu.c、source/dpdk-stable-24.11.6/lib/eal/linux/eal.c,引用均标文件:行号
上一篇:P4-03 最小 PCIe 驱动 | 下一篇:P5-02 VFIO
一句话定位
CPU 用页表把 VA 变成 PA,设备做 DMA 时也要有一套自己的"页表":IOMMU 页表。IOVA 就是设备视角的虚拟地址——没有 IOMMU 时 IOVA 直接等于 PA,有 IOMMU 时内核替你维护 IOVA→PA 的映射。DPDK 的 mempool 写进网卡描述符的"物理地址",本质都是 IOVA。
真实源码精读
① 内核的 DMA 抽象:每设备一张 dma_map_ops 虚表(dma-iommu.c:1613-1636)
static const struct dma_map_ops iommu_dma_ops = {
.flags = DMA_F_PCI_P2PDMA_SUPPORTED,
.alloc = iommu_dma_alloc,
.free = iommu_dma_free,
.alloc_pages = dma_common_alloc_pages,
.free_pages = dma_common_free_pages,
// ...省略 alloc_noncontiguous / mmap / get_sgtable ...
.map_page = iommu_dma_map_page, // :1623
.unmap_page = iommu_dma_unmap_page,
.map_sg = iommu_dma_map_sg, // :1625
.unmap_sg = iommu_dma_unmap_sg,
// ...省略 sync_* / map_resource ...
};驱动代码从来不直接调 iommu_map,而是调 dma_map_sg() 宏 → 落到 dev->dma_ops->map_sg。IOMMU 打开时指向这张表,关掉时指向直通实现——上层驱动一行不用改,这就是内核 DMA-API 分层的意义。
② iommu_dma_map_sg:先算总长度,再一次性分配 IOVA(dma-iommu.c:1332-1349)
if (!iova_len)
return __finalise_sg(dev, sg, nents, 0);
iova = iommu_dma_alloc_iova(domain, iova_len, dma_get_mask(dev), dev);
if (!iova) {
ret = -ENOMEM;
goto out_restore_sg;
}
/*
* We'll leave any physical concatenation to the IOMMU driver's
* implementation - it knows better than we do.
*/
ret = iommu_map_sg(domain, iova, sg, nents, prot, GFP_ATOMIC);
if (ret < 0 || ret < iova_len)
goto out_free_iova;
return __finalise_sg(dev, sg, nents, iova);三步走,跟我 P0 学的内存池一个思路——先要一块连续的 IOVA 空间,再把 sg 里散落的物理页逐段映射进去。__finalise_sg(:1349)把算好的 IOVA 写回每个 sg_dma_address,驱动接下来只跟 IOVA 打交道。函数签名在 :1241-1242,开头还要处理 sg 段对齐(:1275 起 for_each_sg 的 pad 逻辑)。
③ 挂载点:IOMMU 可用时偷换 dev->dma_ops(dma-iommu.c:1642-1663)
void iommu_setup_dma_ops(struct device *dev, u64 dma_base, u64 dma_limit)
{
struct iommu_domain *domain = iommu_get_domain_for_dev(dev);
if (!domain)
goto out_err;
// ...省略注释...
if (iommu_is_dma_domain(domain)) {
if (iommu_dma_init_domain(domain, dma_base, dma_limit, dev))
goto out_err;
dev->dma_ops = &iommu_dma_ops; // :1656 关键一行
}
return;
out_err:
pr_warn("Failed to set up IOMMU for device %s; retaining platform DMA ops\n",
dev_name(dev));
}设备注册时调用一次,之后所有 DMA API 全部改道 IOMMU 路径。注意 :1661 的失败路径:IOMMU 初始化失败就保留 platform DMA ops(直通 PA)——系统不崩,只是没有隔离,这种"静默降级"排查问题时要想到。
④ DPDK 侧:启动时三态选择 IOVA 模式(lib/eal/linux/eal.c:1038-1066)
/* if no EAL option "--iova-mode=<pa|va>", use bus IOVA scheme */
if (internal_conf->iova_mode == RTE_IOVA_DC) {
/* autodetect the IOVA mapping mode */
enum rte_iova_mode iova_mode = bus_iova_mode;
if (iova_mode == RTE_IOVA_DC) {
// ...省略 build option / phys addrs 判断 ...
} else if (is_iommu_enabled()) {
/* we have an IOMMU, pick IOVA as VA mode */
iova_mode = RTE_IOVA_VA; // :1054-1057
} else {
/* physical addresses available, and no IOMMU
* found, so pick IOVA as PA.
*/
iova_mode = RTE_IOVA_PA; // :1058-1063
}
}
rte_eal_get_configuration()->iova_mode = iova_mode;IOVA=VA 是默认最优解:有 IOMMU 就选 VA(mempool 里存的是 mbuf 虚拟地址,IOMMU 页表替设备翻译);没有 IOMMU 才退回 PA。所以 DPDK 起不来的常见报错之一就是 PA 模式 + 物理地址不可得(eal.c:1072-1076 的检查)。
🐧 内核对照:同一件事,谁来做翻译
| 维度 | 内核驱动 | DPDK |
|---|---|---|
| 抽象层 | DMA-API(dma_map_ops 虚表,dma-iommu.c:1613) | IOVA 全局三态(PA/VA/DC),无 per-device 虚表 |
| 选择时机 | 设备注册时挂表(:1656) | EAL 启动时一次选定(eal.c:1066) |
| 映射粒度 | sg 段级,按需 map/unmap | mempool 大块预映射,mbuf 只记 IOVA 偏移 |
| 失败语义 | 降级直通 + pr_warn(:1661) | 启动即报错退出(:1072-1076) |
内核把 DMA 当"每次 I/O 都建立的一段契约",DPDK 把 IOVA 当"一次启动定死的世界设定"。性能取向不同:内核要通用和隔离,DPDK 要热路径零开销。
面试 3 问
- IOVA 和 PA、VA 什么关系? → 有 IOMMU 时 IOVA 是设备视角的虚拟地址,由 IOMMU 页表映射到 PA(iommu_map_sg 建表,dma-iommu.c:1345);无 IOMMU 时 IOVA 就是 PA。VA 是 CPU 视角的虚拟地址,和 IOVA 是两套体系。
- 内核怎么决定走不走 IOMMU? → 设备注册时
iommu_setup_dma_ops检查默认域,可用就把dev->dma_ops换成iommu_dma_ops(:1656),失败则保留直通 ops 并打 warning(:1661)。 - DPDK 的
--iova-mode为什么默认选 VA? → 有 IOMMU 时选 VA 可让 mbuf 直接存虚拟地址、避免 PA 碎片问题(eal.c:1054-1057);没有 IOMMU 且能读 /proc/self/pagemap 才退 PA(:1058-1063)。
🔧 动手实验
J3455 支持 VT-d,做一次"开关 IOMMU"的对照实验:
# 1. 默认开启:dmesg 确认 IOMMU 生效
dmesg | grep -i -E "iommu|dmar" | head
# 期望看到 DMAR: IOMMU enabled / dmar irq 等字样
# 2. 跑 testpmd,看 EAL 启动日志的 IOVA 选择
./testpmd -c 0x3 -n 2 -- -i
# 期望日志:"Selected IOVA mode 'VA'"
# 3. 重启,Grub 内核参数加 intel_iommu=off(关掉 IOMMU)
# 再跑 testpmd,日志应变为 "Selected IOVA mode 'PA'"
# 4. 两种模式下各跑 5 分钟 rxonly,记录 PPS/CPU数据落点:perf-data/P5/01-iommu-mode.md(两种模式的 EAL 日志、dmesg 证据、PPS 对比)。预期:小包转发下 VA/PA 吞吐基本持平(IOMMU 页表走 cache),但 VA 模式是生产部署的默认答案。
评论 (0)