RDMA技术详解

原文出自公众号 深度Linux

背景

当前跨主机内存读取存在多次内存拷贝和上下文切换,严重影响性能

发送侧的内存拷贝信息如下:

  • 数据从用户态缓存到内核中TCP协议栈缓存
  • TCP协议栈缓存拷贝到驱动层
  • 驱动缓存拷贝到网卡缓存
    网络通信中需要在内核中进行协议封装和解封,造成很大的数据复制和上下文切换开销,性能低,同时内核很难支持新的网络协议,灵活性差。

RDMA核心原理

提供基于IO的通道,允许一个应用程序通过RDMA设备访问远程的虚拟内存。

  • CPU Offload:无需远程主机进程参与以读取远程主机内存,同时不涉及远程主机CPU的缓存
  • Kernel Bypass:提供专有的Verbs interface而非传统的TCP/IP Socket interface,应用程序可在用户态直接与网卡进行交互,完成数据传输,无需上下文切换
  • Zero Copy:应用程序可直接执行数据传输,即直接发送数据到主机缓冲区或直接从缓冲区接收,无需复制到网络层。
    根据以上特点,通过提供Verbs接口,应用程序可以在用户空间直接访问RDMA网卡,而RNIC中有Cached Page Table Entry,可将虚拟页面映射到物理页面。

直接访问内存机制

数据传输中的需CPU参与的数据拷贝和协议栈封装操作,允许计算机直接存取其他计算机内存,数据传输大部分工作由硬件执行

零拷贝和内核旁路

通信协议

三种技术具有同一套API,但物理层和链路层不同。

  • InfiniBand(IB):服务器和存储器互联技术,天然支持RDMA,需专用IB网卡和IB交换机。
  • RoCE:RDMA over Ethernet,基于以太网,需交换机支持无损以太网传输,网卡支持RoCE,可消耗很少的CPU负载在数据中心桥接以太网中利用优先流控制实现网络无损连接。可用在传统和非融合以太网中。
    • v1:链路层协议,允许同一广播域下任意两台主机直接访问
    • v2:Internet协议,可实现路由功能
  • iWARP:基于TCP/IP的RDMA技术,在TCP协议上增加一层DDP,不需要交换机支持无损以太网传输,仅需支持iWARP的网卡。部署的兼容和适用性较好,但大量TCP连接可能占用较多内存资源,性能相对较差。

RDMA编程

基本操作

  • Memory verbs
    • RDMA Read:调用者指定远程虚拟地址,像本地内存地址一样进行拷贝。远程主机设置好权限后,进行RDMA读和写对于远程主机都是透明。
    • RDMA write:将数据写到远端主机中。带即时数的RDMA操作会将即时数通知给远程主机。
    • RDMA Atomic:包括原子取、原子加、原子比较和原子交换,属于原子操作扩展
  • Messaging verbs
    • RDMA send:将数据发送到远程QP的接收队列。要求接收端事先注册好接收数据的缓冲区,发送者无法控制位置。可选择是否使用即时数,跟数据缓冲一起传送用于通知接收端接收。
    • RDMA receive:与发送对应,接收主机被告知接收到数据缓冲,还能带即时数。接收端应用程序会负责接收缓冲区的维护和发布。

传输模式

  • 可靠连接(RC):QP与QP一一对应,消息从一个QP的发送队列可靠传输到另一QP的接收队列。数据包按序交付,类似TCP
  • 不可靠连接(UC):QP与QP一一对应,但连接不可靠,数据包可能丢失,且传输层出错不重传
  • 不可靠数据报(UD):一个QP可跟其他任意的UD QP进行数据传输,不保证按序性和交付性。支持一对多,类似UDP。

RDMA编程接口

包括VerbsAPI和RDMA CM API等:

  • 设备查询和初始化
    1. ibv_get_device_list:获取系统RDMA设备列表
    2. ibv_open_device:选择列表中设备使用该接口打开,获取设备上下文
  • 内存注册
    • 通过ibv_reg_mr函数实现,将内存块固定防止被CPU患处,并返回ibv_mr结构体指针,里面含uint32_t类型的key、内存区域上下文、地址、长度等信息,key允许远程访问注册的内存
  • 队列对的创建和管理
    • 使用ibv_alloc_pd分配一个保护域
    • 通过ibv_create_cq创建完成队列
    • 使用ibv_create_qp创建包括发送队列和接收队列的队列对
  • 数据发送与接收
    • 发送通过ibv_post_send完成
    • 构建ibv_send_wr结构体,在里面设置好操作码、工作队列元素结构体、远程地址、远程键,将其提交到发送队列
    • 接收通过ibv_post_recv完成
    • 构建ibv_recv_wr提交到接收队列中
    • 操作完成后,完成队列会生成相应的完成队列元素CQE,通过轮询完成队列可获取操作完成状态和结果信息
  • 释放RDMA资源
    • 注册内存
    • 三个队列
    • 打开的设备符

内存注册和队列对

  • DMA引擎要求数据传输中应用不能修改数据所在内存,操作系统也不能进行page out,且要求物理地址连续
  • 内存注册时创建local和remote两个键指向需要操作的内存区域,被注册内存区域具有 上下文、地址、长度、本地键、远程键等属性,可交由RDMA保护域进行RDMA操作
  • 队列对是收发队列的组合,在一次SEND-RECV流程中,发送端把代表一次发送任务的工作队列元素WQE放在发送队列,同时接收端也需要发送代表接收任务的WQE到接收队列
  • 完成队列中的元素是CQE,描述某个任务时被正确执行还是遇到了错误,由发送端或接收端完成任务后生成和放入CQ。上层应用通过轮询CQ获取任务完成信息。

RDMA通信过程

主机之间的连接与认证依赖于队列对QP的建立,与socket类似,在进行数据传输之前,连接管理器CM负责QP信息的交换,完成初始化,之后利用verbsAPI进行RDMA读写和原子操作:

  1. 应用程序执行读或写操作,请求直接从用户空间发送到本地NIC网卡
  2. 本地NIC读取缓冲区内容发送到远程NIC
  3. RDMA信息包含虚拟地址、内存钥匙和数据本身。请求支持完全在用户空间处理,或应用睡眠直到请求完成时通过系统级终端处理。
  4. 目标NIC确认内存钥匙,按RDMA信息中的远程虚拟地址直接将数据写入应用缓存

Read/Write为单边操作,只需本地明确信息源和目标地址,远程不必感知,数据读写在远端RNIC和应用Buffer之间完成,由远端RNIC封装成消息返回到本地端,多用于数据报文。而Send/Receive为双边操作,需要远端应用感知参与才能完成,多用于连接控制报文。

单向通信-读Read/写

  1. A、B已建立连接,QP已经完成创建和初始化
  2. 数据已经存在于B的buffer,作为内存区已经提前注册到B的RNIC,具有local key,可以接受RDMA操作
  3. B已经将数据的地址VB,key通过专用报文发送给A,同时在WQ中注册了一个WR,用于接收数据传输的A返回的状态
  4. A在收到B发送过来的数据地址VB和R_key后,RNIC将这俩信息和本地地址封装到Read请求,发送到B,接下来不需要任何软件参与即可将B中buffer数据存储到A的虚拟地址 或将 A的数据存储到B的buffer
  5. A在接收/发送数据完成后,向B返回数据传输状态信息

双向通信

  1. AB各自初始化各自的QP、CQ
  2. AB分别在自己的WQ中注册WQE,对A则是SQ,WQE表示待发送数据,对于B,WQ为RQ,WQE指向用于存储数据的buffer
  3. A的RNIC异步调度到A的WQE,识别到Send消息,从buffer直接向B发送数据。数据流到达B的RNIC后,B的WQE被消耗,数据被存储到WQE指向的位置
  4. AB通信完成后,A的CQ产生完成消息CQE表示发送完成,B则产生CQE表示接收完成。