弹性RDMA是阿里云8代ECS/EGS的标配RDMA加速能力,可以为用户提供高性能的RDMA网络的加速能力。在实际使用中,部分应用存在注册大量内存(如:将实例的全部内存都注册成RDMA的MR(Memory Region))的需求。但是由于设备规格约束,导致无法成功注册(提示-EIO,Input/Output Error)。本文描述一种可行的办法,以便实现大量内存的注册。
eRDMA支持的单个MR大小可以通过ibv_devinfo -v | grep mr_size查询得到,如下图所示:
上图表明单个MR的最大大小为64GB。由于硬件存在MR页表总数的规格约束,默认PageSize(4096)的情况下,整个实例可以注册的总的MR大小是有上限的。如果内存是页对齐,同时以4096B的PageSize注册,可以注册的总内存量是整个内存的25%左右(非内存型实例的经验值)。
对于绝大多数应用,实际是不需要注册大量内存的。但是对于某些特定应用的大内存注册场景,我们需要突破如上限制。eRDMA限制的是注册内存的总页表数,Linux提供了大页机制,通过大页分配的内存可以做到2MB/1GB的物理连续。将通过大页分配的内存注册成MR,MR就可以使用更大的Page Size来下发页表,进而减少消耗的页表数量,达到成功注册大内存的目的。
大页的配置方法有很多,本文采用echo <HugePageCnt> | sudo tee /proc/sys/vm/nr_hugepages的方式让操作系统预留一定数目的大页。其他方案不在本文讨论范围,可以自行查阅相关材料。
我们使用ecs.r8a.32xlarge 的实例规格来进行实验,操作系统搭配ubuntu 22.04。该实例提供了约1TB的内存。常规的注册方案,仅能注册不到128G。我们按照以下步骤进行操作。
注册超过4G内存时,由于内核代码存在缺陷,有概率会导致物理连续大页所计算出的page size过小,详情参考:https://lore.kernel.org/all/20250217141623.12428-1-mrgolin@amazon.com/。eRDMA驱动的新版本(驱动程序包大包版本号1.5.2)进行了规避,请先手动更新至该版本的驱动。可以使用如下命令进行更新:
sudo wget http://mirrors.cloud.aliyuncs.com/erdma/env_setup.sh bash env_setup.sh --url http://mirrors.cloud.aliyuncs.com/erdma/erdma_installer-1.5.2.tar.gz
- 配置OS预留足够数量的大页。在实验中,我们预留896G的2M大页,总共页数是458752。因此输入shell命令:
echo 458752 | sudo tee /proc/sys/vm/nr_hugepages,并等待执行完毕。 - 检查当前系统大页状态:
cat /proc/meminfo | grep Huge。HugePages_Total以及HugePages_Free应当符合预期。
- 使用我们的demo程序进行验证:
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <infiniband/verbs.h>
#define TOTAL_SIZE (896UL * 1024 * 1024 * 1024)
#define REG_SIZE (64ULL * 1024 * 1024 * 1024)
#define NUM_BUFS 14
int main(void)
{
struct ibv_device **dev_list;
struct ibv_mr *mrs[NUM_BUFS];
struct ibv_context *ib_ctx;
struct ibv_device *ib_dev;
struct ibv_pd *pd;
void *ptr;
int i;
// 1. 获取 RDMA 设备列表
dev_list = ibv_get_device_list(NULL);
if (!dev_list) {
fprintf(stderr, "Failed to get IB devices list\n");
return -1;
}
// 2. 打开 RDMA 设备
ib_dev = dev_list[0]; // 选择第一个设备
ib_ctx = ibv_open_device(ib_dev);
if (!ib_ctx) {
fprintf(stderr, "Failed to open device %s\n",
ibv_get_device_name(ib_dev));
ibv_free_device_list(dev_list);
return -1;
}
// 3. 创建保护域(Protection Domain)
pd = ibv_alloc_pd(ib_ctx);
if (!pd) {
fprintf(stderr, "Failed to allocate protection domain\n");
ibv_close_device(ib_ctx);
ibv_free_device_list(dev_list);
return -1;
}
ptr = mmap(NULL, TOTAL_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0);
if (ptr == MAP_FAILED) {
perror("mmap");
exit(1);
}
for (i = 0; i < NUM_BUFS; i++) {
mrs[i] = ibv_reg_mr(pd, ptr + i * REG_SIZE, REG_SIZE,
IBV_ACCESS_LOCAL_WRITE |
IBV_ACCESS_REMOTE_READ |
IBV_ACCESS_REMOTE_WRITE);
if (!mrs[i]) {
fprintf(stderr, "Failed to register memory: %s\n",
strerror(errno));
exit(1);
}
}
printf("All memory registered!\n");
for (i = 0; i < NUM_BUFS; i++)
ibv_dereg_mr(mrs[i]);
munmap(ptr, TOTAL_SIZE);
ibv_dealloc_pd(pd);
ibv_close_device(ib_ctx);
ibv_free_device_list(dev_list);
return 0;
}
将上述代码保存为test.c,使用gcc -o rdma_example test.c -libverbs -lrdmacm进行编译。然后执行./rdma_example,如果正常执行完毕,则可以看到All memory registered! 的输出:
其他一些需要注意的事项:
- 对于较新的内核的操作系统(如alinux3、ubuntu 22.04+),都是可以让eRDMA使用更大PageSize来注册内存。较旧的内核缺少对应的API,因此只能使用默认的PageSize(4096)进行内存注册。
- 可以在测试过程中开启eRDMA内核驱动日志,显示注册所使用的PageSize。输入命令
echo 'file erdma* +p'>/sys/kernel/debug/dynamic_debug/control来开启日志(关闭时,使用echo 'file erdma* -p'>/sys/kernel/debug/dynamic_debug/control),然后使用dmesg观察输出当中的page_size打印来确认: