侧边栏壁纸
  • 累计撰写 53 篇文章
  • 累计收到 3 条评论

P5-01 · DMA/IOVA/IOMMU:设备看到的地址,从来不是你 malloc 出的那个

PacketForge
2026-08-18 / 0 评论 / 0 阅读 /
对应路线图: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 的检查)。

DMA 与 IOMMU 对比

🐧 内核对照:同一件事,谁来做翻译

维度内核驱动DPDK
抽象层DMA-API(dma_map_ops 虚表,dma-iommu.c:1613)IOVA 全局三态(PA/VA/DC),无 per-device 虚表
选择时机设备注册时挂表(:1656)EAL 启动时一次选定(eal.c:1066)
映射粒度sg 段级,按需 map/unmapmempool 大块预映射,mbuf 只记 IOVA 偏移
失败语义降级直通 + pr_warn(:1661)启动即报错退出(:1072-1076)

内核把 DMA 当"每次 I/O 都建立的一段契约",DPDK 把 IOVA 当"一次启动定死的世界设定"。性能取向不同:内核要通用和隔离,DPDK 要热路径零开销。

面试 3 问

  1. IOVA 和 PA、VA 什么关系? → 有 IOMMU 时 IOVA 是设备视角的虚拟地址,由 IOMMU 页表映射到 PA(iommu_map_sg 建表,dma-iommu.c:1345);无 IOMMU 时 IOVA 就是 PA。VA 是 CPU 视角的虚拟地址,和 IOVA 是两套体系。
  2. 内核怎么决定走不走 IOMMU? → 设备注册时 iommu_setup_dma_ops 检查默认域,可用就把 dev->dma_ops 换成 iommu_dma_ops(:1656),失败则保留直通 ops 并打 warning(:1661)。
  3. 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 模式是生产部署的默认答案。


下一篇:P5-02 · VFIO 源码深读:把设备安全地交给用户态

0

评论 (0)

取消