最近计划深入学习下 RDMA 编程,并且使用 Rust 构建一套 RDMA 通信库和 RPC 框架,同时将学习和编程的过程持久化到博客中。本篇是第一篇,也将作为系列的概览。
推荐以下个人认为写得很好的 RDMA 博客:
先把 RDMA 杂谈的文章过一遍,对 RDMA 就会有一个相对明确的概念。本文就不再赘述了。
目前 Rust RDMA 编程生态并不活跃,相对易上手的项目是达坦科技开源的 async-rdma,它封装好了 RDMA 异步操作,非常易用。其他一些项目主要是针对 libibverbs 的封装,比如 rdma-sys 和 rust-ibverbs。
学习 RDMA 编程并不需要 RDMA 物理网络环境。Soft-RoCE 是 Remote Direct Memory Access (RDMA) over Converged Ethernet (RoCE) 的软件实现,可以模拟 RoCE 网卡搭建 RDMA 环境。Linux 内核中实现 Soft-RoCE 的模块名为 RXE,可以非常方便地配置一张 Soft-RoCE 网卡。
假定你使用的是 Debian 系操作系统:
# 1. 检查当前内核是否配置了 RXE 内核模块。如果是 m 或者 y 则已配置。
cat /boot/config-$(uname -r) | grep RXE
#> CONFIG_RDMA_RXE=m
# 2. 加载 RXE 内核模块
sudo modprobe ib_core
sudo modprobe rdma_ucm
sudo modprobe rdma_rxe
# 3. 查看当前网络设备。下方的以太网设备为 enp0s1,记住该名字
ip link
#> 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
#> link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
#> 2: enp0s1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
#> link/ether 0a:57:a9:60:fb:79 brd ff:ff:ff:ff:ff:ff
# 4. 创建 RXE 网络设备
sudo rdma link add rxe_0 type rxe netdev enp0s1
# 5. 查看 RDMA 设备,大功告成
rdma link
#> link rxe_0/1 state ACTIVE physical_state LINK_UP netdev enp0s1
如果你的内核没有配置 RXE 模块,你可能需要自行编译 RXE 模块并装载。目前已知的 Ubuntu Cloud Image 是不装载 RXE 的,所以使用 Multipass 的 Mac 用户会麻烦点,可以考虑切换到 UTM + Debian 12。对于 GitHub CI 环境,笔者构建了一份配置 Soft-RoCE 的 action 代码,可以直接复制到你自己的 Workflow 中使用。
配置好 RXE 设备后,还可以使用 ib_send_bw
工具简单压测下:
# 1. 安装 perftest,包含 ib_send_bw 工具
sudo apt install -y perftest
# 2. 启动 ib_send_bw server 端
ib_send_bw -d rxe_0
#> ************************************
#> * Waiting for client to connect... *
#> ************************************
# 3. 启动 ib_send_bw client 端,开始测试
ib_send_bw -d rxe_0 localhost
#> ---------------------------------------------------------------------------------------
#> Send BW Test
#> Dual-port : OFF Device : rxe_0
#> Number of qps : 1 Transport type : IB
#> Connection type : RC Using SRQ : OFF
#> PCIe relax order: ON
#> ibv_wr* API : OFF
#> TX depth : 128
#> CQ Moderation : 1
#> Mtu : 1024[B]
#> Link type : Ethernet
#> GID index : 1
#> Max inline data : 0[B]
#> rdma_cm QPs : OFF
#> Data ex. method : Ethernet
#> ---------------------------------------------------------------------------------------
#> local address: LID 0000 QPN 0x0015 PSN 0x542d8e
#> GID: 00:00:00:00:00:00:00:00:00:00:255:255:192:168:65:03
#> remote address: LID 0000 QPN 0x0016 PSN 0xa2a3de
#> GID: 00:00:00:00:00:00:00:00:00:00:255:255:192:168:65:03
#> ---------------------------------------------------------------------------------------
#> #bytes #iterations BW peak[MB/sec] BW average[MB/sec] MsgRate[Mpps]
#> 65536 1000 1408.61 1036.98 0.016592
#> ---------------------------------------------------------------------------------------
使用 ibv_devinfo -d rxe_0 -v
可以查询到设备的详细信息:
hca_id: rxe_0
transport: InfiniBand (0)
fw_ver: 0.0.0
node_guid: 0857:a9ff:fe60:fb79
sys_image_guid: 0857:a9ff:fe60:fb79
vendor_id: 0xffffff
vendor_part_id: 0
hw_ver: 0x0
phys_port_cnt: 1
max_mr_size: 0xffffffffffffffff
page_size_cap: 0xfffff000
max_qp: 1048560
max_qp_wr: 1048576
device_cap_flags: 0x01223c76
BAD_PKEY_CNTR
BAD_QKEY_CNTR
AUTO_PATH_MIG
CHANGE_PHY_PORT
UD_AV_PORT_ENFORCE
PORT_ACTIVE_EVENT
SYS_IMAGE_GUID
RC_RNR_NAK_GEN
SRQ_RESIZE
MEM_WINDOW
MEM_MGT_EXTENSIONS
MEM_WINDOW_TYPE_2B
max_sge: 32
max_sge_rd: 32
max_cq: 1048576
max_cqe: 32767
max_mr: 524287
max_pd: 1048576
max_qp_rd_atom: 128
max_ee_rd_atom: 0
max_res_rd_atom: 258048
max_qp_init_rd_atom: 128
max_ee_init_rd_atom: 0
atomic_cap: ATOMIC_HCA (1)
max_ee: 0
max_rdd: 0
max_mw: 524287
max_raw_ipv6_qp: 0
max_raw_ethy_qp: 0
max_mcast_grp: 8192
max_mcast_qp_attach: 56
max_total_mcast_qp_attach: 458752
max_ah: 32767
max_fmr: 0
max_srq: 917503
max_srq_wr: 1048576
max_srq_sge: 27
max_pkeys: 64
local_ca_ack_delay: 15
general_odp_caps:
rc_odp_caps:
NO SUPPORT
uc_odp_caps:
NO SUPPORT
ud_odp_caps:
NO SUPPORT
xrc_odp_caps:
NO SUPPORT
completion_timestamp_mask not supported
core clock not supported
device_cap_flags_ex: 0x1C001223C76
Unknown flags: 0x1C000000000
tso_caps:
max_tso: 0
rss_caps:
max_rwq_indirection_tables: 0
max_rwq_indirection_table_size: 0
rx_hash_function: 0x0
rx_hash_fields_mask: 0x0
max_wq_type_rq: 0
packet_pacing_caps:
qp_rate_limit_min: 0kbps
qp_rate_limit_max: 0kbps
tag matching not supported
num_comp_vectors: 8
port: 1
state: PORT_ACTIVE (4)
max_mtu: 4096 (5)
active_mtu: 1024 (3)
sm_lid: 0
port_lid: 0
port_lmc: 0x00
link_layer: Ethernet
max_msg_sz: 0x800000
port_cap_flags: 0x00010000
port_cap_flags2: 0x0000
max_vl_num: 1 (1)
bad_pkey_cntr: 0x0
qkey_viol_cntr: 0x0
sm_sl: 0
pkey_tbl_len: 1
gid_tbl_len: 1024
subnet_timeout: 0
init_type_reply: 0
active_width: 1X (1)
active_speed: 2.5 Gbps (1)
phys_state: LINK_UP (5)
GID[ 0]: fe80::857:a9ff:fe60:fb79, RoCE v2
GID[ 1]: ::ffff:192.168.65.3, RoCE v2
GID[ 2]: fd2b:c1ae:b71a:f09:857:a9ff:fe60:fb79, RoCE v2
万事开头难,当前已经进度斐然了。下面简单写一个 RDMA demo:
# 1. 创建 rdma-demo 项目
cargo new rdma-demo && cd rdma-demo
# 2. 增加 rdma-sys 依赖
cargo add rdma-sys
# 3. 安装 rdma-sys 所需的依赖包
sudo apt install -y libibverbs-dev librdmacm-dev
# 4. 尝试编译
cargo build
将 main.rs
修改为以下代码:
use rdma_sys::{ibv_free_device_list, ibv_get_device_list, ibv_get_device_name};
fn main() {
let mut num_devices = 0;
let list = unsafe { ibv_get_device_list(&mut num_devices) };
assert!(num_devices != 0, "IB device not found!");
let devices = unsafe { std::slice::from_raw_parts(list, num_devices as usize) };
for device in devices {
let name = unsafe { std::ffi::CStr::from_ptr(ibv_get_device_name(*device)) };
println!("device name: {}", name.to_string_lossy());
}
unsafe { ibv_free_device_list(list) };
}
执行,预期会打印:device name: rxe_0
。完整代码参考 rdma-demo。