RDMA技术整理1 DMA技术概述

RDMA全称是Remote Direct Memory Access,基本理念可以看成是远程的DMA技术,目的是为了解决网络传输中数据处理的延迟。因此首先了解一下DMA技术

DMA技术与零拷贝

参考于该博客

该技术用于I/O过程。众所周知,磁盘速度相对于内存以及CPU cache非常慢,因此有很多用于优化磁盘的技术比如零拷贝,异步I/O等等。DMA也是其中的技术之一。

DMA背景

DMA技术之前,I/O过程如下图:

传统IO
传统IO
  • CPU 发出对应的指令给磁盘控制器,然后返回;
  • 磁盘控制器收到指令后,于是就开始准备数据,会把数据放入到磁盘控制器的内部缓冲区中,然后产生一个中断
  • CPU 收到中断信号后,停下手头的工作,接着把磁盘控制器的缓冲区的数据一次一个字节地读进自己的寄存器,然后再把寄存器里的数据写入到内存,而在数据传输的期间 CPU 是无法执行其他任务的。

这种策略需要CPU亲自参与数据的搬运,在传输大量数据的时候用CPU搬运显然是开销过大的;因此开发了新设备DMA控制器负责数据的搬运。具体过程如下图:

有DMA的IO
有DMA的IO
  • 用户进程调用 read 方法,向操作系统发出 I/O 请求,请求读取数据到自己的内存缓冲区中,进程进入阻塞状态;
  • 操作系统收到请求后,进一步将 I/O 请求发送 DMA,然后让 CPU 执行其他任务;
  • DMA 进一步将 I/O 请求发送给磁盘;
  • 磁盘收到 DMA 的 I/O 请求,把数据从磁盘读取到磁盘控制器的缓冲区中,当磁盘控制器的缓冲区被读满后,向 DMA 发起中断信号,告知自己缓冲区已满;
  • DMA 收到磁盘的信号,将磁盘控制器缓冲区中的数据拷贝到内核缓冲区中,此时不占用 CPU,CPU 可以执行其他任务;
  • 当 DMA 读取了足够多的数据,就会发送中断信号给 CPU;
  • CPU 收到 DMA 的信号,知道数据已经准备好,于是将数据从内核拷贝到用户空间,系统调用返回;

可以看出在数据传输的过程中,CPU不再参与数据搬运的工作,这部分由DMA做完了,CPU要做的是进行DMA的调用。DMA一开始是存在于主板,现在一般处于各个I/O设备中。


文件传输过程

如果服务端要提供文件传输的功能,我们能想到的最简单的方式是:将磁盘上的文件读取出来,然后通过网络协议发送给客户端。传统的方法需要两个系统调用:

1
2
read(file, tmp_buf, len);
write(socket, tmp_buf, len);
传统传输文件
传统传输文件

如上图发生了 4 次用户态与内核态的上下文切换,因为发生了两次系统调用,每一次调用都意味着:用户态保留现场,转换到内核态处理,转换到用户态这样的过程,每一次切换都需要耗时几十纳秒到几微秒,高并发情况下会影响系统性能。

另外发生了四次拷贝:

  1. 磁盘通过DMA拷贝到内核缓冲区
  2. 内核缓冲区数据拷贝到用户缓冲区
  3. 用户缓冲区拷贝到内核缓冲区
  4. 内核缓冲区通过DMA拷贝到网卡缓冲区

数据拷贝也会消耗CPU资源降低系统性能。

总共发生了四次上下文切换和四次数据拷贝

零拷贝

为了优化文件传输的性能,主要从两个方面下手:减少用户态和内核态上下文的切换(也就是减少系统调用),减少数据拷贝的次数;

为了实现零拷贝,目前主要由两种方式:

  • mmap+write
  • sendfile

mmap+write

使用read()会把内核缓冲区的数据拷贝到用户缓冲区中,使用mmap()会直接把内核缓冲区映射到用户空间,减少了内核和用户空间之间的数据拷贝:

1
2
buf = mmap(file, len);
write(sockfd, buf, len);
mmap+write
mmap+write

如上图:调用了mmap()之后,DMA就会把磁盘的数据拷贝到内核的缓冲区里。接着,应用进程跟操作系统内核「共享」这个缓冲区;然后使用write()会让系统直接把内核缓冲区的数据拷贝到socket缓冲区中,最后DMA把socket缓冲区的数据拷贝到网卡缓冲区中。

因此mmap()代替read()可以减少一次数据拷贝。最后需要四次上下文切换,三次数据拷贝

sendfile

在linux 2.1内核中出现了sendfile()系统调用:

1
2
#include <sys/socket.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

前两个参数分别是目的端和源端的文件描述符,后面两个参数是源端的偏移量和复制数据的长度,返回值是实际复制数据的长度。

它可以代替read(),write()系统调用,减少了一次系统调用(相当于减少了两次上下文切换);另外它可以直接把内核缓冲区的数据拷贝到socket缓冲区中不需要拷贝到用户态,最后只有两次上下文切换和三次数据拷贝

sendfile1
sendfile1

如果网卡支持SG-DMA(The Scatter-Gather Direct Memory Access),那么可以进一步减少通过CPU把内核缓冲区拷贝到socket缓冲区的过程;

linux系统可以通过如下命令查看是否支持:

1
2
$ ethtool -k eth0 | grep scatter-gather
scatter-gather: on

如果支持该功能,并且linux内核>=2.4,那么sendfile()的过程会发生如下变化:

  1. 第一步,通过 DMA 将磁盘上的数据拷贝到内核缓冲区里;
  2. 第二步,缓冲区描述符和数据长度传到 socket 缓冲区,这样网卡的 SG-DMA 控制器就可以直接将内核缓存中的数据拷贝到网卡的缓冲区里,此过程不需要将数据从操作系统内核缓冲区拷贝到 socket 缓冲区中,这样就减少了一次数据拷贝;
sendfile2
sendfile2

这就是零拷贝,因为没有通过CPU来进行数据搬运的过程;

最后总共使用了两次上下文切换和两次数据拷贝,并且都不需要走CPU,都是DMA进行的拷贝;可以把传输文件的性能提高至少一倍以上。