Linux mmap 技术

Posted by Yano on September 23, 2021

虚拟内存:计算机系统的内存管理

详情可以参考文章:Linux 虚拟内存

虚拟内存是什么?

计算机系统管理内存的技术,在应用程序的角度看,应用程序拥有连续可用的内存(一个连续的内存地址空间)。但是实际上会被分隔成多个物理内存碎片,还有部分可能存储在外部磁盘上,在必要的时候进行数据交换。

虚拟内存是操作系统物理内存和进程之间的中间层,为进程隐藏了物理内存。

虚拟内存的好处?

  • 为进程提供独立的内存空间,简化程序的链接、加载过程并通过动态库共享内存;
  • 控制进程对物理内存的访问,隔离不同进程的访问权限,提高系统的安全性;
  • 以内存作为缓存,提高进程访问磁盘的速度。

内存管理

在 64 位的操作系统上,每个进程都会拥有 256 TiB 的内存空间,内核空间和用户空间分别占 128 TiB。因为每个进程的虚拟内存空间是完全独立的,所以它们都可以完整的使用 0x0000000000000000 到 0x00007FFFFFFFFFFF 的全部内存。

虚拟内存被分为用户空间内核空间两部分:进程在用户态时,只能访问用户空间;进入内核态,才能访问内核空间。我们看一下 32 位地址和 64 位地址空间的分布:

传统 IO 读写过程

初学 Java 时,我们在学习 IO 和 网络编程时,会使用以下代码:

File file = new File("index.html");
RandomAccessFile raf = new RandomAccessFile(file, "rw");
 
byte[] arr = new byte[(int) file.length()];
raf.read(arr);
 
Socket socket = new ServerSocket(8080).accept();
socket.getOutputStream().write(arr);

我们会调用 read 方法读取 index.html 的内容—— 变成字节数组,然后调用 write 方法,将 index.html 字节流写到 socket 中,那么,我们调用这两个方法,在 OS 底层发生了什么呢?我这里借鉴了一张其他文章的图片,尝试解释这个过程。

上图中,上半部分表示用户态和内核态的上下文切换。下半部分表示数据复制操作。下面说说他们的步骤:

  • read 调用导致用户态到内核态的一次变化,同时,第一次复制开始:DMA(Direct Memory Access,直接内存存取,即不使用 CPU 拷贝数据到内存,而是 DMA 引擎传输数据到内存,用于解放 CPU) 引擎从磁盘读取 index.html 文件,并将数据放入到内核缓冲区。
  • 发生第二次数据拷贝,即:将内核缓冲区的数据拷贝到用户缓冲区,同时,发生了一次用内核态到用户态的上下文切换。
  • 发生第三次数据拷贝,我们调用 write 方法,系统将用户缓冲区的数据拷贝到 Socket 缓冲区。此时,又发生了一次用户态到内核态的上下文切换。
  • 第四次拷贝,数据异步的从 Socket 缓冲区,使用 DMA 引擎拷贝到网络协议引擎。这一段,不需要进行上下文切换。
  • write 方法返回,再次从内核态切换到用户态。

mmap 简介

参考 mmap 维基百科,mmap 通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内核空间的数据。这样,在进行网络传输时,就可以减少内核空间到用户空间的拷贝次数。

如上图,user buffer 和 kernel buffer 共享 index.html。如果你想把硬盘的 index.html 传输到网络中,再也不用拷贝到用户空间,再从用户空间拷贝到 Socket 缓冲区。

现在,你只需要从内核缓冲区拷贝到 Socket 缓冲区即可,这将减少一次内存拷贝(从 4 次变成了 3 次),但不减少上下文切换次数。

Java 中的 mmap:MappedByteBuffer

参考文章

GitHub 项目

Java 编程思想-最全思维导图-GitHub 下载链接,需要的小伙伴可以自取~

原创不易,希望大家转载时请先联系我,并标注原文链接。