内存零拷贝科普

什么是内存零拷贝

零拷贝是指计算机执行IO操作时,CPU不需要将数据从一个存储区域复制到另一个存储区域,进而减少上下文切换以及CPU的拷贝时间。它是一种IO操作优化技术。

关键术语

内核态和用户态

内核态:内核空间是提供进程调度、内存分配、连接硬件资源等功能,是被系统保护的空间。进程运行于内核空间就叫做内核态

用户态:用户空间就算提供给各个进程使用的空间,不具备访问内核空间资源的权限,要访问内核空间,需要通过系统调度来完成。进程运行于用户空间就叫做用户态

上下文切换

内存零拷贝科普_第1张图片

上下文:cpu寄存器是cpu内置的容量小、速度极快的内存。程序计数器是存储cpu正在执行的指令位置。他们都是cpu在运行任务前,必须准备好的依赖环境,因此(cpu寄存器的内容和程序计数器的内容)叫做cpu上下文。

上下文切换:先把上一个任务的cpu上下文(cpu寄存器的内容和程序计数器)保存起来,然后加载新任务的上下文到cpu寄存器和程序计数器上,再跳转到程序计数器所指向的位置继续执行任务(现在一般指的是内核态和用户态的切换)

DMA

内存零拷贝科普_第2张图片

DMA全称Direct Memory Access,即直接内存访问
DMA本质是主板的一个独立芯片,允许外部设备和内存存储器进行直接的IO传输,其过程不需要CPU的参与。
也就是说DMA在工作时,cpu是空闲的,可以去干其他事情,从而提升整体的效率

虚拟内存

内存零拷贝科普_第3张图片

虚拟地址取代物理地址,把硬盘当内存

优点:

  1. 虚拟内存空间可以远远大于 物理内存空间
  2. 多个虚拟内存可以指向同一个物理地址

零拷贝可以利用多个虚拟内存可以指向同一个物理地址这个特点,把内核空间和用户空间的虚拟地址映射到同一个物理地址,这样就会减少IO的数据拷贝次数

传统IO

内存零拷贝科普_第4张图片

  1. 应用程序调用read函数,向操作系统发起IO调用,上下文:用户态->内核态
  2. DMA控制器把数据从磁盘中读取到内核缓冲区
  3. CPU把内核缓冲区数据拷贝到用户应用缓冲区,read函数返回,上下文:内核态->用户态
  4. 用户应用进程通过write函数,发起IO调用,上下文:用户态->内核态
  5. CPU将缓冲区的数据拷贝到socket缓冲区
  6. DMA控制器将数据从socket缓冲区拷贝到网卡设备,wirte函数返回,上下文:内核态->用户态

传统IO总共经历了4次上下文切换,4次拷贝(2次CPU拷贝和2次DMA拷贝)

实现零拷贝

零拷贝不是没有拷贝,而是减少用户态和内核态直接的切换以及cpu的拷贝次数

  1. mmap+write
  2. sendfile
  3. 带有DMA收集拷贝功能的sendfile

mmap

mmap用了虚拟内存的特点,将内核中的读缓冲区和用户空间的缓冲区进行映射,所有IO在内核中完成

内存零拷贝科普_第5张图片

  1. 用户进程通过调用mmap方法向操作系统内核发起IO调用,上下文:用户态->内核态
  2. cpu向DMA发起请求,DMA将数据从硬盘拷贝到内核缓冲区
  3. mmap方法返回,上下文:内核态->用户态
  4. 用户进程通过调用write方法向操作系统内核再次发起IO调用,上下文:用户态->内核态
  5. CPU将内核缓冲区的数据拷贝到socket缓冲区
  6. CPU向DMA发起请求,DMA将数据从socket缓冲区拷贝到网卡,wirte方法返回,上下文:内核态->用户态

mmap总共经历了4次上下文切换,3次拷贝(1次CPU拷贝和2次DMA拷贝)

sendfile

sendfile是linux2.1内核引入的一个系统调用函数,表示两个文件描述符直接传输数据,在操作系统的内核中操作,避免数据在内核缓冲区和用户缓冲区之间的拷贝,实现零拷贝操作

内存零拷贝科普_第6张图片

  1. 用户进程发起sendfile系统调用,上下文:用户态->内核态
  2. DMA控制器将数据从硬盘拷贝到内核缓冲区
  3. CPU将读缓存区中得数据拷贝到socket缓冲区
  4. DMA控制器异步将数据从socket缓冲区拷贝到网卡
  5. sendfile函数返回,上下文:内核态->用户态

sendfile总共经历了2次上下文切换以及3次拷贝(2次DMA拷贝+1次CPU拷贝)

sendfile+DMA scatter/gather

linux2.4版本后,对sendfile进行优化,引入SG-DMA,运用scatter/gather操作,他可以直接从内核空间缓冲区将数据读取到网卡,省去cpu拷贝到socket缓冲区

内存零拷贝科普_第7张图片

  1. 用户进程发起sendfile系统调用,上下文:用户态->内核态
  2. DMA控制器将数据从硬盘拷贝到内核缓冲区
  3. CPU把内核缓冲区中得文件描述符信息(包括内核缓冲区得内存地址和偏移量)直接发送到socket缓冲区
  4. DMA控制器根据文件描述符信息直接把数据从内核缓冲区拷贝到网卡
    1. sendfile函数返回,上下文:内核态->用户态

sendfile+DMA scatter/gather总共经历了2次上下文切换以及2次拷贝(2次DMA拷贝)(真.零拷贝),全程没有cpu参与拷贝数据

总结

实现方式有下面几种

1. mmap
2. sendfile
3. 升级版sendfile(DMA scatter/gather)

在java中,mmap最经典就是NIO用MappedByteBuffer进行实现内存映射

sendfile可以用FileChannel中得transferTo()或者transferFrom(),底层就是用sendfile(),kafka实现零拷贝也是用sendfile

附加

kafka零拷贝

kafka底层就是调用sendfile,拷贝就是零拷贝,所以快

netty零拷贝

  1. netty使用butebuffer(采用directbuffer),使用堆外直接内存进行socket读写,不需要进行字节缓冲区得二次拷贝
  2. netty使用Composite Buffers(组合buffer),能够聚合多个bytebuffer对象(保存多个buffer得引用,避免拷贝数据),将对各小buffer合并成大buffer
  3. 文件传输采用transferTo得方法(底层是sendfile)

是不是零拷贝最好

答案是:不是

PageCache

关键的技术是PageCache(磁盘高速缓存,内核缓冲区),PageCache的功能

  1. 缓存最近被访问的数据
  2. 预读能力(局部性原理)

如果大文件也用PageCache,那PageCache就会被大文件占据,导致热点小文件无法利用,反而降低性能。PageCache的功能也大受影响,所以大文件拷贝应该绕过PageCache

直接IO

应用程序直接访问磁盘,直接绕过PageCache(内核缓冲区),减少一次内核缓冲区到应用程序的复制

异步IO

主要是解决进程阻塞问题,异步IO没有涉及PageCache

分情况

所以对于文件传输要分情况:

  1. 传输大文件时,使用异步IO+直接IO(使得大文件可以非阻塞进行操作)
  2. 传输小文件时,使用零拷贝技术

你可能感兴趣的:(java)