小林coding《什么是零拷贝》笔记
参考:
The Linux Kernel Linux :Concepts overview
ALEX XU 徐旭 :Why is Kafka fast?
每个存储器只和相邻的一层存储器设备打交道,并且存储设备为了追求更快的速度,所需的材料成本必然也是更高,也正因为成本太高,所以 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
标签:DMA,Cache,内核,拷贝,CPU,内存 From: https://www.cnblogs.com/suBlog/p/17667930.html