从零到一:深入解析DMA、链式DMA与RDMA的核心原理与实战演进

张开发
2026/5/13 11:48:47 15 分钟阅读
从零到一:深入解析DMA、链式DMA与RDMA的核心原理与实战演进
1. DMA解放CPU的搬运工第一次听说DMA这个概念时我正被一个数据搬运的性能问题困扰。当时我们的视频处理系统需要频繁将摄像头采集的帧数据从内核空间拷贝到用户空间CPU使用率长期保持在70%以上。直到同事建议用DMA试试我才真正理解了这个技术的价值。DMADirect Memory Access就像公司里专门负责文件传递的快递小哥。想象一下如果没有专职快递员每次部门间传递文件都需要领导CPU亲自跑腿这显然是对领导时间的巨大浪费。DMA控制器就是这个专职快递员它可以在CPU领导交代好任务源地址、目标地址、数据量后独立完成数据搬运工作让CPU腾出手来处理更重要的计算任务。在实际硬件中DMA控制器通常集成在南桥芯片或外设中。以PCIe设备为例当我们需要从网卡向内存传输数据时传统方式需要CPU逐个字节拷贝而使用DMA的流程是这样的CPU配置DMA控制器的寄存器设置源地址网卡缓冲区、目标地址内存缓冲区和传输长度DMA控制器启动传输通过PCIe总线将数据从网卡搬运到内存传输完成后DMA控制器通过中断通知CPUCPU处理接收到的数据// 典型的DMA控制器寄存器配置示例 struct dma_registers { uint32_t src_addr; // 源地址寄存器 uint32_t dst_addr; // 目标地址寄存器 uint32_t length; // 传输长度寄存器 uint32_t control; // 控制寄存器 }; void setup_dma_transfer(struct dma_registers *dma, void *src, void *dst, size_t len) { dma-src_addr (uint32_t)src; dma-dst_addr (uint32_t)dst; dma-length len; dma-control DMA_ENABLE | DMA_INTERRUPT_ENABLE; }但DMA并非完美无缺。我在早期项目中遇到过两个典型问题一是DMA传输需要连续的物理内存这在内存碎片化严重的系统中很难保证二是跨设备的DMA传输如PCIe设备访问主机内存涉及复杂的地址转换。这些问题直接引出了我们接下来要讨论的链式DMA技术。2. 链式DMA解决内存碎片化的高手记得有一次调试PCIe采集卡时系统总是报无法分配连续内存错误。这就是典型的DMA限制——它要求源数据和目标数据都位于连续的物理内存中。但在现代操作系统中由于内存管理机制大块连续物理内存就像北京五环内的整块空地一样稀缺。链式DMA的出现完美解决了这个问题。它的核心思想就像快递员面对分散在不同楼层的包裹时使用一份包含所有包裹位置的清单scatter-gather list来指导工作。具体实现上Linux内核提供了scatterlist数据结构来描述离散的内存块struct scatterlist { unsigned long page_link; // 指向物理页的指针 unsigned int offset; // 在页内的偏移量 unsigned int length; // 本块的长度 dma_addr_t dma_address;// DMA映射后的地址 };实际项目中我们通常这样使用链式DMA使用get_user_pages获取用户空间缓冲区的物理页信息通过sg_alloc_table_from_pages创建scatter-gather表调用dma_map_sg完成DMA地址映射将映射后的sg表传递给设备驱动// 创建scatter-gather表示例 int prepare_sg_table(struct sg_table *table, void __user *buf, size_t len) { struct page **pages; int ret, n_pages; n_pages (len PAGE_SIZE - 1) PAGE_SHIFT; pages kmalloc_array(n_pages, sizeof(*pages), GFP_KERNEL); // 获取用户缓冲区的物理页 ret get_user_pages_fast((unsigned long)buf, n_pages, 1, pages); if (ret n_pages) { // 错误处理 } // 创建sg表 ret sg_alloc_table_from_pages(table, pages, n_pages, 0, len, GFP_KERNEL); // DMA地址映射 nents dma_map_sg(dev, table-sgl, table-nents, DMA_TO_DEVICE); return nents; }在硬件层面支持链式DMA的设备会有专门的描述符Descriptor来存储这些分散的内存块信息。以我们常用的Xilinx DMA IP核为例它的描述符结构包含源地址目标地址传输长度下一个描述符地址控制标志位这种链式结构使得DMA控制器可以自动遍历整个描述符链表完成对非连续内存的传输。在实际测试中使用链式DMA后我们的视频采集系统内存分配成功率从60%提升到了99%CPU占用率还降低了15%。3. RDMA跨机器的内存直通车第一次见识RDMA的威力是在一个分布式存储项目中。传统TCP/IP网络传输需要经过多次数据拷贝用户态-内核态-网卡而RDMARemote Direct Memory Access就像在两个服务器之间架设了直达高铁数据可以直接从本机应用内存到达远程应用内存完全绕过操作系统内核。RDMA的三大核心技术特点使其在高速网络领域独树一帜零拷贝数据不需要在内核和用户空间之间来回拷贝内核旁路应用程序可以直接操作网卡无需内核参与无CPU参与数据传输过程不消耗远程主机的CPU资源在InfiniBand架构中RDMA的工作流程可以类比为特快专递服务寄件人发起端将包裹数据交给快递站HCA网卡快递站直接根据收件地址远程内存地址送货收件人目标端在家用户空间就能收到包裹无需去邮局内核取件// 典型的RDMA操作示例使用libibverbs struct ibv_mr *register_memory(struct ibv_pd *pd, void *buf, size_t size) { // 注册内存区域 struct ibv_mr *mr ibv_reg_mr(pd, buf, size, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE); return mr; } void post_rdma_write(struct ibv_qp *qp, struct ibv_mr *local_mr, uint64_t remote_addr, uint32_t rkey) { struct ibv_sge sg { .addr (uint64_t)local_mr-addr, .length local_mr-length, .lkey local_mr-lkey }; struct ibv_send_wr wr { .wr_id 0, .sg_list sg, .num_sge 1, .opcode IBV_WR_RDMA_WRITE, .send_flags IBV_SEND_SIGNALED, .wr.rdma { .remote_addr remote_addr, .rkey rkey } }; struct ibv_send_wr *bad_wr; ibv_post_send(qp, wr, bad_wr); }在实际部署RDMA时我们通常会面临三种协议选择协议网络要求性能兼容性典型应用场景InfiniBand专用网络最高差HPC集群RoCEv2无损以太网高好数据中心iWARP标准以太网中等最好普通企业网络在我们的分布式存储系统中使用RoCEv2协议后网络延迟从原来的50μs降低到5μs以下吞吐量提升了8倍。但RDMA也有其局限性比如对网络丢包极其敏感丢包率超过0.01%就会导致性能急剧下降这要求底层网络必须支持PFC优先级流控制等无损传输技术。4. 实战演进从DMA到RDMA的性能飞跃在最近的一个AI训练平台项目中我们完整经历了从传统DMA到RDMA的技术演进路径。这个项目需要处理海量的训练数据在计算节点间的传输最初的设计使用的是普通DMATCP/IP的方案但很快就遇到了性能瓶颈。第一阶段基础DMA优化我们首先对本地DMA传输进行了深度优化使用大页Hugepage减少TLB缺失实现DMA缓冲池避免频繁内存分配采用双缓冲机制重叠计算和传输// DMA双缓冲实现示例 struct dma_double_buffer { void *buf[2]; // 双缓冲区 dma_addr_t dma_addr[2]; // DMA地址 int active_idx; // 当前活跃缓冲区索引 }; void swap_buffer(struct dma_double_buffer *db) { db-active_idx ^ 1; // 切换活跃缓冲区 } void process_dma_data(struct device *dev, struct dma_double_buffer *db) { // 处理非活跃缓冲区的数据 int process_idx db-active_idx ^ 1; process_data(db-buf[process_idx]); // 启动对活跃缓冲区的DMA传输 start_dma_transfer(dev, db-dma_addr[db-active_idx]); }第二阶段引入链式DMA当处理大型非连续数据时如稀疏矩阵我们切换到链式DMA方案设计自适应sg列表分配策略实现动态描述符缓存优化中断合并策略第三阶段RDMA改造最终我们采用RDMA实现节点间通信使用ibv_reg_mr注册内存区域通过ibv_create_qp创建队列对实现CMConnection Manager建立连接设计零拷贝数据流水线改造后的性能对比令人振奋指标DMATCP/IPRDMA方案提升幅度延迟50μs3μs16倍吞吐量5Gbps40Gbps8倍CPU使用率35%5%减少86%这个案例让我深刻体会到理解DMA技术从基础到高级的演进路径对于设计高性能系统至关重要。RDMA虽然强大但它的基础仍然是DMA和链式DMA技术。就像建筑高楼需要先打好地基一样只有深入理解这些底层原理才能在适当的时候选择正确的技术方案。

更多文章