一次RDMA用户态驱动调试的复盘

1、背景

在达坦科技实习期间,笔者开发了一个RDMA用户态驱动,用于与达坦科技自研的BlueRDMA进行交互。整个驱动分为内核态和用户态两个部分,内核部分做的仅仅是将必要的内存空间暴露给用户态,例如将bar空间上的csr(control status register)寄存器映射到了用户态,主要的操作由用户态驱动执行。用户态驱动申请了4个巨页,并分别指定这几个巨页为与硬件交互的Ringbuf。当用户态驱动需要向硬件发送信息(即描述符)时,只需要在内存上写入若干个描述符,然后修改csr寄存器中队列头指针的值。当硬件发现Ringbuf队列非空时,就会向对应的内存区域发起DMA请求,获取描述符,执行相应的操作。

BlueRDMA基于BlueSim提供了一个可以在软件上进行模拟的仿真器。通过BlueRDMA中的脚本,软件能够与硬件仿真器交互测试功能。通过这个仿真器,软件能够与硬件代码协同测试。但是由于是全软件模拟硬件,仿真器的速度很慢,不适合跑较大规模的测试。通常是在仿真器上验证行为后,后续在硬件上运行更大规模的测试。小规模的测试往往不够充分,也留下了一些潜在的软件bug。笔者在仿真器上跑了一些测试,一切都工作正常,直到准备在硬件上进行性能测试。

具体的现象是,当关闭日志时,硬件有很大概率会出现丢包的问题。例如,当发送方写入了若干个描述符,但接收方并没有收到全部的包,只收到了一系列连续的报文,但未能收到期望的全部包。

由于与日志打印相关,笔者的第一反应是内存问题。在之前调试一些C/C++项目时,曾遇到过开启打印能消除“segment fault”的神奇bug。虽然这是一个Rust项目,内存安全性相对较好一些,但在涉及到Ringbuf相关内存申请时,不可避免会引入一些unsafe。经过反复检查各个unsafe和写入操作,感觉问题不在这上面。

会不会是发送速度的问题呢?开启打印日志不可避免会带来锁等开销,由于目前的程序在每个描述符下发到硬件上时都加上了一个日志,用于判断描述符是否正常写入,因此相当于每次描述符写入都要上锁。这操作相对较慢。关闭日志后,发送速度变快了。但是为什么变快了会导致发送数据不完整呢?

2、cache一致性的问题

与此同时,还遇到了另外一个bug,这主要与cache一致性相关。硬件向软件上报描述符时,同样是通过Ringbuf实现的。硬件会首先将描述符拷贝到Ringbuf上,然后修改Ringbuf的头指针,表示有新的描述符产生。软件会轮询Ringbuf的头指针,一旦头指针发生变化,就会从对应的内存区域读走描述符,同时修改尾指针,表示软件已读取。在实际测试中,这个机制一直工作得很好,但有时软件会读取到一些“全0”描述符。这种描述符的特征在于,即使你给DMA buffer提前填充了一些别的数据(例如全部填1),读取描述符时的数据仍然是0。

笔者一开始怀疑是硬件上报了“全0”描述符。在经过沟通和一些试验后,发现如果不立即读取这个新的描述符,而是稍等一会再去读取,就能读取到其内容。这可能与cache一致性相关。上文提到,用户态驱动使用了巨页作为与硬件交互的Ringbuf,巨页本身能提供超过4K的连续物理内存,但巨页本身并不具有DMA cache coherence。也就是说,可能存在内存上发生了DMA拷贝,但用户态程序读取的仍然是之前的cache内容。

会不会上面“丢包”的问题也是cache一致性导致的呢?毕竟如果写入描述符的速度变快了,很可能cache没有同步到物理内存上,然后就发生了连续丢包。在mentor的建议下,笔者决定先从cache一致性入手,或许解决cache一致性的问题,上面的丢包问题也能一并解决。

在阅读了erdma等项目mr注册部分的实现后,发现只需要使用dma_alloc_coherent接口就能拿到一块物理内存连续且设备读/写都能马上与CPU同步的内存。同样地,用户态驱动只需要通过mmap就能将这块Ringbuf挂载在自己的虚拟内存上。完成这一部分工作后,读取到“0描述符”的bug已经消失了,但发送方丢包的问题仍然存在。

经过再次检查写入Ringbuf的代码,并阅读内核中其他驱动读写bar空间的代码后,笔者发现用户态可能还缺少了volatile读/写操作。尝试使用Rust标准库提供的volatile_read/volatile_write,并在写入Buffer时加上fence,然而该问题依然未得到解决。

3、从单独的模块入手

为了进一步确定丢包的原因,笔者决定做一些实验进行探究。

上述的硬件测试是在两台机器上进行的。两台机器各有一张F

RDMA(Remote Direct Memory Access)技术允许在无需CPU干预的情况下直接从一台计算机的内存读取或写入另一台计算机的内存。为了简化连接管理,RDMA提供了用户态的连接管理接口,通常称为RDMA Core CM(Connection Manager)。以下是关于如何在用户态使用RDMA Core CM功能的一些方法和资源: ### 使用 RDMA Core CM 的基本步骤 1. **初始化上下文**:首先需要创建一个`rdma_cm_id`对象,该对象用于表示一个RDMA连接。这个过程类似于创建一个网络套接字。 2. **绑定地址**:使用`rdma_bind_addr`函数将`rdma_cm_id`绑定到本地网络接口的一个地址上。 3. **监听连接请求**:服务器端调用`rdma_listen`开始监听来自客户端的连接请求。 4. **发起连接请求**:客户端通过调用`rdma_resolve_addr`来解析目标地址,然后通过`rdma_connect`发送连接请求。 5. **处理连接事件**:服务器端通过`rdma_get_cm_event`获取连接事件,并根据事件类型进行相应的处理,如接受连接请求。 6. **数据传输**:一旦连接建立成功,就可以通过`ibv_post_send`和`ibv_post_recv`等函数进行数据传输了。 7. **断开连接**:当通信结束时,可以通过调用`rdma_disconnect`来断开连接。 8. **清理资源**:最后,需要释放所有分配的资源,包括`rdma_cm_id`和其他相关结构体。 ### 示例代码 下面是一个简单的示例代码片段,展示了如何使用RDMA Core CM API创建一个服务器端程序: ```c #include <rdma/rdma_cma.h> #include <stdio.h> #include <stdlib.h> int main() { struct rdma_cm_id *id; struct rdma_event_channel *channel; // 创建事件通道 channel = rdma_create_event_channel(); if (!channel) { perror("rdma_create_event_channel"); exit(EXIT_FAILURE); } // 创建CM ID if (rdma_create_id(channel, &id, NULL, RDMA_PS_TCP)) { perror("rdma_create_id"); exit(EXIT_FAILURE); } // 绑定地址 struct sockaddr_in sin = {0}; sin.sin_family = AF_INET; sin.sin_port = htons(7471); // 使用任意端口 if (rdma_bind_addr(id, (struct sockaddr *)&sin)) { perror("rdma_bind_addr"); exit(EXIT_FAILURE); } // 开始监听 if (rdma_listen(id, 10)) { // 第二个参数是队列长度 perror("rdma_listen"); exit(EXIT_FAILURE); } printf("Server is listening...\n"); // 处理事件 struct rdma_cm_event *event; while (1) { if (rdma_get_cm_event(channel, &event)) { perror("rdma_get_cm_event"); break; } switch (event->event) { case RDMA_CM_EVENT_CONNECT_REQUEST: // 接受连接请求 if (rdma_accept(event->id, NULL)) { perror("rdma_accept"); } break; default: break; } rdma_ack_cm_event(event); } // 清理资源 rdma_destroy_id(id); rdma_destroy_event_channel(channel); return 0; } ``` ### 文档与社区资源 - **官方文档**:对于更详细的API说明和编程指南,可以查阅[libibverbs](https://www.mellanox.com/related-docs/prod_software/RDMA_for_Linux__libibverbs_and_libmlx4_User_Guide.pdf)文档[^2]。 - **GitHub仓库**:可以在GitHub上查找名为`linux-rdma`的仓库,这里包含了许多RDMA相关的开发资料和示例代码[^2]。 - **学术研究**:有关于RDMA性能分析和应用的研究论文,例如《Performance of RDMA-capable storage protocols on wide-area network》[^3],这些资料可以帮助深入理解RDMA的工作原理及其优化策略。 ### 社区支持 加入RDMA开发者社区可以获得最新的技术支持和交流机会。这些社区不仅提供了一个平台供开发者分享经验,还经常发布教程文章和技术博客帮助新手入门。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

达坦科技DatenLord

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值