本系列文章将带领读者逐步了解Linux操作系统下的RDMA子系统。本篇文章作为系列的第一篇,将从全局视角介绍如下内容:
- RDMA简介
- 用户态和内核态驱动的关系
- 驱动代码的目录结构
1、RDMA概念简介
为了便于刚接触RDMA的读者阅读本系列文章,首先对RDMA的概念做一个简单的介绍。RDMA是Remote Direct Memory Access的缩写。
在介绍RDMA之前,先简单介绍一下更为人所熟知的DMA技术,DMA技术的目的是为了解放CPU的算力,让硬件有能力绕开CPU自己去完成主存(Main Memory)和外设存储器之间的简单而枯燥的数据搬运工作。比较常见的DMA操作是连接在PCIe总线上的设备(如网卡、显卡等)自己主动发起对计算机主存的读写访问操作。可以看到,DMA操作相当于是把主机内存中的一部分地址范围直接暴露给硬件直接访问,而且这种DMA操作局限在一张主板所连接的设备上。
随着高性能网络的发展,网络接口上的数据带宽已经接近主板上PCIe总线的带宽,那么是否可以把网线看做类似PCIe总线这样的通信媒介,实现把网络中一台主机的内存通过网络暴露给其他主机直接访问呢?这就是RDMA所要解决的问题:通过专用的RDMA网卡以及专用的通信协议,实现根据网络上接收到的请求直接对主机内存进行读写操作。读写的内存地址由远端主机决定,本地主机上的RDMA网卡根据网络请求中的地址信息和载荷数据,直接对本地主机的内存进行访问。
2、用户态和内核态驱动的关系
RDMA系统的驱动程序分为内核态和用户态两个大的组成部分。为什么要将一个设备的驱动程序分成两部分呢?这主要是为了从安全和性能两个角度取得一个平衡。
首先,从安全角度来说,RDMA网卡作为一个硬件设备,允许其根据网络请求直接操纵本地主机的内存,这其实是一个相当危险的操作。不同于软件通过CPU访问内存时有MMU对内存访问进行权限控制,对于硬件设备而言,其对本机内存的访问是直接在物理地址上进行最底层的读写访问(虽然一些现代处理器配备有IOMMU来为外设提供类似MMU的保护能力,但在高性能网络中为了提升性能,可能并不会开启)。
因此,RDMA网卡在正式开始工作之前,必须对其进行一些设置,告知网卡哪些地址范围是可以访问的,哪些地址范围是不能访问的。此外,由于RDMA协议中传递的内存地址是虚拟地址,而硬件访问内存时需要的是物理地址,因此在使用网卡之前也需要对网卡配置一个类似于CPU上MMU所使用的页表一样的东西用于帮助RDMA网卡自己实现VA到PA的转换,如果这个转换关系表填错了,则会导致本机内存被写乱的风险。
从上面的介绍可以看出,对于网卡一些重要配置操作,必须是安全可靠的,也就是不能让用户为所欲为的配置,要实现这一点,只能通过内核态的驱动程序来实现,即用户对网卡的重要配置操作,都需要通过执行系统调用来陷入内核,由内核确认操作合法后再交由硬件执行。
但是,RDMA网络追求的是高性能和低延迟,这就意味着不可能所有的操作都需要陷入到操作系统内核去做,对于发送数据和接收数据这样最简单的操作,最好不要陷入内核态去给硬件发送指令,而是直接从用户态发起对硬件的操作。
除此之外,考虑到上层开发者使用的便利性,最好不要让上层开发者面对内核态驱动和用户态驱动两套API接口。大家经常开玩笑说没有什么问题是增加一个中间层解决不了的,所以用户态驱动程序还有一个作用,就是将内核态启动的API接口进行一次封装,这样对于上层开发者而言,只需要对着用户态驱动框架所提供的API进行开发就好了。
3、驱动代码的目录结构
3.1 内核态驱动
RDMA系统的内核驱动部分代码位于内核源码中的drivers/infiniband目录下,只需要正常克隆Linux内核的源码仓库即可获得到内核态驱动程序的源码。接下来以v6.8版本的内核源码为例,内核态源码的目录结构大致如下(为了展示方便,进行了一些删减)
├── core
├── hw
│ ├── erdma
│ ├── hns
│ ├── mlx5
│ └── ......
├── sw
│ ├── rdmavt
│ ├── rxe
│ └── siw
└── ulp
可以看到RDMA子系统的代码结构并不复杂,目录结构非常清晰。在这里我们重点需要关注的是core、hw、sw这三个目录,其中:
- core目录是RDMA子模块的核心逻辑,包含了对于各种特权级Verbs行为的处理、创建与用户态交互的设备文件接口等工作,可以说这个core目录提供了一个RDMA设备驱动开发的抽象框架,该目录下的代码与具体的硬件设备无关。
- hw目录是与各个硬件厂商相关的硬件适配层代码,用于将core目录下RDMA子系统的抽象控制信息翻译给各家硬件特有的控制协议。从狭义的角度来说,这个目录下的代码才是真正的“硬件驱动程序”。在上面列出了3个硬件厂商对应的目录,其中:
- mlx5目录下是目前最知名的Mellanox系列网卡,其特点是驱动功能最完善,但是也最复杂,并不建议新手上来直接学习该目录下的代码。
- hns目录下是华为海思旗下网卡的驱动程序,其代码量适中,不过包含两个版本的硬件设备代码,混在一起对于阅读有一定的影响,同样不太建议新手直接阅读。
- erdma目录下是阿里巴巴自研弹性RDMA的驱动程序,由于该代码进入内核较晚,且该产品属于比较新的产品,因此没有太多的历史包袱,代码量也很小,只支持RDMA中部分常用的功能,因此是一个非常适合入门者学习的代码。
- sw目录是软件模拟的RDMA协议栈,在没有真实的RDMA设备时,可以使用该目录下的驱动程序将普通网卡模拟成一块RDMA网卡进行使用,当然其性能无法与真实硬件相媲美,通常仅用于在没有真实RDMA网卡情况下的调试、测试等用途。另外,由于软件模拟RDMA网卡的过程中涉及到对RDMA数据包的生成与解析操作,因此,如果对RDMA协议本身感兴趣的同学,可以阅读rxe设备的代码来了解有关RDMA协议本身实现的细节。
3.2 用户态驱动
用户态驱动程序并没有放在Linux Kernel的代码库中,而是在如下的独立Github仓库中进行托管:
https://github.com/linux-rdma/rdma-core
需要注意,这个项目的名字虽然叫做rdma-core,但它和上面内核源码树中的core文件夹没有任何关系,rdma-core项目中的所有代码编译后都是在用户态执行的,这一点大家务必区分清楚。
克隆该项目后,可以看到如下的目录结构同样为了便于展示,删减了大部分初学者不需要关注的目录:
├── build
├── kernel-headers
├── libibverbs
├── providers
│ ├── erdma
│ ├── hns
│ ├── mlx5
│ ├── rxe
│ └── ......
├── pyverbs
└── ......
- build目录里包含了用于构建用户态驱动的构建系统
- kernel-headers中包含了与内核代码相关的必要头文件。由于用户态驱动程序依赖内核态驱动程序来提供对设备特权级操作的支持,因此必不可少要和内核打交道。由于Linux Kernel的代码迭代速度很快,用户态与内核态的接口在不同版本之间也会有细微变化。该目录下的文件会在Linux Kernel发布新版本时进行对应的更新,从而保证用户态驱动可以与对应的内核版本进行正常的交互。
- libibverbs目录里提供了所有对上层用户暴露的Verbs API接口,与内核态驱动类似,该目录下的代码对RDMA设备进行了一个抽象封装,提供了RDMA设备通用功能的抽象,与具体的设备没有关系。
- providers目录下则包含了与具体设备相对应的代码,也就是狭义上的“用户态驱动程序”,这里的代码通常会通过MMIO的方式在用户态直接操作网卡硬件上的CSR寄存器,或者读写Main Memory中与硬件网卡设备共享的环形缓冲区。
- pyverbs目录下则提供了一套Python版本的Verbs API接口,rdma-core这个软件项目的主体部分编译后会得到一个动态链接库,这样的动态链接库更适合于C、Rust这样的系统级编程语言来使用。不过这些语言虽然运行的快,但开发难度还是相对较高的,为了方便大家调试开发,或者做一些简单的实验,pyverbs为大家提供了一套Python API接口,从而使得开发一些简单的应用变得很方便,也非常适合初学者。
往期推荐
达坦科技始终致力于打造高性能Al+ Cloud 基础设施平台,积极推动 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
如果您有兴趣加入达坦科技Rust前沿技术交流群或硬件相关群,请添加小助手微信:DatenLord_Tech