1. 内存映射

Linux 通过将虚拟内存区域与磁盘上的对象关联起来,来初始化这个虚拟内存区域的内容,这个过程称为内存映射。虚拟内存区域可以映射到两种类型的对象中的一种:

  • Linux 文件系统中的普通文件:磁盘文件的连续部分。
  • 匿名文件:由内核创建,包含的都是二进制零。

一旦虚拟页面被初始化,它就在由内核维护的交换空间之间换来换去。在任何时刻,交换空间都限制当前运行的进程能够分配的虚拟页面的总数。

一个对象可以被映射到虚拟内存的一个区域,要么作为共享对象,要么作为私有对象。进程对共享对象映射到的虚拟地址空间的写操作,对把该共享对象映射到它们虚拟内存区域的其他进程也是可见的,并且这些变化反映在磁盘上的对象中。另一方面,对于映射到私有对象的区域的改变,对其他进程来说是不可见的,并且不会反映在磁盘上的对象中。

许多进程有同样的只读代码区域,内存映射可以控制多个进程如何共享对象。即使对象被映射到多个共享区域,物理内存中只需要存放共享对象的一个副本。

私有对象使用写时复制(copy-on-write)的技术被映射到虚拟内存中。和共享对象一样,在物理内存中只保存私有对象的一个副本。当一个进程试图写私有区域内的某个页面时,写操作就会触发一个保护故障。故障处理程序会在物理内存中创建这个页面的新副本,更新页表条目指向新副本,恢复这个页面的写权限。当故障处理程序返回时,CPU 就可以正常执行写操作了。

2. 动态内存分配

当运行时需要额外虚拟内存时,用动态内存分配器更方便,也有更好的可移植性。

动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。

分配器有两种基本风格,两种风格都要求应用显式地分配块,它们的不同之处在于由哪个实体负责释放已分配的块。

  • 显式分配器要求应用显式地释放任何已分配的块。例如 C 通过调用 malloc 和 free 分配和释放块。
  • 隐时分配器会自动释放不再使用的已分配的块。例如 Java 的垃圾回收。

造成堆利用率很低的主要原因是碎片的现象,当有未使用的内存但不能用来满足分配请求时,就发生这种现象。分配器通常采用试图维持少量的大空闲块,而不是维持大量的小空闲块。