什么是mmap

什么是mmap

Posted by 果果 on July 13, 2022

RocketMQ和Kafka 为什么快?什么是 mmap?

这一类的问题都逃不过的一个点就是零拷贝,虽然还有一些其他的原因,但是今天我们主要讲的就是零拷贝,也叫内存映射(Memory Mapped)。

传统IO

在开始谈零拷贝之前,首先要对传统的 IO 方式有一个概念。

基于传统的 IO 方式,底层实际上通过调用 read() 和 write() 来实现。

通过 read() 把数据从硬盘读取到内核缓冲区,再复制到用户缓冲区;然后再通过 write() 写入到 socket 缓冲区,最后写入网卡设备。

File.read(fileDesc, buf, len);
Socket.send(socket, buf, len);

整个过程发生了 4 次用户态和内核态的上下文切换和 4 次拷贝,具体流程如下:

m1

  1. 用户进程通过 read() 方法向操作系统发起调用,此时上下文从用户态转向内核态;
  2. DMA 控制器把数据从硬盘中拷贝到读缓冲区;
  3. CPU 把读缓冲区数据拷贝到应用缓冲区,上下文从内核态转为用户态,read() 返回;
  4. 用户进程通过 write() 方法发起调用,上下文从用户态转为内核态;
  5. CPU 将应用缓冲区中数据拷贝到 socket 缓冲区;
  6. DMA 控制器把数据从 socket 缓冲区拷贝到网卡,上下文从内核态切换回用户态,write() 返回。

什么是 DMA 拷贝呢?

对于一个 IO 操作而言,都是通过 CPU 发出对应的指令来完成。但是,相比 CPU 来说,IO 的速度太慢了,CPU 有大量的时间处于等待 IO 的状态。

因此就产生了 DMA(Direct Memory Access)直接内存访问技术,本质上来说他就是一块主板上独立的芯片,通过它来进行内存和 IO 设备的数据传输,从而减少 CPU 的等待时间。

但是无论谁来拷贝,频繁的拷贝耗时也是对性能的影响。

零拷贝

零拷贝技术是指计算机执行操作时,CPU 不需要先将数据从某处内存复制到另一个特定区域,这种技术通常用于通过网络传输文件时节省 CPU 周期和内存带宽。

那么对于零拷贝而言,并非真的是完全没有数据拷贝的过程,只不过是减少用户态和内核态的切换次数以及 CPU 拷贝的次数。

这里,仅仅有针对性的来谈谈几种常见的零拷贝技术。

mmap+write

mmap+write 简单来说就是使用 mmap 替换了 read+write 中的 read 操作,减少了一次 CPU 的拷贝。

mmap 主要实现方式是将读缓冲区的地址和用户缓冲区的地址进行映射,内核缓冲区和应用缓冲区共享,从而减少了从读缓冲区到用户缓冲区的一次CPU拷贝。

m2

整个过程发生了 4 次用户态和内核态的上下文切换和 3 次拷贝,具体流程如下:

  1. 用户进程通过 mmap() 方法向操作系统发起调用,上下文从用户态转向内核态;
  2. DMA 控制器把数据从硬盘中拷贝到读缓冲区;
  3. 上下文从内核态转为用户态,mmap 调用返回;
  4. 用户进程通过 write() 方法发起调用,上下文从用户态转为内核态;
  5. CPU 将读缓冲区中数据拷贝到 socket 缓冲区;
  6. DMA 控制器把数据从 socket 缓冲区拷贝到网卡,上下文从内核态切换回用户态,write() 返回。

mmap 的方式节省了一次 CPU 拷贝,同时由于用户进程中的内存是虚拟的,只是映射到内核的读缓冲区,所以可以节省一半的内存空间,比较适合大文件的传输。

sendfile

相比 mmap 来说,sendfile 同样减少了一次 CPU 拷贝,而且还减少了 2 次上下文切换。

sendfile 是 Linux2.1 内核版本后引入的一个系统调用函数。通过使用 sendfile 数据可以直接在内核空间进行传输,因此避免了用户空间和内核空间的拷贝,同时由于使用 sendfile 替代了 read+write 从而节省了一次系统调用,也就是 2 次上下文切换。

m3

整个过程发生了 2 次用户态和内核态的上下文切换和 3 次拷贝,具体流程如下:

  1. 用户进程通过 sendfile() 方法向操作系统发起调用,上下文从用户态转向内核态;
  2. DMA 控制器把数据从硬盘中拷贝到读缓冲区;
  3. CPU 将读缓冲区中数据拷贝到 socket 缓冲区;
  4. DMA 控制器把数据从 socket 缓冲区拷贝到网卡,上下文从内核态切换回用户态,sendfile 调用返回。

sendfile 方法 IO 数据对用户空间完全不可见,所以只能适用于完全不需要用户空间处理的情况,比如静态文件服务器。

应用场景

Kafka 使用到了零拷贝的技术。

对于 MQ 而言,无非就是生产者发送数据到 MQ 然后持久化到磁盘,之后消费者从 MQ 读取数据。

Kafka 使用 mmap+write 持久化数据,发送数据使用 sendfile。

总结

  • 由于 CPU 和 IO 速度的差异问题,产生了 DMA 技术,通过 DMA 搬运来减少 CPU 的等待时间。
  • 传统的 IOread+write 方式会产生 2 次 DMA 拷贝 + 2 次 CPU 拷贝,同时有 4 次上下文切换。
  • 而通过 mmap+write 方式则产生 2 次 DMA 拷贝 + 1 次 CPU 拷贝,4 次上下文切换,通过内存映射减少了一次 CPU 拷贝,可以减少内存使用,适合大文件的传输。
  • sendfile 方式是新增的一个系统调用函数,产生 2 次 DMA 拷贝 + 1 次 CPU 拷贝,但是只有 2 次上下文切换。因为只有一次调用,减少了上下文的切换,但是用户空间对 IO 数据不可见,适用于静态文件服务器。
  • sendfile+DMA gather 方式产生 2 次 DMA 拷贝,没有 CPU 拷贝,而且也只有2次上下文切换。虽然极大地提升了性能,但是需要依赖新的硬件设备支持。