深入理解零拷贝

1. 普通文件拷贝

1.1 什么是文件拷贝

文件拷贝指的是数据在内核缓冲区和应用程序缓冲区直接的传输,并非指进程空间中的内存拷贝。

1.2 传统IO执行流程

image.png

  1. 什么是用户态和内核态?什么是DMA 拷贝?(科普一下相关的概念)

用户态和内核态:用户状态就是你写了一些操作文件的代码,JVM帮你读了出来,然后发现你需要操作文件,可是操作系统不可能让你用代码直接去操作这个文件,所以这个时候就需要切换到内核态来协助你完成本次文件的操作,可以理解为调用内核态来帮你把文件复制出来让你操作。
DMA(DirectMemoryAccess,直接内存存取)其实就是因为 CPU 还有许多需要做的事情,所以找了个助理,也就是 DMA 替他完成一部分的拷贝工作,这样 CPU 就能去完成其他事情。

  1. 梳理一下上图的详细过程:

磁盘文件–>内核缓冲区: 一次DMA拷贝,由用户态切到内核态,即一次上下文切换;
内核缓冲区–>用户缓存区: 一次CPU拷贝,由内核态切换到用户态,即一次上下文切换;
用户缓冲区–>socket缓冲区:一次CPU拷贝,由用户态切换到内核态,即一次上下文切换;
socket缓冲区–>协议引擎:一次DMA拷贝,IO操作完毕后会再次切换到用户态,即一次上下文切换
简单总结一下:整个过程有4次拷贝(CPU拷贝和DMA拷贝各2次)+4次上下文切换,总体来说普通文件拷贝的过程还是相对较繁琐。

1.3 代码实现

用简单伪代码来实现一下普通文件的拷贝(如去服务器上下载文件):

//1.打开文件
File filefd = new file(...); 
//2.打开socket
Socket sockfd = socket(...); 
//3.创建buffer
buffer = new buffer(...); 
//4.从文件内容读到buffer中
read(filefd, buffer); 
//5.将buffer中的内容发送到网络
write(sockfd, buffer); 

1.4 零拷贝

上面谈到了传统IO执行流程(文件拷贝)比较繁琐,需要经历4次拷贝和4次上下文切换,为了优化这个问题则有了零拷贝技术。
常见的零拷贝技术有2种:mmap和sendfile。

2. mmap

2.1 什么是内存映射?

内存映射将用户空间的一段内存区域映射到内核空间,用户对这段内存区域的修改可以直接反映到内核空间,同样,内核空间对这段区域的修改也直接反映用户空间,简单来说就是用户空间共享这个内核缓区。

2.2 内存映射后的流程图

image.png
从图中可以看出,采用内存映射后数据拷贝减少为 3 次(2次DMA拷贝+1次CPU拷贝),内核缓冲区数据直接到socket缓冲区,但是上下文切换还是4次。
RocketMQ 中就是使用的 mmap 来提升磁盘文件的读写性能。

2.3 mmap优化

使用mmap对文件拷贝的优化,伪代码如下:

//打开文件
File filefd = new file(...); 
//打开socket
Socket sockfd = socket(...); 
//将文件映射到进程空间
buffer = mmap(filefd);
//将buffer中的内容发送到网络
write(sockfd, buffer); 

3. Sendfile

3.1 什么是零拷贝技术?

零拷贝就是一种避免 CPU 将数据从一块存储拷贝到另外一块存储,从而有效地提高数据传输效率的技术。
sendfile是Linux提供的函数,实现了零拷贝。

3.2 SendFile的流程图

image.png
从上图可以得出结论,整个过程只有2次DMA拷贝,没有了CPU拷贝(这也是sendfile技术叫做零拷贝的原因),且上下文切换也只有2次,磁盘文件读写的效率也会大幅度提升。
Kafka 和 Tomcat 内部使用就是 sendFile 这种零拷贝。

3.3 SendFile优化

使用sendfile来优化以上普通读写的代码:

//打开文件
File filefd = new file(...); 
//打开socket
sockfd = socket(...); 
//将文件内容发送到网络
sendfile(sockfd, filefd);

4. 零拷贝的总结

  1. 传统 IO 执行的话需要 4 次上下文切换(用户态 -> 内核态 -> 用户态 -> 内核态 -> 用户态)和 4 次拷贝(磁盘文件 DMA 拷贝到内核缓冲区,内核缓冲区 CPU 拷贝到用户缓冲区,用户缓冲区 CPU 拷贝到 Socket 缓冲区,Socket 缓冲区 DMA 拷贝到协议引擎)。

  2. mmap 将磁盘文件映射到内存,支持读和写,对内存的操作会反映在磁盘文件上,适合小数据量读写,需要 4 次上下文切换(用户态 -> 内核态 -> 用户态 -> 内核态 -> 用户态)和3 次拷贝(磁盘文件DMA拷贝到内核缓冲区,内核缓冲区 CPU 拷贝到 Socket 缓冲区,Socket 缓冲区 DMA 拷贝到协议引擎)。

  3. sendfile 是将读到内核空间的数据,转到 socket buffer,进行网络发送,适合大文件传输,只需要 2 次上下文切换(用户态 -> 内核态 -> 用户态)和 2 次拷贝(磁盘文件 DMA 拷贝到内核缓冲区,内核缓冲区 DMA 拷贝到协议引擎)。

参考文章:https://www.xttblog.com/?p=5101

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值