RDMA简介
DMA与零拷贝技术
DMA背景
在没有 DMA 技术前,I/O 的过程是这样的:
- 用户向CPU发出read请求;
- CPU 发出对应的指令给磁盘控制器,然后返回;
- 磁盘控制器收到指令后,把数据放入到磁盘控制器的内部缓冲区中,然后产生一个中断;
- CPU 收到中断信号后,停下手头的工作,并把磁盘控制器的缓冲区的数据一次一个字节地读进自己的寄存器,然后再把寄存器里的数据写入到内存;
- 用户通过read回调读取缓冲区数据。
在数据传输的期间 CPU 是无法执行其他任务的。
DMA技术
传统I/O过程需要CPU全程参与数据搬运的过程,且在数据传输期间CPU无法执行其他任务,在传输大量数据的场景下,CPU显然成为传输瓶颈。
针对传统I/O痛点,产生了直接内存访问DMA(Direct Memory Access)技术(单片机原理及应用中学到过)。
原理: 在进行 I/O 设备和内存的数据传输的时候,数据搬运的工作全部交给 DMA 控制器,而 CPU 不再参与任何与数据搬运相关的事情,这样 CPU 就可以去处理别的事务。
具体过程:
- 用户进程调用 read 方法,向操作系统发出 I/O 请求,请求读取数据到自己的内存缓冲区中,进程进入阻塞状态;
- 操作系统收到请求后,进一步将 I/O 请求发送 DMA,然后让 CPU 执行其他任务;
- DMA 进一步将 I/O 请求发送给磁盘;
- 磁盘收到 DMA 的 I/O 请求,把数据从磁盘读取到磁盘控制器的缓冲区中,当磁盘控制器的缓冲区被读满后,向 DMA 发起中断信号,告知自己缓冲区已满;
- DMA 收到磁盘的信号,将磁盘控制器缓冲区中的数据拷贝到内核缓冲区中,此时不占用 CPU,CPU 可以执行其他任务;
- 当 DMA 读取了足够多的数据,就会发送中断信号给 CPU;
- CPU 收到 DMA 的信号,知道数据已经准备好,于是将数据从内核拷贝到用户空间,系统调用返回。
CPU 不再参与数据搬运的工作,而是全程由 DMA 完成
网络文件传输
传统文件传输
服务端提供的文件传输的功能,需要将磁盘上的文件读取出来,然后通过网络协议发送给客户端。
传统 I/O 的工作方式是:数据读取和写入是从用户空间到内核空间来回复制,而内核空间的数据是通过操作系统层面的 I/O 接口从磁盘读取或写入。
1 |
|
数据拷贝过程如下:
首先,期间共发生了 4 次用户态与内核态的上下文切换,因为发生了两次系统调用,一次是 read() ,一次是 write(),每次系统调用都得先从用户态切换到内核态,等内核完成任务后,再从内核态切换回用户态。
上下文切换的成本并不小,一次切换需要耗时几十纳秒到几微秒,在高并发的场景下,这类时间容易被累积和放大,从而影响系统的性能。
其次,还发生了 4 次数据拷贝,其中两次是 DMA 的拷贝,另外两次则是通过 CPU 拷贝的,下面说一下这个过程:
- 第一次拷贝,把磁盘上的数据拷贝到操作系统内核的缓冲区里,这个拷贝的过程是通过 DMA 搬运的。
- 第二次拷贝,把内核缓冲区的数据拷贝到用户的缓冲区里,于是我们应用程序就可以使用这部分数据了,这个拷贝到过程是由 CPU 完成的。
- 第三次拷贝,把刚才拷贝到用户的缓冲区里的数据,再拷贝到内核的 socket 的缓冲区里,这个过程依然还是由 CPU 搬运的。
- 第四次拷贝,把内核的 socket 缓冲区里的数据,拷贝到网卡的缓冲区里,这个过程又是由 DMA 搬运的。
我们回过头看这个文件传输的过程,我们只是搬运一份数据,结果却搬运了 4 次,过多的数据拷贝无疑会消耗 CPU 资源,大大降低了系统性能。
这种简单又传统的文件传输方式,存在冗余的上文切换和数据拷贝,在高并发系统里是非常糟糕的,多了很多不必要的开销,会严重影响系统性能。
所以,要想提高文件传输的性能,就需要减少 「用户态与内核态的上下文切换」 和 「内存拷贝」 的次数。
零拷贝技术
通常有2种方法实现零拷贝技术:
- mmap + write
- sendfile
mmap + write
使用mmap()
代替read()
函数,mmap()
函数会直接吧内核缓冲区里的数据映射到用户空间,取消了系统内核与用户空间之间的数据拷贝操作。
1 |
|
- 应用进程调用了
mmap()
后,DMA 会把磁盘的数据拷贝到内核的缓冲区里。接着,应用进程跟操作系统内核「共享」这个缓冲区; - 应用进程再调用
write()
,操作系统直接将内核缓冲区的数据拷贝到 socket 缓冲区中,这一切都发生在内核态,由 CPU 来搬运数据; - 最后,把内核的 socket 缓冲区里的数据,拷贝到网卡的缓冲区里,这个过程是由 DMA 搬运的。
sendfile
sendfile()
函数是linux系统提供的专门用于发送文件的系统函数。
1 |
|
- 它可以替代前面的
read()
和write()
这两个系统调用,这样就可以减少一次系统调用,也就减少了 2 次上下文切换的开销。 - 该系统调用,还可以直接把内核缓冲区里的数据拷贝到 socket 缓冲区里,不再拷贝到用户态,这样就只有 2 次上下文切换,和 3 次数据拷贝。
SG-DMA
如果网卡支持 SG-DMA(The Scatter-Gather Direct Memory Access)技术(和普通的 DMA 有所不同),可以进一步减少通过 CPU 把内核缓冲区里的数据拷贝到 socket 缓冲区的过程。
可以在你的 Linux 系统通过下面这个命令,查看网卡是否支持 scatter-gather 特性:
1 |
|
对于支持网卡支持 SG-DMA 技术的情况下, sendfile()
系统调用的过程发生了点变化,具体过程如下:
- 第一步,通过 DMA 将磁盘上的数据拷贝到内核缓冲区里;
- 第二步,缓冲区描述符和数据长度传到 socket 缓冲区,这样网卡的 SG-DMA 控制器就可以直接将内核缓存中的数据拷贝到网卡的缓冲区里,此过程不需要将数据从操作系统内核缓冲区拷贝到 socket 缓冲区中,这样就减少了一次数据拷贝;
这个过程之中,只进行了 2 次数据拷贝,如下图:
以上就是所谓的零拷贝(Zero-copy)技术,因为我们没有在内存层面去拷贝数据,全程没有通过 CPU 来搬运数据,所有的数据都是通过 DMA 来进行传输的。
零拷贝技术的文件传输方式相比传统文件传输的方式,减少了 2 次上下文切换和数据拷贝次数,只需要 2 次上下文切换和数据拷贝次数,就可以完成文件的传输,而且 2 次的数据拷贝过程,都不需要通过 CPU,2 次都是由 DMA 来搬运。
大文件传输
文章之前提到的内核缓冲区实际上是磁盘高速缓存(PageCache)。
优势:
- 缓存最近被访问的数据
- 预读功能
痛点:
- 在传输大文件(GB 级别的文件)的时候,PageCache 会不起作用,那就白白浪费 DMA 多做的一次数据拷贝,造成性能的降低,即使使用了 PageCache 的零拷贝也会损失性能。
- 因为如果你有很多 GB 级别文件需要传输,每当用户访问这些大文件的时候,内核就会把它们载入 PageCache 中,于是 PageCache 空间很快被这些大文件占满。
- PageCache 由于长时间被大文件占据,其他「热点」的小文件可能就无法充分使用到 PageCache,于是这样磁盘读写的性能就会下降了;
- PageCache 中的大文件数据,由于没有享受到缓存带来的好处,但却耗费 DMA 多拷贝到 PageCache 一次;
在高并发的场景下,针对大文件的传输的方式,应该使用「异步 I/O + 直接 I/O」来替代零拷贝技术。
由于传统read()
方式读取文件时,进程会等待磁盘数据返回从而阻塞在read()
方法调用,通过异步I/O的方法解决阻塞问题:
把读操作分为两部分:
- 前半部分,内核向磁盘发起读请求,但是可以不等待数据就位就可以返回,于是进程此时可以处理其他任务;
- 后半部分,当内核将磁盘中的数据拷贝到进程缓冲区后,进程将接收到内核的通知,再去处理数据。
异步 I/O 并没有涉及到 PageCache,所以使用异步 I/O 就意味着要绕开 PageCache。
绕开 PageCache 的 I/O 叫直接 I/O,使用 PageCache 的 I/O 则叫缓存 I/O。通常,对于磁盘,异步 I/O 只支持直接 I/O。
RDMA基本原理
传统网络通信
使用TCP/IP协议的应用程序通常采用应用编程接口:UNIX BSD的套接字(socket),来实现网络进程之间的通信。
使用传统的TCP/IP通信,数据需要通过用户空间发送到远端的用户空间,就需要经历若干次内存拷贝。
- 数据发送方需要将数据从用户空间 Buffer 复制到内核空间的 Socket Buffer;
- 数据发送方要在内核空间中添加数据包头,进行数据封装;
- 数据从内核空间的 Socket Buffer 复制到 NIC Buffer 进行网络传输;
- 数据接受方接收到从远程机器发送的数据包后,要将数据包从 NIC Buffer 中复制到内核空间的 Socket Buffer;
- 经过一系列的多层网络协议进行数据包的解析工作,解析后的数据从内核空间的 Socket Buffer 被复制到用户空间 Buffer;
- 这个时候再进行系统上下文切换,用户应用程序才被调用。
RDMA
为了解决传统网络通信在计算任务中的瓶颈问题,远程直接内存访问RDMA(Remote Direct Memory Access)技术应运而生。该技术采用Kernel Bypass和Zero Copy技术,实现了低延迟和高带宽利用率的目标,同时有效降低了CPU占用和内存带宽瓶颈。RDMA为I/O操作提供了专用的通道,允许应用程序直接读写远程系统的虚拟内存,通过RDMA设备进行数据交换。
简介:
- Remote:数据通过网络与远程机器间进行数据传输。
- Direct:没有内核的参与,有关发送传输的所有内容都卸载到网卡上。
- Memory:在用户空间虚拟内存与RNIC网卡直接进行数据传输不涉及到系统内核,没有额外的数据移动和复制。
- Access:send、receive、read、write、atomic操作。
特点:
- CPU Offload:无需CPU干预,应用程序可以访问远程主机内存而不消耗远程主机中的任何CPU。远程主机内存能够被读取而不需要远程主机上的进程(或CPU)参与。远程主机的CPU的缓存(cache)不会被访问的内存内容所填充。
- Kernel Bypass:RDMA 提供一个专有的 Verbs interface 而不是传统的TCP/IP Socket interface。应用程序可以直接在用户态执行数据传输,不需要在内核态与用户态之间做上下文切换。
- Zero Copy:每个应用程序都能直接访问集群中的设备的虚拟内存,这意味着应用程序能够直接执行数据传输,在不涉及到网络软件栈的情况下,数据能够被直接发送到缓冲区或者能够直接从缓冲区里接收,而不需要被复制到网络层。
硬件实现
RDMA目前主要有三种硬件实现,可以使用同一套API:
- Infiniband:基于 InfiniBand 架构的 RDMA 技术,由 IBTA(InfiniBand Trade Association)提出。搭建基于 IB 技术的 RDMA 网络需要专用的 IB 网卡和 IB 交换机。从性能上,很明显Infiniband网络最好,但网卡和交换机是价格也很高,然而RoCEv2和iWARP仅需使用特殊的网卡就可以了,价格也相对便宜很多。
- iWARP:Internet Wide Area RDMA Protocal,基于 TCP/IP 协议的 RDMA 技术,由 IETF 标 准定义。iWARP 支持在标准以太网基础设施上使用 RDMA 技术,而不需要交换机支持无损以太网传输,但服务器需要使用支持iWARP 的网卡。与此同时,受 TCP 影响,性能稍差。
- RoCE:基于以太网的 RDMA 技术,也是由 IBTA 提出。RoCE支持在标准以太网基础设施上使用RDMA技术,但是需要交换机支持无损以太网传输,需要服务器使用 RoCE 网卡,性能与 IB 相当。
RoCE 协议分为两个版本:
- RoCE v1协议:基于以太网承载 RDMA,只能部署于二层网络,它的报文结构是在原有的 IB 架构的报文上增加二层以太网的报文头,通过 Ethertype 0x8915标识 RoCE 报文。
- RoCE v2协议:基于 UDP/IP 协议承载 RDMA,可部署于三层网络,它的报文结构是在原有的 IB 架构的报文上增加 UDP 头、IP 头和二层以太网报文头,通过 UDP 目的端口号 4791 标 识 RoCE 报文。RoCE v2 支持基于源端口号 hash,采用 ECMP 实现负载分担,提高了网络的利用率。
RDMA与传统技术对比
传统socket通信:
socket数据流:
需要从用户空间复制到内核空间(CPU)然后网卡从内核空间把处理好的数据从物理链路发送到对端网卡;对端网卡过程相似。
RDMA把模型分成了两部分,一部分是控制通路一部分是数据通路,控制通路需要进入内核态准备通信所需的内存资源,而数据通路指的是实际数据交互过程中的流程。具体的通信过程如下:
- 发送端和接收端分别通过控制通路陷入内核态创建好通信所需要的内存资源。
- 在数据通路上,接收端APP通知硬件准备接收数据,告诉硬件将接收到的数据放在哪片内存中。
- 在数据通路上,发送端APP通知硬件发送数据,告诉硬件待发送数据位于哪片内存中。
- 发送端RDMA网卡从内存中搬移数据,组装报文发送给对端。
- 对端收到报文,对其进行解析并通过DMA将有效载荷写入内存。然后以某种方式通知上层APP,告知其数据已经接受并且存放到指定位置。
RDMA数据流向:
报文的组装和解析通过硬件而不是CPU完成