一,相关概念

用户态与内核态:Linux操作系统体系架构分为用户态与内核态。内核主要控制计算机的硬件资源,为上层应用提供运行支持。用户态为上层应用的活动空间,应用程序的执行需要内核的支持,如CPU资源,存储资源,IO资源等,用户态通过内核提供的访问接口也就是系统调用来使用这些资源。
直接内存访问DMA:DMA允许外设与内存存储器之间直接进行IO数据传输,其过程不需要CPU参与
虚拟内存:计算机内存管理的一种技术手段,它可以是连续的也可以是非连续的内存,也可以是磁盘上的空间。虚拟内存的两个特点:一是多个虚拟地址可以指向同一个物理内存地址;二是虚拟内存空间可大于实际可用的物理地址。虚拟内存的第一个特点可以将内核空间地址与用户空间地址映射到同一个物理地址,这样DMA就可以操作内核和用户空间进程同时可见的缓冲区了。
零拷贝:不将数据从一个存储区域拷贝到另一个区域。当然零拷贝不是真正的不拷贝数据,而是借助一些手段尽量减少数据的拷贝(数据不用在用户空间与内核空间来回拷贝)从而释放CPU,减少上下文切换,减少内存的占用,以达到提升程序性能的目的。
内存映射:Linux提供的mmap系统调用,它可以将一段用户内存映射到内核空间,当映射成功后,用户对这段内存区域的修改可以直接反映到内核空间;同样的,内核空间对这段区域的修改也直接反映到用户空间。正因为这样的映射关系,就不需要在用户态与内核态之间拷贝数据,提高了数据传输的效率。

用户态与内核态示意图:
image.png
虚拟内存示意图:
image.png

二,传统IO实现的文件传输

image.png

  1. 应用程序调用read(),上下文切换到内核,DMA将磁盘数据复制到内核的缓存空间
  2. read()返回,上下文切换到用户态,CPU将数据复制到用户的缓存空间
  3. 应用程序调用write(),上下文再次切换到内核,CPU将数据复制到内核socket缓存
  4. write()返回,上下文再次切换到用户态,DMA将socket缓存数据复制到网卡缓存上
    这里一共出现了4次上下文切换,4次数据拷贝

三,mmap(内存映射)+write实现的零拷贝

image.png

  1. 应用程序发出mmap系统调用,用户态切换到内核态,发生一次上下文切换,然后通过DMA将磁盘文件中的数据复制到内核空间缓冲区
  2. mmap系统调用返回,内核空间切换到用户态,再发生一次上下文切换。但是不需要将数据从内核空间拷贝到用户空间,因为内核空间与用户空间共享这个缓冲区
  3. 发出write系统调用后,用户态再次切换到内核态,数据将从内核空间缓冲区复制到内核空间的socket缓冲区
  4. write系统调用返回,内核态切换到用户空间,DMA将sokcet缓冲区数据复制到网卡
    这里一共出现4次上下文切换,3次数据拷贝

四,通过sendfile实现的零拷贝

image.png

  1. 发出sendfile系统调用,导致用户空间到内核空间的上下文切换,然后通过DMA引擎将磁盘文件中的内容复制到内核空间缓冲区中,接着再将数据从内核空间缓冲区复制到socket相关的缓冲区
  2. sendfile系统调用返回,导致内核空间到用户空间的上下文切换。DMA异步将内核空间socket缓冲区中的数据传递到网卡
    这里一共出现了2次上下文切换,3次数据拷贝

五,带有DMA收集拷贝功能的sendfile实现的零拷贝
image.png
从Linux 2.4版本开始,操作系统提供scatter和gather的SG-DMA方式,直接从内核空间缓冲区中将数据读取到网卡,无需将内核空间缓冲区的数据再复制一份到socket缓冲区。具体过程如下:

  1. 应用程序发出sendfile调用,用户态切换到内核态,通过DMA将数据复制到内核缓冲区
  2. 与之前sendfile不同的是,这里不再将数据从内核缓冲区复制到socket buffer,而是文件描述符fd(内核缓冲区内存地址与内核缓冲区偏移量地址)复制到socket buffer。
  3. sendfile系统调用返回,内核态切换到用户态,DMA通过内核缓冲区内存地址与偏移量将数据拷贝到网卡。
    这里一共出现了2次上下文切换,2次数据拷贝

六,splice实现的零拷贝
image.png

Linux 在 2.6.17 版本引入 splice 系统调用,不仅不需要硬件支持,还实现了两个文件描述符之间的数据零拷贝。splice 系统调用可以在内核空间的读缓冲区(read buffer)和网络缓冲区(socket buffer)之间建立管道(pipeline),从而避免了两者之间的 CPU 拷贝操作。具体过程如下:

  1. 用户发起splice调用,用户态切换到内核态
  2. CPU利用DMA将数据拷贝到内核缓冲区,并在内核缓冲区与sokcet buffer之间建立起管道pipeline
  3. CPU利用DMA将数据从socket buffer拷贝到网卡
  4. splice调用返回,内核态切换到用户态
    这里一共出现了2次下下文切换,两次数据拷贝
拷贝方式CPU拷贝DMA拷贝系统调用上下文切换
传统方式(read+write)22read/write4
内存映射(mmap+write)12mmap/write4
sendfile12sendfile2
sendfile+DMA gather copy02sendfile2
splice02sendfile2

参考的文章:
零拷贝原理
linux零拷贝原理
高性能IO背后-零拷贝技术
netty零拷贝实现原理
重新认识 Java 中的内存映射(mmap)


步履不停
38 声望13 粉丝

好走的都是下坡路


« 上一篇
redis重新分片