寫在前面
隨着DeepSeek的火爆,當眾人將目光集中到模型優化時,底層網絡通信基礎設施的重要性往往容易被忽略。實際上,高性能、易維護、易定製的網絡基礎設施對於提升AI訓練、推理的效率是至關重要的一環。目前,主流的高性能網絡通信方案基本被幾家頭部公司壟斷,其提供的標準API接口也已經被精心封裝,絕大多數開發者無需關心底層實現細節。達坦科技始終致力於國產高性能網絡基礎設施解決方案的研發,深知網絡通信基礎設施在AI大模型生態中的重要地位。
為了讓更多開發者能夠對RDMA底層實現有更清晰的瞭解,我們會在近期開源一套全新的100G RDMA加速卡軟硬件方案:blue-rdma。與此同時,我們也會為大家帶來一系列文章,涵蓋了RDMA驅動程序開發、RDMA硬件原理、RDMA生態適配、RDMA性能調優等多方面內容,乾貨滿滿。
由於目前blue-rdma還處於非常早期的階段,希望大家在閲讀全系列後文章後,能夠對整個軟件及硬件方案有深入瞭解;可以在社區中一起為blue-rdma提PR,不斷改進blue-rdma;甚至自己定製開發blue-rdma來滿足自己的使用需要。在文章的末尾,可以添加達坦的微信小助手,加入blue-rdma的討論羣。
好了,言歸正傳,這次分享的文章與blue-rdma本身的實現沒有關係,而是一個關於在Linux操作系統上如何實現一個簡單RDMA用户態驅動程序的介紹。
01、背景知識
RDMA (Remote Direct Memory Access) 是一種可以將主機用户內存數據直接傳輸到另一台主機用户內存數據中的技術。和 DMA 技術相同,在數據的傳輸寫入過程中不需要 CPU 的參與,從而減輕 CPU 的負擔,這使得 CPU 能夠專注於計算任務,從而提高計算效率。同時 RDMA 也是一種內核旁路技術,允許服務器之間進行內存到內存的數據傳輸的過程中,繞過操作系統的網絡棧,從而降低網絡傳輸的延遲以及提高網絡帶寬的利用效率。對於在同一個數據中心內的 AI 大模型訓練以及大數據,雲計算的場景下,RDMA 的這些優勢能夠很好的發揮 。
RDMA 設備驅動可以分為內核態和用户態兩部分:
內核態:
- 負責硬件資源的管理:創建和管理相應的 RDMA 對象,對硬件進行資源隔離,使得多個用户態程序可以安全地共享設備資源。
- 內存管理:提供註冊用户態內存作為 DMA 區域的接口,使設備可以直接讀取相應內存,減少 CPU 開銷。提供將設備內存地址空間映射到用户態的能力,使得用户態程序能夠通過 mmap 系統調用將設備的寄存器等資源映射到用户程序的地址空間,給予用户程序繞過內核直接與硬件進行交互的能力。
用户態: - 硬件接口抽象:利用內核驅動提供的功能,封裝硬件操作,向用户提供一組易用的 API,讓用户程序可以通過 API 來控制硬件,發送和接收數據。
- 直接內存訪問:允許應用程序註冊內存區域,直接進行 RDMA 讀寫,而無需內核參與數據拷貝。
這種設計減少了內核與用户空間之間的切換,提升了性能。
在我們的工作場景下發現,我們的工作負載往往是單用户單進程獨佔 RDMA 設備進行傳輸。這讓我們意識到也許並不需要操作系統來提供對 RDMA 設備的共享訪問與權限控制,可以將驅動的主要邏輯放到用户態來實現。基於這個想法,我們開發了 blue-rdma 項目,將驅動開發的重心從內核模塊轉移到用户態驅動。通過犧牲操作系統對設備的安全訪問控制,我們得到了幾點好處:
- 在用户態開發驅動程序意味着不需要和內核版本綁定,能夠方便地移植到不同內核版本的機器上。
- 內核態驅動只需要簡單的實現,原本需要同時面對內核態驅動和用户態驅動的複雜度,現在只需要關注用户態驅動的複雜度。
- 用户態驅動程序往往比內核模塊更容易測試與調試。
- 用户態驅動程序的 bug 不會直接導致 kernel panic.
- 用户態驅動程序所使用的開發語言可以不限於傳統驅動開發所使用的 C 語言,支持多語言開發(如Rust)。
使用Rust在用户態開發驅動程序
我們的 blue-rdma 項目選擇使用 Rust 來作為用户態驅動的開發語言。
Rust 的所有權系統和借用檢查機制確保了內存安全的同時,其性能也十分優秀。Rust 對多線程和異步編程支持相當成熟,這使得在用户態開發中實現高效且安全的併發操作變得更加容易,從而滿足現代多核處理器環境下對併發性能的需求。Rust 還擁有擁有現代化的工具鏈和豐富的庫。這些工具使得開發者能夠快速構建、測試和維護驅動程序,提升開發效率。Rust 也能夠通過外部函數接口(FFI)與 C/C++ 代碼進行交互,這使得開發者可以利用現有的庫和資源。
02、技術實現路徑
大部分 RDMA 開發者都習慣使用 libibverbs 來進行開發,因此 blue-rdma 也想要適配 libibverbs 的接口,方便開發者們使用。
rdma-core 是 RDMA 用户態驅動的框架,libibverbs 就是這個框架提供的其中一個組件,類似的還有 librdmacm 組件以及 libibumad 組件。除此之外還有一些示例代碼以及測試套件。
適配 libibverbs 需要修改 rdma-core, 接下來我以 rdma-core 的 stable-v55 分支作為示例,講解如何讓 libibverbs 支持 blue-rdma 後端。
基於 rdma-core 框架添加新的 provider
以下以 rdma-core 分支 stable-v55 為例。
rdma-core 作為 RDMA 用户態驅動框架,通過 provider 機制支持不同硬件的適配,只需要在 providers 文件夾下添加新的實現,就可以通過 verbs 接口調用支持的方法了。
下面以 blue-rdma provider 為例,介紹如何在 rdma-core 下添加新的 provider.
目錄結構與構建配置
首先需要在 providers 文件夾下創建新的 provider, 基本文件夾結構如下。
>>> tree rdma-core/providers
providers/
├── bluerdma
│ ├── CMakeLists.txt # 構建規則
│ ├── bluerdma-abi.h # 內核交互 ABI 定義
│ ├── bluerdma.c # 數據結構聲明
│ └── bluerdma.h # 驅動實現與註冊邏輯
├── bnxt_re
- CMakeLists.txt 描述了怎樣編譯 provider。
- bluerdma-abi.h 中包含了與內核模塊交互時請求參數類型和回覆參數類型的聲明。
- bluerdma.h 中包含了用户態驅動所需結構體的聲明。
- bluerdma.c 中包含了用户態驅動的具體實現以及註冊當前 provider.
此外,還需要修改 rdma-core 的 CMakeLists.txt 文件讓構建系統將新編寫的 provider 包括進去。
>>> git diff rdma-core/CMakeLists.txt
diff --git a/CMakeLists.txt b/CMakeLists.txt
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -711,6 +711,7 @@
# Providers
if (HAVE_COHERENT_DMA)
+add_subdirectory(providers/bluerdma) # 引入 bluerdma
add_subdirectory(providers/bnxt_re)
add_subdirectory(providers/cxgb4) # NO SPARSE
add_subdirectory(providers/efa)
我們的驅動需要 COHERENT_DMA 支持,同時我注意到之前的 providers 都是按照字典序來排序的,所以這裏將 bluerdma 放在了第一個,實際位置可以調整。
注:如果想要合入 rdma-core 主線的話,需要修改更多的文件,比如 MAINTAINERS. 可以在那些已經合入主線中的驅動中找到更全面的修改,比如 erdma 的 PR[1].
接下來介紹新增文件夾中每個文件中的內容以及相關信息。
CMakeLists.txt 裏描述了 provider 的構建規則。
>>> cat rdma-core/providers/bluerdma/CMakeLists.txt
rdma_provider(bluerdma
bluerdma.c
)
在 rdma-core 裏已經有相當不錯的基礎設施來輔助編寫 provider 了,這裏 rdma_provider[2] 函數定義在 rdma-core/buildlib/rdma_functions.cmake 當中,最終會將 bluerdma.c 編譯成一個動態鏈接庫。
實際的編譯產物是 BUILD_DIR/lib/libbluerdma-rdmav34.so.
值得一提的是,大部分情況下使用 rdma_provider就已經能滿足需求,比如 rxe, erdma 等 provider 都只使用了 rdma_provider 描述構建目標。
但是在 mlx 驅動中的 CMakeLists.txt 裏可以發現他們的驅動還依賴了 libmlx.so 這個動態庫,對於這樣的需求, rdma-core 在 rdma_functions.cmake 中提供了 rdma_pkg_config[3] 這個函數,其內部通過 pkg-config 來添加依賴。
關鍵代碼實現
bluerdma-abi.h 用於聲明和內核模塊交互時 command 的請求參數類型以及回覆參數類型。
#ifndef __BLUERDMA_ABI_H__
#define __BLUERDMA_ABI_H__
#include <infiniband/kern-abi.h>
#include <rdma/bluerdma-abi.h>
#include <kernel-abi/bluerdma-abi.h>
DECLARE_DRV_CMD(bluerdma_cmd_alloc_context, IB_USER_VERBS_CMD_GET_CONTEXT, empty, empty);
#endif /* __BLUERDMA_ABI_H__ */
這裏使用的 DECLARE_DRV_CMD[4] 定義在 rdma-core/libibverbs/kern-abi.h 當中。
define DECLARE_DRV_CMD(_name, _enum, _kabi_req, _kabi_resp)
作用是聲明 command 對應的請求參數類型以及回覆參數類型,並做類型大小以及對齊的檢查。
生成的請求參數類型以及回覆參數類型最終會放置在 BUILD_DIR/include/kernel-abi/bluerdma-abi.h 當中。
DECLARE_DRV_CMD 的第 3 個參數是請求的 payload 類型, 第 4 個參數是回覆的 payload 類型,這兩個類型通常是定義在 rdma-core/kernel-headers/rdma/ 文件夾下對應的頭文件當中。
如果不需要帶 payload, 可以用 empty 佔位符來代替,kern-abi.h 中定義了相關的類型。
實際上,在 kern-abi.h 中還定義了默認的請求參數類型以及回覆參數類型。在 rxe.c 中對 ibv_cmd_alloc_context的調用[5]使用就是這些默認參數類型。值得一提的是,儘管可以直接在 kernel-headers/rdma/ 下直接添加自己使用的 abi header, 但是邏輯上 abi header 規定的是與內核模塊交互所用到的類型,因此更好的方式是使用 rdma-core/kernel-headers/update 這個腳本從 linux kernel 生成對應的 kernel-headers, 同時也能避免 abi 兼容問題。
bluerdma.h 中包含了用户態驅動所需結構體的聲明。
#ifndef __BLUERDMA_ABI_H__
#define __BLUERDMA_ABI_H__
#include <infiniband/driver.h>
#include <infiniband/verbs.h>
struct bluerdma_device {
struct verbs_device verbs_dev;
void* driver_data;
};
struct bluerdma_context {
struct verbs_context verbs_ctx;
};
#endif /* __BLUERDMA_ABI_H__ */
bluerdma.c 中包含了用户態驅動的具體實現以及註冊當前 provider.
static const struct verbs_match_ent bluerdma_match_table[] = {
VERBS_DRIVER_ID(RDMA_DRIVER_UNKNOWN),
VERBS_NAME_MATCH("bluerdma", NULL),
{},
};
static const struct verbs_device_ops bluerdma_dev_ops = {
.name = "bluerdma",
.match_min_abi_version = 1,
.match_max_abi_version = 1,
.match_table = bluerdma_match_table,
.alloc_device = bluerdma_alloc_device,
.uninit_device = bluerdma_uninit_device,
.alloc_context = bluerdma_alloc_context,
};
PROVIDER_DRIVER(bluerdma, bluerdma_dev_ops);
這裏的 PROVIDER_DRIVER[6], VERBS_DRIVER_ID[7] 和 VERBS_NAME_MATCH[8] 都定義在 rdma-core/libibverbs/driver.h 當中。
PROVIDER_DRIVER 的作用就是註冊相應的 verbs_device_ops.
當用户想要使用 verbs 接口時,會先調用 ibv_get_device_list[9] 獲取所有的 uverbs 設備信息。在第一次調用 ibv_get_device_list 時,會進行所有 uverbs 設備的初始化,將現有的 uverbs 設備與所有 verbs_device_ops 的 match_table 進行匹配,如果成功匹配,就調用相應的 alloc_device 進行分配。
整個初始化的過程都由 dev_list_lock 鎖進行保護,保證了 alloc_device 是單線程的並最多隻被調用一次。
儘管在 driver.h 當中推薦新的驅動只使用合適的 VERBS_DRIVER_ID 進行匹配,但是這裏依舊使用了 RDMA_DRIVER_UNKNOWN.
這樣做的原因是,如果不使用RDMA_DRIVER_UNKNOWN 而去自己添加新的設備 ID 的話,新添加的 ID 應當位於 rdma-core/kernel-headers/rdma/ib_user_ioctl_verbs.h 當中,前面提到過,kernel-headers 最好是使用 update 腳本從 linux kernel 進行生成,所以這裏選擇使用 RDMA_DRIVER_UNKNOWN.
而從 rdma-core/libibverbs/init.c 中 match_driver_id[10]的實現來看,RDMA_DRIVER_UNKNOWN 會讓 match_driver_id 永遠返回 NULL, 因此需要其他匹配方式。
VERBS_NAME_MATCH 是基於 uverbs 名字的匹配方案,儘管 driver.h 當中不鼓勵使用 VERBS_NAME_MATCH 來匹配設備,但是 VERBS_NAME_MATCH 作為 VERBS_DRIVER_ID 的替代方案是相當合適的。
match_min_abi_version 以及 match_max_abi_version 都設置為 1, 這是因為新 driver 的 abi version 都應該從 1 開始[11],而 rxe 的 abi_version[12] 在 32bit 平台下不兼容才需要新的 abi version.
alloc_device 分配的 verbs_device 結構體內都帶有一個引用計數,當這個引用計數變為 0 的時候,會調用 uninit_device 來回收分配的內存。
當用户通過 ibv_get_device_list 拿到了相應的設備之後,再通過 ibv_open_device[13] 打開設備。在 ibv_open_device 裏會調用註冊的 alloc_context 回調函數分配 context.
static struct verbs_context_ops bluerdma_ctx_ops = {
// ...
.query_port = bluerdma_query_port,
.free_context = bluerdma_free_context,
// ...
};
static struct verbs_context *
bluerdma_alloc_context(struct ibv_device *ibdev, int cmd_fd, void *private_data)
{
struct bluerdma_device *dev = to_bdev(ibdev);
struct bluerdma_context *context;
context = verbs_init_and_alloc_context(ibdev, cmd_fd, context, ibv_ctx,
RDMA_DRIVER_UNKNOWN);
if (!context)
return NULL;
if (ibv_cmd_get_context(&context->ibv_ctx, NULL, 0, NULL, 0))
goto err_out;
verbs_set_ops(&context->ibv_ctx, &bluerdma_ctx_ops);
return &context->ibv_ctx;
err_out:
verbs_uninit_context(&context->ibv_ctx);
free(context);
return NULL;
}
在 alloc_context 中需要使用 ibv_cmd_get_context 來通知內核模塊分配 uverbs 相關的上下文,不然的話無法使用內核模塊中 uverbs 的相關接口。
在 alloc_context 中也會向 context 中註冊 verbs 相關的回調函數,如果不註冊對應的相關函數,則默認使用 verbs_init_and_alloc_context[14]當中註冊的默認回調函數[15]。
註冊了相關回調函數後,我們的 provider 就可以向用户提供 verbs 的接口了。
最後還需要將 Rust 編寫的驅動核心邏輯接入 provider, 這一點可以通過前文提到的 rdma_pkg_config將 Rust 編寫的動態鏈接庫鏈接到 provider 當中,也可使用動態加載的方式,在 alloc_context 的時候通過 dlopen, dlsym 加載相應的函數。
值得一提的是,大部分常用的 ibv_* 系列接口最終都會調用到 provider 註冊的回調函數,但是也會存在一些函數並沒有對應的回調函數。比如 ibv_query_gid[16] 和 ibv_query_pkey[17] 函數。其中 ibv_query_gid 是利用 ioctl 或者 write 系統調用向 uverbs 設備發起請求,在內核模塊中根據設備的 core_cap_flags 來調用對應的方法獲取 gid, 在 rdma-core 的框架下需要進入內核態來完成這個請求。類似的, ibv_query_pkey 是通過使用 read 系統調用讀取與 verbs_device 相應的 sysfs 文件來獲取 pkey.
編寫 Rust 膠水層連接具體驅動邏輯
完成了 rdma-core 中 provider 的實現後,接下來還需要做的是將 verbs_context_ops 中註冊的回調函數連接到我們用 Rust 實現的具體驅動邏輯,為此我們需要編寫一個 ffi 膠水層。
對於 Rust 具體驅動邏輯,這個膠水層需要將 provider 中定義的 C 類型導入到 Rust 當中,從而讓膠水層可以導出簽名正確的回調函數,並訪問 C 類型中的成員,將 Rust 驅動的實例存儲在 C provider 當中。
對於 C provider,這個膠水層需要編譯出一個動態鏈接庫,這個動態鏈接庫包含了回調函數的具體實現,讓 C provider 可以通過動態鏈接或者動態加載的方式,使用 verbs_set_ops 宏註冊所需的回調函數。
FFI 綁定生成
將 provider 中定義的 C 類型導入到 Rust 當中需要使用到 bindgen 來生成 C 到 Rust 的 ffi 類型,這部分可以參考 rust-ibverbs-sys[18] 庫來編寫 bulid.rs。
// build.rs
use std::env;
use std::path::{Path, PathBuf};
use std::process::Command;
fn main() {
let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("failed to get current directory");
let rdma_core_dir = format!("{manifest_dir}/../rdma-core-v55");
println!("cargo:include={rdma_core_dir}/build/include");
println!("cargo:rustc-link-search=native={rdma_core_dir}/build/lib");
println!("cargo:rustc-link-lib=ibverbs");
// build rdma-core
// note that we only build it to generate the bindings!
let built_in = cmake::Config::new(&rdma_core_dir)
.define("NO_MAN_PAGES", "1")
.no_build_target(true)
.build();
let built_in = built_in.to_str().expect("build directory path is not valid UTF-8");
let provider = "bluerdma";
let bindings = bindgen::Builder::default()
.header(format!("{rdma_core_dir}/providers/{provider}/{provider}.h"))
.clang_arg(format!("-I{built_in}/build/include/"))
.allowlist_type("ibv_.*")
.allowlist_type("verbs_.*")
.allowlist_type(format!("{provider}_.*"))
.generate()
.expect("Unable to generate bindings");
// write the bindings to the $OUT_DIR/bindings.rs file.
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Could not write bindings");
}
這樣就可以通過 bindgen 生成出來的 Rust 文件來訪問 C 文件中定義的結構體了。
// src/ffi.rs
use std::mem::size_of;
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
const _: () = assert!(size_of::<bluerdma_device>() == size_of::<verbs_device>() + size_of::<*const std::ffi::c_void>());
我們還需要為 C provider 提供一個動態鏈接庫,只需要在 Cargo.toml 聲明 lib 類型為 cdylib 即可。
# Cargo.toml
[package]
name = "bluerdma_rust"
version = "0.0.0"
edition = "2024"
[lib]
crate-type = ["cdylib"]
跨語言接口設計
接下來就可以實現並導出相關的函數。#[unsafe(export_name="xxx")] 用來聲明導出的符號,其中 unsafe 部分是 Rust Edition 2024 之後要求的,之前的版本可以不加。導出的函數會被 C 調用因此需要加上 extern "C" 來滿足 ffi 規範。而 C provider 要求的回調函數簽名基本都需要和指針打交道,膠水層也沒有辦法對外部 C 調用做出約束,所以這些函數都帶有 unsafe 標識。
// src/exports.rs
#[unsafe(export_name = "bluerdma_new")]
pub unsafe extern "C" fn bluerdma_new(sysfs_name: *const c_char) -> *mut c_void {
// Safety: caller must ensure `sysfs_name` is a valid C str.
let sysfs_name = unsafe { CStr::from_ptr(sysfs_name) }.to_str().unwrap();
Box::into_raw(Box::new(BlueRdmaDriver::new(sysfs_name)).cast()
}
#[unsafe(export_name = "bluerdma_free")]
pub unsafe extern "C" fn bluerdma_free(driver_data: *const c_void) {
let ptr = driver_data.cast::<BlueRdmaDriver>().cast_mut();
// Safety: caller must ensure `driver_data` is a valid Box.
let _ = unsafe { Box::from_raw(ptr) };
}
#[unsafe(export_name = "bluerdma_query_port")]
pub unsafe extern "C" fn bluerdma_query_port(
context: *mut ffi::ibv_context,
port_num: u8,
port_attr: *mut ffi::ibv_port_attr,
) -> ::std::os::raw::c_int {
// Safety: caller must ensure `context` is valid.
let Some(ibdev) = unsafe { context.as_ref() }.map(|ctx| ctx.device) else {
return libc::EINVAL;
};
let driver = unsafe { to_driver(ibdev).as_ref() };
let Some(driver) = driver else {
return libc::EINVAL;
};
let Some(port_data) = driver.query_port(port_num) else {
return libc::EINVAL;
};
// set `port_attr` from `port_data`
return 0;
}
bluerdma_new 以及 bluerdma_free 都不是回調函數,而是初始化和釋放 Rust driver 的函數。在 C provider 初始化的時候會調用 bluerdma_new, 並將得到的 Rust driver 的指針存儲到 bluerdma_device 中,在釋放的時候也需要調用 bluerdma_free 函數。之後對 verbs api 的調用中都能從 ibv_context 中取得 Rust driver 的指針。
bluerdma_query_port 則是回調函數,libibverbs 提供的 ibv_query_port 函數最終就會調用這個回調函數。在 bluerdma_query_port 的實現中,從 context 取得 Rust driver 的指針,經過檢查後轉換為共享引用,再調用 driver 上的方法來實現具體功能,最後按照 C provider 回調函數的規範設置參數以及返回值。
其中 blue_query_port 中 to_driver 函數的作用是通過 ibv_device 的指針得到 driver_data 的指針,它的實現使用了類似 kernel 中的 container_of 宏的功能,在 Rust-For-Linux 中也有對應的 container_of 宏[19]。
// src/export.rs
const unsafe fn to_driver(ibdev: *mut ffi::ibv_device) -> *const BlueRdmaDriver {
let ibdev = ibdev.cast_const().cast::<u8>();
let offset: usize = ::core::mem::offset_of!(bluerdma_device, verbs_dev.device);
let bdev = unsafe { ibdev.sub(offset) }.cast::<bluerdma_device>();
if let Some(bdev) = unsafe { bdev.as_ref() } {
bdev.driver_data.cast()
} else {
ptr::null()
}
}
// 類似的 C 函數
inline static void* to_driver(struct ibv_device *ibdev) {
struct bluerdma_device* bdev = container_of(ibdev, struct bluerdma_device, verbs_dev.device);
return bdev ? bdev->driver_data : NULL;
}
編寫完膠水層後,用户通過 rdma-core 提供的 libibverbs 動態鏈接庫使用 verbs 系列接口時,就能通過註冊的回調函數調用膠水層動態鏈接庫提供的函數,從而連接具體的 Rust 驅動實現。
03、總結
本文主要講解了在用户態使用 Rust 開發 RDMA 驅動程序中,如何為 libibverbs 的 verbs API 添加一個新的後端實現。具體實現上,本文詳細描述瞭如何在 rdma-core 框架上新增一個新的 provider, 使得 verbs API 可以調用到在 provider 中註冊的回調函數。同時提供一個實例講解如何通過編寫 Rust ffi 膠水層,將 C provider 與 Rust 核心驅動邏輯進行連接。最終實現了用户態 Rust 開發的 RDMA 驅動程序與 libibverbs 生態的兼容,為 RDMA 相關開發人員提供了熟悉的 API 接口。
04、未來展望
本文講述的 blue-rdma 實際上還不是很完善,還存在不少可以改進的點。
ibv_context 單例化:根據我們的場景,我們核心 Rust driver 只會實例化一次,同時驅動的狀態也都存儲在 Rust driver 當中,因此在 alloc_context 的時候可以複用同一個 context, 降低一些開銷。這點可以參考 HabanaLabs provider 的實現[20],使用引用計數和全局靜態變量,在 provider 中第一次調用 alloc_context 用 bluerdma_new 創建新的實例並存儲在一個全局的 context 中,之後調用 alloc_context 時僅增加引用計數並返回這個全局 context,而不實際分配資源給新的 context. 回收時在 free_context 中減少引用計數,如果計數為 0, 再通過 bluerdma_free 釋放 Rust driver 以及釋放全局的 context 資源。
將 Rust driver 的生命週期與設備綁定:目前的實現機制中,Rust driver 由 C provider 創建並存儲在 provider 定義的結構體中。這個結構體由 rdma-core 管理生命週期,因此當 rdma-core 相關的動態鏈接庫,如 libibverbs, 全部從內存中卸載掉時,Rust driver 之前存儲的狀態就丟失了。我們更希望 Rust driver 的生命週期與內核態的設備保持一致,這樣即便進程退出,卸載了 rdma-core 相關的動態鏈接庫,Rust driver 仍舊存儲着進程之前設置的狀態。這樣下次進程重新使用 verbs 接口時可以不必重新初始化 Rust driver 的狀態。
本文的重點放在了 RDMA 驅動程序的用户態,不過內核態的驅動程序也承擔了很重要的職責,之後考慮出一篇文章單獨講講內核態驅動的實現。
05、關於作者
作者是達坦科技軟硬件聯合開發小組的工程師,負責 RDMA 相關的開發和維護。歡迎在 Github (Foreverhighness) 上與我交流。
06、參考資料
達坦科技始終致力於打造高性能 Al+ Cloud 基礎設施平台,積極推動 AI 應用的落地。達坦科技通過軟硬件深度融合的方式,提供AI推理引擎和高性能網絡。為 AI 應用提供彈性、便利、經濟的基礎設施服務,以此滿足不同行業客户對 AI+Cloud 的需求。
公眾號:達坦科技DatenLord
DatenLord官網:
https://datenlord.github.io/zh-cn/
知乎賬號:
https://www.zhihu.com/org/da-tan-ke-ji
B站:
https://space.bilibili.com/2017027518
郵箱:info@datenlord.com
如果您有興趣加入達坦科技blue-rdma相關羣 ,請添加小助手微信:DatenLord_Tech