RDMA - ODP按需分页设计原理-优点-源码浅析

术语

ODP: 按需分页

HMM: 异构内存管理(Heterogeneous Memory Management)

显示/隐式ODP

  • 显式 ODP在显式 ODP 中,应用程序仍注册内存缓冲区以进行通信,但此操作用于定义 IO 的访问控制,而不是固定页面。ODP 内存区域 (MR) 在注册时不需要具有有效的映射。(IBV_EXP_ACCESS_ON_DEMAND)

  • 隐式 ODP在隐式 ODP 中,应用程序会获得一个特殊的内存密钥,该密钥代表其完整的地址空间。所有引用此密钥的 IO 访问(受与密钥关联的访问权限约束)都不需要注册任何虚拟地址范围。(IBV_EXP_IMPLICIT_MR_SIZE)

ODP预取 (IBV_EXP_PREFETCH_WRITE_ACCESS) , 驱动程序可以预取给定范围的页面并映射它们以供 HCA 访问。预取动词仅适用于 ODP MR,并且会尽最大努力完成,并且可能会忽略错误

  • 无效虚拟页: 未映射的页面, 由于页面不再固定,OS 内核可以交换和迁移页面

简介

什么是按需分页?

应用程序使用系统调用(ibv_reg_mr)向 RDMA 适配器注册内存,然后将引用相应虚拟地址的 IO 操作直接发布到硬件。到目前为止,这是通过在注册调用期间固定内存来实现的。按需分页的目标是避免固定已注册内存区域 (MR) 的页面。这将使用户获得与交换其进程地址空间的任何其他部分时相同的灵活性。我们不必要求整个 MR 都适配物理内存大小,而是允许 MR 可以更大,并且只适合物理内存中的当前工作集。这可以使RDMA 进行编程变得更加简单。今天,处理的数据多于物理内存RAM 的开发人员需要在其进程的整个生命周期内取消注册和重新注册内存区域,或者保留单个内存区域并将数据复制到其中。按需分页将允许这些开发人员在其进程生命周期开始时注册单个 MR,并让操作系统管理在给定时间需要获取哪些页面。将来,我们可能能够为每个进程提供单个内存访问密钥,该密钥将整个进程的地址作为一个大内存区域提供,所以开发人员根本不需要注册内存区域

当前内存注册的痛点

注册内存的大小必须适配物理内存 • 应用程序必须具有内存锁定权限 • 由于以下操作存在, 持续同步地址空间和 HCA 之间的转换表很困难 – 地址空间更改(malloc、mmap、堆栈) – NUMA 迁移 – fork() • 注册是一项成本高昂的操作 – 没有引用局部性

尽管固定 MR 在 RDMA 应用中很普遍,但它有几个限制。它将大量页面固定在物理内存中(例如,数十或数百 GB),占用宝贵的 DRAM 资源。应用程序只能初始化不大于可用物理内存的 MR。同时,它失去了过度使用、页面迁移、透明大页面等机会。面对这些限制,提出了 ODP(按需分页MR)

ODP带来的好处

简化编程 – MPI 集合通信无需动态注册内存 – 无需管理专用缓冲池(MR pool) • 几乎无限的内存注册(如支持TB级别的MR) – 无需特殊权限 • 优化物理内存以保存工作集

内存注册流程

image-20240613080843387

设计思路

• 仅限内核 – 对应用程序透明 • 通用代码 (ib_core) 任务 – 管理页面失效(page invalidations) • 注册 MMU 通知程序调用(MMU notifier calls) • 提供失效上下文 • 确定页面失效和 MR 之间的交集 – 支持缺页错误(page faults) • 在失效和缺页错误之间进行同步 • 将用户页面换入并映射到 dma

• 驱动程序代码 (mlx4_core/ib) 任务 – 处理页面错误 • 捕获并分类 HW 硬件页面错误 • 提供页面错误上下文 – 针对每个QP的请求者/响应者(Per-QP work_struct) – 处理 HW 页面失效

参考数据结构

image-20240614231111245

页面换入参考流程

image-20240614231145038

页面失效参考流程

image-20240614231221001

ODP的RNIC页表与CPU页表

image-20240614233506173

图 1, 我们展示了 ODP MR 的 RNIC 页表并将其与 CPU 页表进行比较。有效的虚拟页面映射到页表中的物理页面。无效的虚拟页面不会被映射。v5 在 CPU 页表中有效,但在 RNIC 页表中无效

同步 CPU 和 RNIC 页表的流程

image-20240614233725255

ODP MR 与固定 MR 不同,因为它不会将页面固定在物理内存中,RNIC 页表将一些虚拟页面映射到物理页面(我们称之为有效虚拟页面),其余页面保持未映射状态,即无效虚拟页面。由于页面不再固定,OS 内核可以交换和迁移页面。应用程序能够公开大于物理内存的 MR。由于虚拟到物理的映射可能会发生变化,CPU 和 RNIC 页表通过上图所示的三个流程同步。

  1. 故障。当 RDMA 请求访问无效虚拟页面上的数据时,

    (1a) RNIC 会停止 QP 并引发 RNIC 页面错误 1 中断。

    (1b) 驱动程序通过 hmm_range_fault [2] 向 OS 内核请求虚拟到物理映射。OS 内核会在这些虚拟页面上触发 CPU 页面错误,并在必要时填充 CPU 页表。

    (1c) 驱动程序更新 RNIC 页表上的映射

    (1d) 恢复 QP。

  2. 失效。当 OS 内核尝试在交换或页面迁移等场景中取消虚拟页面映射时,

    (2a) 它会通过 mmu_interval_notifier [2] 通知 RNIC 驱动程序使虚拟页面失效。

    (2b) RNIC 驱动程序从 RNIC 页表中删除虚拟到物理的映射。

    (2c) 驱动程序通知内核 RNIC 不再使用物理页面。然后,OS 内核修改 CPU 页表并重用物理页面。ODP MR 依靠故障和失效流来同步 CPU 和 RNIC 页表。RNIC 页表中的所有有效虚拟页面都保证在 CPU 页表中有效,但反之则不然。当内核将无效虚拟页面更改为有效虚拟页面时,它不会通知驱动程序。如图 1 所示,v5 在 CPU 页表中有效,但在 RNIC 页表中仍然无效。

  3. 建议流程解决了上述问题。应用程序可以主动请求 RNIC 驱动程序填充 RNIC 页表中的范围。RNIC 驱动程序通过步骤 (3a) – (3b) 完成建议,这些步骤与步骤 (1b) – (1c) 相同。

页面预取及好处

用于预取页面的新动词(verbs) • 用途 – 预热新内存映射 – MPI 约会协议优化 – UD 响应器优化

测试

• ODP 支持 – 为 IB 和 RoCE 实现了所有 RC 传输流 • 不包括 SRQ 和内存窗口 – IB 和 RoCE 上的 UD – 原始以太网 QP • 互操作性 – 同时运行的非 ODP 应用程序的延迟几乎不受影响 – 混合请求者/响应者也能很好地工作 • 内存驻留 ODP 页面的本机性能 • 页入性能 – 4K 页面错误大约需要 135us – 4M 页面错误大约需要 1ms

执行时间细分(请求发送者)

image-20240614231550757

未来工作

• 超大 MR 支持 • 支持 TB 级的 MR • 隐式 ODP – 预先注册完整的应用程序地址空间 – 有效消除内存注册 • 元数据大小必须是当前映射内存的函数,而不是 MR 大小 – 适用于所有数据结构(IB 核心、驱动程序和硬件) • 内存窗口 (MW) 成为控制访问权限的主要工具

根据 IO 访问更新 PTE 访问/脏位 • 页面失效批处理 – swapper中的页面驱逐 – NUMA 迁移过程 • 将 ODP 扩展到客户物理机器翻译以实现虚拟化

总结

RDMA 性能很棒 – 但需要精心设计 • ODP 简化了 RDMA 编程和部署 – 将内存管理移至操作系统 – 解除内存固定限制 • ODP 不会牺牲性能或互操作性 • ODP 消除了内存注册

RDMA SoftRoCE(rxe) 支持ODP

该补丁系列在 SoftRoCE(rxe) 驱动程序上实现了按需分页功能,到目前为止,该功能仅在 mlx5 驱动程序中可用。作为一项重要更改,有必要将三重任务集(请求者、响应者和完成者)转换为工作队列,因为它们必须能够休眠才能在访问 MR 之前触发页面错误。对此进行了一些讨论,Bob Pearson 发布了用于转换的补丁集[2]。我发现它会导致软锁定,并于 11 月 18 日向他报告。但是,自那以后没有任何进展。自 RFC 补丁集首次发布以来,该问题一直阻碍着此 ODP 补丁集的进展,已超过三个月。由于他的补丁集由 13 个补丁组成,太大太复杂,无法找到软锁定的原因,因此我准备了一个补丁,可以实现转换而不会出现问题。我尽量减少更改,以便他可以轻松地对其进行他最初打算进行的更改。

[概述]

当应用程序注册内存区域 (MR) 时,RDMA 驱动程序通常会将页面固定在 MR 中,以便在 RDMA 通信期间物理地址永远不会改变。这需要 MR 适配物理内存,不可避免地会导致内存压力。另一方面,按需分页 (ODP) 允许应用程序注册 MR 而不固定页面。当驱动程序需要时,它们会被调入页面,当操作系统回收时,它们会被调出页面。因此,可以注册一个不用适配物理内存的大型 MR,而不会占用太多物理内存。

[为什么要添加此功能?]

我们富士通为 RDMA 做出了贡献,希望将其与持久内存一起使用。持久内存可以托管文件系统,允许应用程序直接读取/写入文件,而无需涉及页面缓存。这称为 FS-DAX(文件系统直接访问)模式。存在一个问题,即启用 DAX 的文件系统上的数据无法使用软件 RAID 或其他硬件方法复制。使用具有高速连接的 RDMA 进行数据复制是解决该问题的最佳方法。但是,已知有一个问题阻碍了 RDMA 与 FS-DAX 的结合使用。当在同一节点上同时处理对文件的 RDMA 操作和文件元数据的更新时,可以执行非法内存访问,而忽略更新的元数据。这是因为 RDMA 操作不经过页面缓存,而是直接访问数据。曾经有人努力[3]解决这个问题,但最终被否决了。虽然没有通用的解决方案,但可以使用 ODP 功能解决这个问题。它使内核驱动程序能够在处理 RDMA 操作之前更新元数据。我们增强了 rxe 以加快持久内存的使用。我们对 rxe 的贡献包括 RDMA Atomic write[4] 和 RDMA Flush[5]。随着它们与 ODP 合并,开发人员将有一个环境来创建和测试带有 FS-DAX 的 RDMA 软件。为此目的,正在开发一个库(librpma)[6]。任何人都可以使用此环境,无需任何特殊硬件,只需一台具有普通 NIC 的普通计算机即可,尽管在性能方面不如硬件实现。

[设计考虑]

ODP 仅在 mlx5 中可用,但 ib_uverbs(infiniband/core)中提供了可以常用的函数和数据结构。接口严重依赖于 HMM 基础架构[7],此补丁集尽可能多地使用它们。虽然 mlx5 同时具有显式和隐式 ODP 功能以及预取功能,但此补丁集仅实现了显式 ODP 功能。当响应者和完成者处于睡眠状态时,由于接收队列溢出而发生数据包丢失的可能性更大。涉及多个队列,但由于 SoftRoCE 使用 UDP,因此最重要的是 UDP 缓冲区。大小可以在 net.core.rmem_default 和 net.core.rmem_max sysconfig 参数中配置。用户应该在数据包丢失的情况下更改这些值,但页面错误通常不会持续太久而导致问题。

[ODP 如何工作?]

“struct ib_umem_odp” 用于管理页面。它是在每个启用 ODP 的 MR 注册时为其创建的。此结构包含一对数组 (dma_list/pfn_list),用作驱动程序页表。DMA 地址和 PFN 存储在驱动程序页表中。它们在页面输入和页面输出时更新,两者都使用 ib_uverbs 层中的通用接口。当请求者、响应者或完成者访问 MR 以处理 RDMA 操作时,可能会发生页面输入。如果他们发现正在访问的页面不在物理内存中,或者页面上未设置必要的权限,他们会引发页面错误,使页面具有适当的权限,同时更新驱动程序页表。确认页面存在后,它们执行内存访问,例如读取、写入或原子操作。页面输出由页面回收或文件系统事件触发(例如,正在用作 MR 的文件的元数据更新)。创建启用 ODP 的 MR 时,驱动程序会注册一个 MMU 通知器回调。当内核发出页面失效通知时,会触发回调以取消映射 DMA 地址并更新驱动程序页表。之后,内核释放页面。

[支持的操作]

RC 连接上支持所有传统操作。新的 Atomic write[4] 和 RDMA Flush[5] 操作不包含在此补丁集中。我将在合并此补丁集后发布它们。在 UD 连接上,支持 Send、Recv 和 SRQ-Recv。

[如何测试 ODP?]

只有少数资源可用于测试。推荐使用 rdma-core 中的 pyverbs 测试用例和 perftest[8]。除此之外,ibv_rc_pingpong 命令也可以用于测试。请注意,您可能必须从上游构建 perftest,因为旧版本不能正确处理 ODP 功能。该树可从以下 URL 获得:https://github.com/daimatsuda/linux/tree/odp_v3 [未来工作] 我的下一步工作是使用 ODP 启用新的 Atomic write[4] 和 RDMA Flush[5] 操作。之后,我将实现预取功能。它允许应用程序使用 ibv_advise_mr(3) 触发页面错误以优化性能。一些现有软件(如 librpma[6])使用此功能。此外,我认为我们将来还可以添加隐式 ODP 功能

MLX5支持ODP

以下补丁集在 RDMA 堆栈和 mlx5_ib Infiniband 驱动程序中实现了按需分页 (ODP) 支持。

页面错误通常如何工作?

使用固定内存区域,驱动程序会将虚拟地址映射到总线地址,并将这些地址传递给 HCA 以将它们与新 MR 关联。使用 ODP,驱动程序现在可以将 MR 中的某些页面标记为不存在。当 HCA 尝试执行通信操作的内存访问时,它会注意到页面不存在,并向驱动程序发出页面错误事件。此外,HCA 执行传输协议所需的任何操作以暂停通信,直到页面错误得到解决。在收到页面错误中断后,驱动程序首先需要知道页面错误发生在哪个虚拟地址上,以及在哪个内存密钥上。处理发送/接收操作时,此信息位于工作队列内。驱动程序读取所需的工作队列元素,并解析它们以收集地址和内存密钥。对于其他 RDMA 操作,HCA 生成的事件仅包含虚拟地址和 rkey,因为不涉及工作队列元素。有了 rkey,驱动程序可以在其数据结构中找到相关的内存区域,并计算完成操作所需的实际页面。然后,它使用 get_user_pages 将所需的页面检索回内存,获取 dma 映射,并将地址传递给 HCA。最后,驱动程序通知 HCA 它可以继续对遇到页面错误的队列对进行操作。get_user_pages 返回的页面通过释放其引用立即取消固定。

如何处理失效?

补丁添加了基础架构以将 RDMA 堆栈订阅为 mmu 通知程序客户端 [1]。每个使用 ODP 的进程都会注册一个通知程序客户端。收到页面失效通知时,会将它们传递给 mlx5_ib 驱动程序,该驱动程序会使用新的、不存在的映射更新 HCA。只有在刷新 HCA 的页表缓存后,通知程序才会返回,从而允许内核释放页面。

支持哪些操作?

目前,RC 协议仅支持发送、接收和 RDMA 写入操作,UD 协议也支持发送操作。我们希望将来能够实现对其他传输和操作的支持。

补丁集的结构

首先,补丁适用于 roland/infiniband.git 树中的 for-next 分支,应用了签名补丁 [2],还应用了重构 umem 以使用线性 SG 表 [3] 的补丁。

补丁 1-5:第一组补丁为 IB 核心层添加了页面错误支持,允许注册 MR 而无需固定其页面。第一个补丁添加了功能位、配置选项以及用于从用户空间查询分页功能的方法。接下来的两个补丁 (2-3) 对 ib_umem 类型进行了一些必要的更改。补丁 4 和 5 分别添加了分页支持和无效支持。

补丁 6-9:下一组补丁包含对 mlx5 驱动程序的一些小修复。补丁 6-7 修复了两个可能影响分页代码的错误,补丁 8-9 添加代码以将缺失信息存储在 mlx5 结构中,这是分页代码正常工作所必需的。

补丁 10-16:这组补丁为 mlx5 驱动程序添加了小规模的新功能并构建了分页支持。补丁 10-11 对 UMR 机制(mlx5 用于更新设备页面映射的内部机制)进行了更改。补丁 12 为 mlx5_core 模块添加了页面错误处理的基础结构支持。补丁 13 为设备配置分页功能,补丁 15 添加了执行部分设备页表更新的函数。最后,补丁 16 添加了辅助函数,用于从驱动程序上下文中的用户空间工作队列读取信息。

补丁 17-20:此补丁集的第四部分终于为 mlx5 驱动程序添加了分页支持。补丁 17 在 mlx5_ib 中添加了基础结构,以处理来自 mlx5_core 的页面错误。补丁 18 添加了处理 UD 发送页面错误和 RC 发送和接收页面错误的代码。补丁 19 添加了对由 RDMA 写入操作引起的页面错误的支持,补丁 20 为 mlx5 驱动程序添加了无效支持,允许动态取消页面映射

ODP代码分析

IBV_ACCESS_ON_DEMAND ODP标记位

rdma-core

1
2
3
4
5
6
7
8
9
rc_pingpong.c
...
pp_init_ctx
    if (use_odp)
        const uint32_t rc_caps_mask = IBV_ODP_SUPPORT_SEND | IBV_ODP_SUPPORT_RECV
        ibv_query_device_ex(ctx->context, NULL, &attrx)
        if (!(attrx.odp_caps.general_caps & IBV_ODP_SUPPORT) ||
        attrx.odp_caps.general_caps & IBV_ODP_SUPPORT_IMPLICIT
        access_flags |= IBV_ACCESS_ON_DEMAND

linux-kernel(参考mlx5)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
kernel, drivers/infiniband/hw/mlx5/odp.c

module_init(mlx5_ib_init);
    mlx5_ib_odp_init -> IB/mlx5:添加隐式 MR 支持,添加隐式 MR,覆盖整个用户地址空间。MR 实现为由 1GB 直接 MR 组成的间接 KSM MR。页面和直接 MR  ODP 添加/删除到 MR
    mlx5_imr_ksm_entries = BIT_ULL(get_order(TASK_SIZE) - MLX5_IMR_MTT_BITS)

ibv_reg_mr -> 注册内存时带上ODP标志
.reg_user_mr = mlx5_ib_reg_user_mr
    if (access_flags & IB_ACCESS_ON_DEMAND)
        return create_user_odp_mr(pd, start, length, iova, access_flags, udata)
            struct ib_umem_odp *odp
            if (!IS_ENABLED(CONFIG_INFINIBAND_ON_DEMAND_PAGING))
            mlx5r_odp_create_eq(dev, &dev->odp_pf_eq)
                INIT_WORK(&eq->work, mlx5_ib_eq_pf_action)
                    mempool_refill
                        mempool_free(mempool_alloc(pool, GFP_KERNEL), pool)
                    mlx5_ib_eq_pf_process
                eq->pool = mempool_create_kmalloc_pool(MLX5_IB_NUM_PF_DRAIN, sizeof(struct mlx5_pagefault))
                eq->wq = alloc_workqueue("mlx5_ib_page_fault", WQ_HIGHPRI | WQ_UNBOUND | WQ_MEM_RECLAIM, MLX5_NUM_CMD_EQE)
                eq->irq_nb.notifier_call = mlx5_ib_eq_pf_int
                    mlx5_ib_eq_pf_int -> net/mlx5:将中断处理程序更改为调用链式通知程序,多个 EQ 可能会在后续补丁中共享同一个 IRQEQ 将注册到原子链式通知程序,而不是直接调用 IRQ 处理程序。不使用 Linux 内置共享 IRQ,因为它会强制调用者在调用 free_irq() 之前禁用 IRQ 并清除关联。此补丁是分离 IRQ  EQ 逻辑的第一步
                        mlx5_ib_eq_pf_process
                            INIT_WORK(&pfault->work, mlx5_ib_eqe_pf_action)
                                mlx5_ib_pfault
                                    mlx5_ib_page_fault_resume
                                        MLX5_SET(page_fault_resume_in, in, opcode, MLX5_CMD_OP_PAGE_FAULT_RESUME)
                eq->core = mlx5_eq_create_generic(dev->mdev, &param) -> net/mlx5:将 IRQ 请求/释放与 EQ 生命周期分开,不再在 EQ 创建时请求 IRQ,而是在 EQ 表创建之前请求 IRQ。不再在 EQ 销毁后释放 IRQ,而是在 eq 表销毁后释放 IRQ
                    create_async_eq(dev, eq, param)
                        struct mlx5_eq_table *eq_table
                        create_map_eq(dev, eq, param)
                            u8 log_eq_size = order_base_2(param->nent + MLX5_NUM_SPARE_EQE)
                            INIT_RADIX_TREE(&cq_table->tree, GFP_ATOMIC)
                            mlx5_frag_buf_alloc_node(dev, wq_get_byte_sz(log_eq_size, log_eq_stride), &eq->frag_buf, dev->priv.numa_node) -> net/mlx5e:实现碎片工作队列 (WQ),添加新类型的 struct mlx5_frag_buf,用于分配碎片缓冲区而不是连续缓冲区,并使完成队列 (CQ) 使用它,因为它们很大(Striding RQ 中每个 CQ 的默认值为 2MB
                            mlx5_init_fbc
                            init_eq_buf
                            mlx5_irq_get_index
                            mlx5_fill_page_frag_array
                            MLX5_SET(create_eq_in, in, opcode, MLX5_CMD_OP_CREATE_EQ)
                            mlx5_cmd_exec(dev, in, inlen, out, sizeof(out))
                            mlx5_debug_eq_add(dev, eq)
                mlx5_eq_enable(dev->mdev, eq->core, &eq->irq_nb)
            if (!start && length == U64_MAX) -> start = 0 and max len
                mlx5_ib_alloc_implicit_mr(to_mpd(pd), access_flags)
                    ib_init_umem_odp
                        mmu_interval_notifier_insert -> 先注册, 再通过 hmm_range_fault 填充, 参考异构内存管理HMM: https://blog.csdn.net/Rong_Toa/article/details/117910321
            if (!mlx5r_umr_can_load_pas(dev, length))
            odp = ib_umem_odp_get(&dev->ib_dev, start, length, access_flags, &mlx5_mn_ops)
                ib_init_umem_odp(umem_odp, ops)
            mr = alloc_cacheable_mr(pd, &odp->umem, iova, access_flags)
                page_size = mlx5_umem_dmabuf_default_pgsz(umem, iova)
                or mlx5_umem_find_best_pgsz
                    ib_umem_find_best_pgsz
                rb_key.ats = mlx5_umem_needs_ats(dev, umem, access_flags)
                ent = mkey_cache_ent_from_rb_key(dev, rb_key) -> RDMA/mlx5:引入 mlx5r_cache_rb_key,从使用 mkey 顺序切换到使用新结构作为缓存条目 RB 树的键。该键是 UMR 操作无法修改的所有 mkey 属性。使用此键定义缓存条目并搜索和创建缓存 mkey
                mr = reg_create(pd, umem, iova, access_flags, page_size, false) -> no cache
                    mr = kzalloc(sizeof(*mr), GFP_KERNEL)
                    mr->page_shift = order_base_2(page_size)
                    mlx5_ib_create_mkey(dev, &mr->mmkey, in, inlen)
                        mlx5_core_create_mkey
                            MLX5_SET(create_mkey_in, in, opcode, MLX5_CMD_OP_CREATE_MKEY)
                    set_mr_fields(dev, mr, umem->length, access_flags, iova)
                mr = _mlx5_mr_cache_alloc(dev, ent, access_flags)
                    if (!ent->mkeys_queue.ci)
                        create_cache_mkey(ent, &mr->mmkey.key)
                            mlx5_core_create_mkey -> 创建缓存
                    else
                        mr->mmkey.key = pop_mkey_locked(ent) -> 从缓存中获取
                            last_page = list_last_entry(&ent->mkeys_queue.pages_list, struct mlx5_mkeys_page, list)
                            ent->mkeys_queue.ci--
                        queue_adjust_cache_locked
                set_mr_fields(dev, mr, umem->length, access_flags, iova)
            xa_init(&mr->implicit_children)
            mlx5r_store_odp_mkey(dev, &mr->mmkey) -> RDMA/mlx5:从 ODP 流中清除 synchronize_srcu(),从 ODP 流中清除 synchronize_srcu(),因为作为 dereg_mr 的一部分,它被发现非常耗时。例如,注销 10000  ODP MR,每个 MR 的大小为 2M 大页面,耗时 19.6 秒,相比之下,注销相同数量的非 ODP MR 耗时 172 毫秒。新的锁定方案使用 wait_event() 机制,该机制遵循 MR 的使用计数,而不是使用 synchronize_srcu()。通过这一改变,上述测试所需的时间为 95 毫秒,这甚至比非 ODP 流更好。一旦完全放弃 srcu 使用,就必须使用锁来保护 XA 访问。作为使用上述机制的一部分,我们还可以清理 num_deferred_work 内容并改为遵循使用计数
                xa_store(&dev->odp_mkeys, mlx5_base_mkey(mmkey->key), mmkey, GFP_KERNEL)
            mlx5_ib_init_odp_mr(mr)
                pagefault_real_mr(mr, to_ib_umem_odp(mr->umem), mr->umem->address, mr->umem->length, NULL, MLX5_PF_FLAGS_SNAPSHOT | MLX5_PF_FLAGS_ENABLE) -> RDMA/mlx5:从 pagefault_mr 中分离隐式处理,单个例程在处理隐式父级时,前进到下一个子 MR 的方案非常混乱。此方案只能在处理隐式父级时使用,在处理正常 MR 时不得触发。通过将所有单个 MR 内容直接放入一个函数并在隐式情况下循环调用它来重新安排事物。简化新 pagefault_real_mr() 中的一些错误处理以删除不需要的 goto
                    ib_umem_odp_map_dma_and_lock -> DMA 映射 ODP MR 中的用户空间内存并锁定它。将参数中传递的范围映射到 DMA 地址。映射页面的 DMA 地址在 umem_odp->dma_list 中更新。成功后,ODP MR 将被锁定,以让调用者完成其设备页表更新。成功时返回映射的页面数,失败时返回负错误代码
                        current_seq = range.notifier_seq = mmu_interval_read_begin(&umem_odp->notifier)
                        hmm_range_fault(&range) -> 设备驱动程序填充一系列虚拟地址
                        mmu_interval_read_retry
                        hmm_order = hmm_pfn_to_map_order(range.hmm_pfns[pfn_index])
                        ib_umem_odp_map_dma_single_page(umem_odp, dma_index, hmm_pfn_to_page(range.hmm_pfns[pfn_index]),access_mask)
                            *dma_addr = ib_dma_map_page(dev, page, 0, 1 << umem_odp->page_shift, DMA_BIDIRECTIONAL)
                                dma_map_page(dev->dma_device, page, offset, size, direction)
                    mlx5r_umr_update_xlt
                        mlx5r_umr_create_xlt
                            mlx5r_umr_alloc_xlt
                            dma = dma_map_single(ddev, xlt, sg->length, DMA_TO_DEVICE)
                        mlx5r_umr_set_update_xlt_ctrl_seg
                        mlx5r_umr_set_update_xlt_mkey_seg
                        mlx5r_umr_set_update_xlt_data_seg
                    return np << (page_shift - PAGE_SHIFT)
            return &mr->ibmr

const struct mmu_interval_notifier_ops mlx5_mn_ops = {
	.invalidate = mlx5_ib_invalidate_range,
};


mlx5_ib_eq_pf_process
    switch (eqe->sub_type)
    ...
    INIT_WORK(&pfault->work, mlx5_ib_eqe_pf_action)
    cc = mlx5_eq_update_cc(eq->core, ++cc)
    mlx5_eq_update_ci(eq->core, cc, 1)

总结

  • ODP需要软硬件做整体设计, 处理好缺页异常/失效/状态恢复等逻辑
  • MLX5采用mkey结合红黑树做缓存, EQ事件队列用于异步处理缺页等逻辑
  • 依赖Linux内核异构内存管理API支持(HMM)

参考

在SoftRoCE上支持ODP: https://lwn.net/Articles/918463/

MLX5支持ODP: https://www.spinics.net/lists/linux-rdma/msg18906.html#google_vignette

MLX5支持隐式ODP提交记录: https://github.com/ssbandjl/linux/commit/81713d3788d2e6bc005f15ee1c59d0eb06050a6b

Nvidia RDMA优化内存访问: https://docs.nvidia.com/networking/display/mlnxofedv492240/optimized+memory+access

ODP SSD相关论文(TeRM:使用 SSD 扩展 RDMA 连接内存): https://www.usenix.org/system/files/fast24-yang-zhe.pdf

OFA ODP(用户级网络的按需分页): https://openfabrics.org/images/eventpresos/workshops2013/2013_Workshop_Tues_0930_liss_odp.pdf

rdma-core ODP提交记录, ibv_query_device_ex 报告按需分页功能的功能。 该补丁还添加了 IBV_ACCESS_ON_DEMAND 访问标志,以允许注册启用按需分页的内存区域: https://patchwork.kernel.org/project/linux-rdma/patch/1441292199-8371-3-git-send-email-haggaie@mellanox.com/

Linux内核-HMM异构内存管理: https://docs.kernel.org/translations/zh_CN/mm/hmm.html

晓兵(ssbandjl)

博客: https://cloud.tencent.com/developer/user/5060293/articles | https://logread.cn | https://blog.csdn.net/ssbandjl | https://www.zhihu.com/people/ssbandjl/posts | https://chattoyou.cn

DPU专栏

https://cloud.tencent.com/developer/column/101987

技术会友: 欢迎对DPU/智能网卡/卸载/网络,存储加速/安全隔离等技术感兴趣的朋友加入DPU技术交流群

weixin: ssbandjl

公众号: 云原生云

云原生云