Jianxin Xiong, Intel Corporation
RDMA 是 “DMA + 网络”
DMA 需要对内存进行正确的设置
get_user_pages()sg_alloc_table() / sg_set_page() / sg_next() / ...dma_map_sg()
GPU 内存是本地(local)的
需要 NIC 驱动和 GPU 驱动之间的协作
Mellanox 的 Peer-Direct 方案
我们能否有一个非专有的上游解决方案?
Dma-buf 是 Linux 内核中的一种标准机制,用于在不同设备驱动程序之间共享缓冲区。
其工作流程如下:
* 导出方 (Exporter): 拥有内存分配的驱动程序。它创建一个 dma-buf 对象并导出一个文件描述符(fd)。
* 导入方 (Importer): 需要访问该内存的驱动程序。它通过文件描述符获取 dma-buf 对象的引用,将其附加(attach)到自己的设备上,并将其映射(map)为设备可访问的DMA地址。
dma_buf_export() 函数,并传入 dma_buf_export_info 结构体。该结构体包含 dma_buf_ops,定义了对该缓冲区的操作。struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info);
struct dma_buf_export_info {
const char *exp_name;
struct module *owner;
const struct dma_buf_ops *ops; // 操作函数集
size_t size;
int flags;
struct dma_resv *resv;
void *priv;
};
dma_buf_ops 结构体中定义了多种回调函数,其中加粗的为强制实现项:
struct dma_buf_ops {
/* 加粗为强制实现项 */
bool cache_sgt_mapping;
bool dynamic_mapping;
int (*attach)(struct dma_buf *, struct dma_buf_attachment *);
void (*detach)(struct dma_buf *, struct dma_buf_attachment *);
struct sg_table * (*map_dma_buf)(struct dma_buf_attachment *, enum dma_data_direction);
void (*unmap_dma_buf)(struct dma_buf_attachment *, struct sg_table *, enum dma_data_direction);
void (*release)(struct dma_buf *);
int (*begin_cpu_access)(struct dma_buf *, enum dma_data_direction);
int (*end_cpu_access)(struct dma_buf *, enum dma_data_direction);
int (*mmap)(struct dma_buf *, struct vm_area_struct *vma);
void *(*map)(struct dma_buf *, unsigned long);
void (*unmap)(struct dma_buf *, void *);
void *(*vmap)(struct dma_buf *);
void (*vunmap)(struct dma_buf *, void *vaddr);
};
dma_buf_fd() 函数将 dma-buf 对象与一个文件描述符关联起来。int dma_buf_fd(struct dma_buf *dmabuf, int flags);
fd 获取 dma-buf 对象。struct dma_buf *dma_buf_get(int fd);
void dma_buf_put(struct dma_buf *dma_buf);
dev 设备访问。struct dma_buf_attachment *dma_buf_attach(struct dma_buf *dma_buf, struct device *dev);
// 动态附加
struct dma_buf_attachment *dma_buf_dynamic_attach(struct dma_buf *dma_buf, struct device *dev, bool allow_dynamic, void *priv);
void dma_buf_detach(struct dma_buf *dmabuf, struct dma_buf_attachment *attach);
struct sg_table *dma_buf_map_attachment(struct dma_buf_attachment *attach, enum dma_data_direction direction);
void dma_buf_unmap_attachment(struct dma_buf_attachment *attach, struct sg_table *sg_table, enum dma_data_direction direction);
int dma_buf_begin_cpu_access();
int dma_buf_end_cpu_access();
void *dma_buf_kmap();
void dma_buf_kunmap();
int dma_buf_mmap();
void *dma_buf_vmap();
void dma_buf_vunmap();
下图展示了使用 dma-buf 进行 GPU 内存 RDMA 注册的完整流程:
1. 应用程序 (Application) 调用 GPU 库 (GPU library) 来分配 GPU 内存。
2. GPU 库返回内存地址、大小以及一个代表该内存的 文件描述符 (fd)。
3. 应用程序调用 RDMA 库 (RDMA library) (例如 OFI 或 Verbs) 的内存注册函数 (如 ibv_reg_mr_fd),并将文件描述符 fd 传入。
4. 调用链最终到达内核态的 RDMA 驱动 (RDMA driver)。
5. 在内核中,GPU 驱动 作为 导出方 (exporter),将 GPU 内存导出为 dma-buf 对象。
6. RDMA 驱动 作为 导入方 (importer),通过文件描述符导入该 dma-buf,并将其映射为可供 NIC 进行 点对点 DMA (peer-to-peer DMA) 的物理地址。
7. 这样,NIC 就可以通过 PCIe 直接访问 GPU 内存,实现 RDMA 操作。
许多现有的 GPU 驱动已支持 Dma-buf
ioctl() 访问 /dev/dri/card<n>。| command | function |
|---|---|
DRM_IOCTL_MODE_CREATE_DUMB |
分配一个 "dumb" 缓冲区 |
DRM_IOCTL_I915_GEM_CREATE |
分配一个 "GEM" 缓冲区 |
DRM_IOCTL_PRIME_HANDLE_TO_FD |
获取 dma-buf 文件描述符 |
当前的 GPU 驱动实现可能未针对 P2P 访问进行优化。
用户空间库需要提供接口来检索 dma-buf fd
ioctl。ib_umem_get() 导入 dma-buf 作为用户内存。ib_umem_dmabuf_get() 来处理基于文件描述符的 dma-buf 内存。// 原有函数
struct ib_umem *
ib_umem_get(
struct ib_ucontext *ucontext,
unsigned long addr,
size_t size,
int access);
// 新增函数
struct ib_umem *
ib_umem_dmabuf_get(
struct ib_ucontext *ucontext,
unsigned long addr,
size_t size,
int dmabuf_fd,
int access);
IB_USER_VERBS_CMD_REG_MR_FDIB_USER_VERBS_CMD_REREG_MR_FDfd_type: 文件描述符的类型,允许未来扩展。fd: 文件描述符。ib_device 结构中添加两个函数指针,用于与供应商驱动程序对接struct ib_device {
......
struct ib_mr * (*reg_user_mr_fd)(....., int fd_type, int fd, int acc, ..... );
int (*rereg_user_mr_fd)(....., int fd_type, int fd, int acc, ..... );
};
reg),而不支持重新注册 (rereg)。ib_dev->dev.uverbs_cmd_mask。ib_umem_get() 替换为 ib_umem_dmabuf_get()。ibv_reg_mr_fd 和 ibv_rereg_mr_fd。ibv_mr_fd_type 和 fd。struct ibv_mr *ibv_reg_mr_fd (
struct ibv_pd *pd,
void *addr,
size_t length,
enum ibv_mr_fd_type,
int fd,
int access);
int ibv_rereg_mr_fd (
struct ibv_mr *mr,
int flags,
struct ibv_pd *pd,
void *addr,
size_t length,
enum ibv_mr_fd_type,
int fd,
int access);
int ibv_cmd_reg_mr_fd(....., int fd_type, int fd, int access, .....);
int ibv_cmd_rereg_mr_fd(....., int fd_type, int fd, int access, .....);
verbs_context_ops 结构中添加两个函数指针,用于与供应商库对接struct verbs_context_ops {
......
struct ibv_mr *(*reg_mr_fd)(....., enum ibv_mr_fd_type, int fd, int access );
int (*rereg_mr_fd)(....., enum ibv_mr_fd_type, int fd, int access );
};
ibv_cmd_ 版本的函数即可。在 fi_mr_attr 结构中增加了新字段,以便在进行内存注册时可以传递文件描述符(fd)。
struct fi_mr_attr {
......
enum fi_hmem_iface iface; /* 用于内存分配的API */
union {
uint64_t reserved;
......
int fd;
} device;
};
fi_mr_regattr() 函数。FI_HMEM 能力位(capability bit)来表示。一个软件原型已经完成,其特性如下:
- 基于上游 Linux 内核 5.6 和最新的用户空间 rdma-core 库。
- GPU:使用 i915 驱动程序的 Intel GPU。
- RDMA NIC:Mellanox ConnectX-4 EDR,使用上游驱动程序。