小林coding《什么是零拷贝》笔记

参考:

The Linux Kernel Linux :Concepts overview

兰新宇 :  Linux中的mmap映射 [一]

ALEX XU  徐旭 :Why is Kafka fast?

nginx.com/blog Improving NGINX Performance with Kernel TLS and SSL_sendfile( )

 

每个存储器只和相邻的一层存储器设备打交道,并且存储设备为了追求更快的速度,所需的材料成本必然也是更高,也正因为成本太高,所以 CPU 内部的寄存器、L1\L2\L3 Cache 只好用较小的容量,相反内存、硬盘则可用更大的容量,这就我们今天所说的存储器层次结构。

速度越快的存储器,造价成本往往也越高,存储容量也就越小。下面这张表格是不同层级的存储器之间的成本对比图:

 

 

Page Cache

物理内存是易失性的,将数据存入内存的常见情况是从文件中读取数据。每当读取文件时,数据都会放入物理内存 Page Cache 中避免后续读取时昂贵的磁盘访问。类似地,当写入文件时,数据被放置在页面缓存中并最终进入磁盘等存储设备。写入的页面被标记为脏页,当 Linux 决定将放置 Page Cache 的内存用于其他目的时,它会确保将脏页更新到磁盘。

这块 放置 Page Cache 的物理内存是由 linux 内核虚拟内存管理的用户进程需要切换到内核态才能访问。

 

为什么要有 DMA 技术?

在没有 DMA 技术前,I/O 的过程是这样的:

整个数据的传输过程,都要需要 CPU 亲自参与搬运数据的过程,而且这个过程,CPU 是不能做其他事情的。

用 DMA 控制器进行数据传输

什么是 DMA 技术?简单理解就是,在进行 I/O 设备和内存的数据传输的时候,数据搬运的工作全部交给 DMA 控制器,而 CPU 不再参与任何与数据搬运相关的事情,这样 CPU 就可以去处理别的事务。

 

 

传统的文件传输

场景:将磁盘上的文件读取出来,然后通过网络协议发送给客户端。

 

  • 上图 read 时的缓存区就是内核空间Page Cache,从磁盘读取的文件会放在那里。需要由系统调用 read() 切换到内核态来拷贝到用户缓冲区读取。
  • 由 TCP 发送数据的 socket 缓冲区也是内核管理的(Socket 也是一种文件)。需要由系统调用 write() 切换到内核态来写入。

发生了 4 次用户态与内核态的上下文切换因为发生了两次系统调用,一次是 read() ,一次是 write()每次系统调用都得先从用户态切换到内核态,等内核完成任务后,再从内核态切换回用户态。

还发生了 4 次数据拷贝,其中两次是 DMA 的拷贝,另外两次则是通过 CPU 拷贝的。

所以,要想提高文件传输的性能,就需要减少「用户态与内核态的上下文切换」和「内存拷贝」的次数。

 

mmap + write

 mmap() 系统调用函数可以把内核管理的 Page Cache 文件缓存「映射」到用户空间Page Cache 就可以由用户进程直接读写了。这样,操作系统内核与用户空间就不需要再进行任何的数据拷贝操作。

于是 使用 mmap() 来代替 read()减少了一次数据拷贝的过程

但是 mmap() 是懒加载,实际访问时会触发大量缺页中断建立实际的物理内存映射。另一方面,随着硬件性能的发展,内存拷贝消耗的时间已经大大降低了。所以啊,很多情况下,mmap()的性能反倒是比不过read()和write()的。

 

sendfile

Linux 内核版本 2.1 中,提供了一个专门发送文件的系统调用函数 sendfile(),函数形式如下:

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

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

这就是所谓的零拷贝(Zero-copy)技术,数据直接在内核态】由【源文件 - Page Cahe 缓冲区】 发到【目标文件 - Socket 缓冲区】 ,也就是说全程没有通过 CPU 来把内存数据从内核态搬运到用户空间,所有的数据都是通过 DMA 来进行传输的。。

零拷贝技术的文件传输方式相比传统文件传输的方式,减少了 2 次上下文切换和数据拷贝次数,只需要 2 次上下文切换和数据拷贝次数,就可以完成文件的传输,而且 2 次的数据拷贝过程,都不需要通过 CPU,2 次都是由 DMA 来搬运。

所以,总体来看,零拷贝技术可以把文件传输的性能提高至少一倍以上。

 

使用零拷贝技术的项目

Kafka

追溯 Kafka 文件传输的代码,你会发现,最终它调用了 Java NIO 库里的 transferTo 方法:

@Overridepublic 
long transferFrom(FileChannel fileChannel, long position, long count) throws IOException { 
    return fileChannel.transferTo(position, count, socketChannel);
}

如果 Linux 系统支持 sendfile() 系统调用,那么 transferTo() 实际上最后就会使用到 sendfile() 系统调用函数。

生产者消息写入 PageCache 后(即 CommitLog 文件),直接在内核态 sendFile 发到 Socket 缓冲区给消费者。

RocketMQ

rocketMQ 没有采用零拷贝技术,而是用了 mmap + write

Nginx

在内核中实现的 TLS 叫 kTLS, 可显着减少用户空间和内核之间复制操作的需要,从而提高性能。

将 kTLS 和 sendfile() 结合意味着数据在传递到网络堆栈进行传输之前直接在内核空间中加密。这样就无需将数据复制到用户空间以由 TLS 库加密,然后再复制回内核空间进行传输。 kTLS 还支持将 TLS 处理卸载到硬件,包括将 TLS 对称加密处理卸载到网络设备。