怎么用服务器ip做网站,淮安注册公司,wordpress 数据库优化插件,烟台网站排名优化费用前言 零拷贝是老生常谈的问题啦#xff0c;大厂非常喜欢问。比如Kafka为什么快#xff0c;RocketMQ为什么快等#xff0c;都涉及到零拷贝知识点。最近技术讨论群几个伙伴分享了阿里、虾皮的面试真题#xff0c;也都涉及到零拷贝。因此本文将跟大家一起来学习零拷贝原理。1.… 前言 零拷贝是老生常谈的问题啦大厂非常喜欢问。比如Kafka为什么快RocketMQ为什么快等都涉及到零拷贝知识点。最近技术讨论群几个伙伴分享了阿里、虾皮的面试真题也都涉及到零拷贝。因此本文将跟大家一起来学习零拷贝原理。1. 什么是零拷贝2. 传统的IO执行流程3. 零拷贝相关的知识点回顾4. 零拷贝实现的几种方式5. java提供的零拷贝方式1.什么是零拷贝 零拷贝字面上的意思包括两个“零”和“拷贝”“拷贝”就是指数据从一个存储区域转移到另一个存储区域。“零” 表示次数为0它表示拷贝数据的次数为0。合起来那零拷贝就是不需要将数据从一个存储区域复制到另一个存储区域咯。零拷贝是指计算机执行IO操作时CPU不需要将数据从一个存储区域复制到另一个存储区域从而可以减少上下文切换以及CPU的拷贝时间。它是一种I/O操作优化技术。2. 传统 IO 的执行流程 做服务端开发的小伙伴文件下载功能应该实现过不少了吧。如果你实现的是一个web程序前端请求过来服务端的任务就是将服务端主机磁盘中的文件从已连接的socket发出去。关键实现代码如下while((n read(diskfd, buf, BUF_SIZE)) 0)write(sockfd, buf , n);传统的IO流程包括read和write的过程。read把数据从磁盘读取到内核缓冲区再拷贝到用户缓冲区write先把数据写入到socket缓冲区最后写入网卡设备。流程图如下用户应用进程调用read函数向操作系统发起IO调用上下文从用户态转为内核态切换1DMA控制器把数据从磁盘中读取到内核缓冲区。CPU把内核缓冲区数据拷贝到用户应用缓冲区上下文从内核态转为用户态切换2read函数返回用户应用进程通过write函数发起IO调用上下文从用户态转为内核态切换3CPU将用户缓冲区中的数据拷贝到socket缓冲区DMA控制器把数据从socket缓冲区拷贝到网卡设备上下文从内核态切换回用户态切换4write函数返回从流程图可以看出传统IO的读写流程包括了4次上下文切换4次用户态和内核态的切换4次数据拷贝两次CPU拷贝以及两次的DMA拷贝)什么是DMA拷贝呢我们一起来回顾下零拷贝涉及的操作系统知识点哈。3. 零拷贝相关的知识点回顾 3.1 内核空间和用户空间我们电脑上跑着的应用程序其实是需要经过操作系统才能做一些特殊操作如磁盘文件读写、内存的读写等等。因为这些都是比较危险的操作不可以由应用程序乱来只能交给底层操作系统来。因此操作系统为每个进程都分配了内存空间一部分是用户空间一部分是内核空间。内核空间是操作系统内核访问的区域是受保护的内存空间而用户空间是用户应用程序访问的内存区域。 以32位操作系统为例它会为每一个进程都分配了4G(2的32次方)的内存空间。内核空间主要提供进程调度、内存分配、连接硬件资源等功能用户空间提供给各个程序进程的空间它不具有访问内核空间资源的权限如果应用程序需要使用到内核空间的资源则需要通过系统调用来完成。进程从用户空间切换到内核空间完成相关操作后再从内核空间切换回用户空间。3.2 什么是用户态、内核态如果进程运行于内核空间被称为进程的内核态如果进程运行于用户空间被称为进程的用户态。3.3 什么是上下文切换什么是CPU上下文CPU 寄存器是CPU内置的容量小、但速度极快的内存。而程序计数器则是用来存储 CPU 正在执行的指令位置、或者即将执行的下一条指令位置。它们都是 CPU 在运行任何任务前必须的依赖环境因此叫做CPU上下文。什么是CPU上下文切换它是指先把前一个任务的CPU上下文也就是CPU寄存器和程序计数器保存起来然后加载新任务的上下文到这些寄存器和程序计数器最后再跳转到程序计数器所指的新位置运行新任务。一般我们说的上下文切换就是指内核操作系统的核心在CPU上对进程或者线程进行切换。进程从用户态到内核态的转变需要通过系统调用来完成。系统调用的过程会发生CPU上下文的切换。CPU 寄存器里原来用户态的指令位置需要先保存起来。接着为了执行内核态代码CPU 寄存器需要更新为内核态指令的新位置。最后才是跳转到内核态运行内核任务。3.4 虚拟内存现代操作系统使用虚拟内存即虚拟地址取代物理地址使用虚拟内存可以有2个好处虚拟内存空间可以远远大于物理内存空间多个虚拟内存可以指向同一个物理地址正是多个虚拟内存可以指向同一个物理地址可以把内核空间和用户空间的虚拟地址映射到同一个物理地址这样的话就可以减少IO的数据拷贝次数啦示意图如下3.5 DMA技术DMA英文全称是Direct Memory Access即直接内存访问。DMA本质上是一块主板上独立的芯片允许外设设备和内存存储器之间直接进行IO数据传输其过程不需要CPU的参与。我们一起来看下IO流程DMA帮忙做了什么事情.用户应用进程调用read函数向操作系统发起IO调用进入阻塞状态等待数据返回。CPU收到指令后对DMA控制器发起指令调度。DMA收到IO请求后将请求发送给磁盘磁盘将数据放入磁盘控制缓冲区并通知DMADMA将数据从磁盘控制器缓冲区拷贝到内核缓冲区。DMA向CPU发出数据读完的信号把工作交换给CPU由CPU负责将数据从内核缓冲区拷贝到用户缓冲区。用户应用进程由内核态切换回用户态解除阻塞状态可以发现DMA做的事情很清晰啦它主要就是帮忙CPU转发一下IO请求以及拷贝数据。为什么需要它的主要就是效率它帮忙CPU做事情这时候CPU就可以闲下来去做别的事情提高了CPU的利用效率。大白话解释就是CPU老哥太忙太累啦所以他找了个小弟名叫DMA 替他完成一部分的拷贝工作这样CPU老哥就能着手去做其他事情。4. 零拷贝实现的几种方式 零拷贝并不是没有拷贝数据而是减少用户态/内核态的切换次数以及CPU拷贝的次数。零拷贝实现有多种方式分别是mmapwritesendfile带有DMA收集拷贝功能的sendfile4.1 mmapwrite实现的零拷贝mmap 的函数原型如下void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);addr指定映射的虚拟内存地址length映射的长度prot映射内存的保护模式flags指定映射的类型fd:进行映射的文件句柄offset:文件偏移量前面一小节零拷贝相关的知识点回顾我们介绍了虚拟内存可以把内核空间和用户空间的虚拟地址映射到同一个物理地址从而减少数据拷贝次数mmap就是用了虚拟内存这个特点它将内核中的读缓冲区与用户空间的缓冲区进行映射所有的IO都在内核中完成。mmapwrite实现的零拷贝流程如下用户进程通过mmap方法向操作系统内核发起IO调用上下文从用户态切换为内核态。CPU利用DMA控制器把数据从硬盘中拷贝到内核缓冲区。上下文从内核态切换回用户态mmap方法返回。用户进程通过write方法向操作系统内核发起IO调用上下文从用户态切换为内核态。CPU将内核缓冲区的数据拷贝到的socket缓冲区。CPU利用DMA控制器把数据从socket缓冲区拷贝到网卡上下文从内核态切换回用户态write调用返回。可以发现mmapwrite实现的零拷贝I/O发生了4次用户空间与内核空间的上下文切换以及3次数据拷贝。其中3次数据拷贝中包括了2次DMA拷贝和1次CPU拷贝。mmap是将读缓冲区的地址和用户缓冲区的地址进行映射内核缓冲区和应用缓冲区共享所以节省了一次CPU拷贝‘’并且用户进程内存是虚拟的只是映射到内核的读缓冲区可以节省一半的内存空间。4.2 sendfile实现的零拷贝sendfile是Linux2.1内核版本后引入的一个系统调用函数API如下ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);out_fd:为待写入内容的文件描述符一个socket描述符。in_fd:为待读出内容的文件描述符必须是真实的文件不能是socket和管道。offset指定从读入文件的哪个位置开始读如果为NULL表示文件的默认起始位置。count指定在fdout和fdin之间传输的字节数。sendfile表示在两个文件描述符之间传输数据它是在操作系统内核中操作的避免了数据从内核缓冲区和用户缓冲区之间的拷贝操作因此可以使用它来实现零拷贝。sendfile实现的零拷贝流程如下sendfile实现的零拷贝用户进程发起sendfile系统调用上下文切换1从用户态转向内核态DMA控制器把数据从硬盘中拷贝到内核缓冲区。CPU将读缓冲区中数据拷贝到socket缓冲区DMA控制器异步把数据从socket缓冲区拷贝到网卡上下文切换2从内核态切换回用户态sendfile调用返回。可以发现sendfile实现的零拷贝I/O发生了2次用户空间与内核空间的上下文切换以及3次数据拷贝。其中3次数据拷贝中包括了2次DMA拷贝和1次CPU拷贝。那能不能把CPU拷贝的次数减少到0次呢有的即带有DMA收集拷贝功能的sendfile4.3 sendfileDMA scatter/gather实现的零拷贝linux 2.4版本之后对sendfile做了优化升级引入SG-DMA技术其实就是对DMA拷贝加入了scatter/gather操作它可以直接从内核空间缓冲区中将数据读取到网卡。使用这个特点搞零拷贝即还可以多省去一次CPU拷贝。sendfileDMA scatter/gather实现的零拷贝流程如下用户进程发起sendfile系统调用上下文切换1从用户态转向内核态DMA控制器把数据从硬盘中拷贝到内核缓冲区。CPU把内核缓冲区中的文件描述符信息包括内核缓冲区的内存地址和偏移量发送到socket缓冲区DMA控制器根据文件描述符信息直接把数据从内核缓冲区拷贝到网卡上下文切换2从内核态切换回用户态sendfile调用返回。可以发现sendfileDMA scatter/gather实现的零拷贝I/O发生了2次用户空间与内核空间的上下文切换以及2次数据拷贝。其中2次数据拷贝都是包DMA拷贝。这就是真正的 零拷贝Zero-copy) 技术全程都没有通过CPU来搬运数据所有的数据都是通过DMA来进行传输的。5. java提供的零拷贝方式 Java NIO对mmap的支持Java NIO对sendfile的支持5.1 Java NIO对mmap的支持Java NIO有一个MappedByteBuffer的类可以用来实现内存映射。它的底层是调用了Linux内核的mmap的API。mmap的小demo如下public class MmapTest {public static void main(String[] args) {try {FileChannel readChannel FileChannel.open(Paths.get(./jay.txt), StandardOpenOption.READ);MappedByteBuffer data readChannel.map(FileChannel.MapMode.READ_ONLY, 0, 1024 * 1024 * 40);FileChannel writeChannel FileChannel.open(Paths.get(./siting.txt), StandardOpenOption.WRITE, StandardOpenOption.CREATE);//数据传输writeChannel.write(data);readChannel.close();writeChannel.close();}catch (Exception e){System.out.println(e.getMessage());}}
}5.2 Java NIO对sendfile的支持FileChannel的transferTo()/transferFrom()底层就是sendfile() 系统调用函数。Kafka 这个开源项目就用到它平时面试的时候回答面试官为什么这么快就可以提到零拷贝sendfile这个点。Override
public long transferFrom(FileChannel fileChannel, long position, long count) throws IOException {return fileChannel.transferTo(position, count, socketChannel);
}sendfile的小demo如下public class SendFileTest {public static void main(String[] args) {try {FileChannel readChannel FileChannel.open(Paths.get(./jay.txt), StandardOpenOption.READ);long len readChannel.size();long position readChannel.position();FileChannel writeChannel FileChannel.open(Paths.get(./siting.txt), StandardOpenOption.WRITE, StandardOpenOption.CREATE);//数据传输readChannel.transferTo(position, len, writeChannel);readChannel.close();writeChannel.close();} catch (Exception e) {System.out.println(e.getMessage());}}
}参考与感谢 框架篇小白也能秒懂的Linux零拷贝原理[1]深入剖析Linux IO原理和几种零拷贝机制的实现[2]阿里二面什么是mmap[1]框架篇小白也能秒懂的Linux零拷贝原理: https://juejin.cn/post/6887469050515947528[2]深入剖析Linux IO原理和几种零拷贝机制的实现: https://juejin.cn/post/6844903949359644680#heading-11求点赞、在看、分享