零拷贝:CPU 参与数据拷贝的操作
[知乎] 深入剖析Linux IO原理和几种零拷贝机制的实现

CPU 浪费在数据拷贝操作:

  • 占用 CPU 时间
  • 线程上下文切换 context switch

image.png
解决:

  1. CPU 不参与数据的拷贝工作:零

Producer 和 Consumer 之间数据的流动,需要多少次拷贝?
比如:Java 读取文件并通过网卡发送

  • 磁盘 -> 磁盘内核缓冲区:FileInputStream.read()
  • 磁盘内核缓冲区 -> JVM byte[](用户空间):CPU 参与
  • JVM byte[] -> Socket 内核缓冲区:CPU 参与
  • Socket 内核缓冲区 -> 网卡

Linux 的 IO

Linux 系统中,要从硬盘读取数据到内存中的方法:

  • 轮询
    • CPU 依次从磁盘读取数据
  • CPU 中断
    • CPU 下达读取命令,然后挂起 read 进程
    • 当硬盘将部分数据写入内存后,使用硬件中断触发
    • CPU 查询中断向量表,执行对应的程序,并唤醒 read 进程,读取下一份数据
  • DMA 控制器,Direct Memory Access
    • 独立的处理器,只能执行简单的数据操作
    • CPU 直接命令 DMA 去执行全部数据的拷贝

使用 read() 和 write() 系统调用,会使得用户进程从用户态切换成内核态:

零拷贝 - 图2

Linux 的系统调用:

mmap:

  • 将用户空间的内存与内核空间的内存映射起来

零拷贝 - 图3

sendfile:

  • 直接把数据在内核空间拷贝,减少上下文切换
  • 但用户无法修改数据
  • 需要 CPU 参与拷贝工作

零拷贝 - 图4

senfile + DMA Gather Copy:

  • 需要硬件支持
  • 只适用于将数据从文件拷贝到 socket 套接字上的传输过程。

零拷贝 - 图5

splice:

  • 在两个内存缓冲区直接建立管道,从而避免两者之间的 CPU 拷贝工作

零拷贝 - 图6

Java NIO 中的零拷贝:

  • MappedByteBuffer
    • JDK1.7 引入了 ByteBuffer,因为 byte[] 受 JVM 管制,一定是存在于用户空间的
    • ByteBuffer 可以映射到堆外内存
  • FileChannel.transferTo
    • sendfile + DMA Gather Copy

MQ 中使用的零拷贝:

TODO 多思考一下
RocketMQ:

  • mmap + write
  • 优点:适用于小文件传输,频繁调用时,效率高
  • 缺点:DMA 利用少,会比 sendfile 多消耗 CPU,内存安全性控制复杂,需要避免 JVM Crash 问题

Kafka:索引文件和数据文件应该是使用不同的系统调用

  • sendfile
  • 优点:利用 DMA 减少 CPU 消耗,大文件传输效率高,无内存安全性问题
  • 缺点:小文件效率低于 mmap,只能是 BIO 方式传输,不能使用 NIO 方式