通常存在两个不同的层。
一层位于应用程序级别,通常作为 C 标准库的一部分。这就是您通过malloc
和free
(或operator new
在 C++ 中通常调用malloc
)之类的函数所调用的内容。该层负责您的分配,但不知道内存或它来自哪里。
在操作系统级别的另一层不知道也不关心您的分配。它只维护一个已保留、分配和访问的固定大小内存页面的列表,以及每个页面的信息,例如它映射到的位置。
每一层都有许多不同的实现,但一般来说它是这样工作的:
当你分配内存时,分配器(“应用程序级部分”)会查看它是否在它的书的某个地方有一个匹配的块,它可以给你(如果需要,一些分配器会将更大的块分成两部分)。
如果它没有找到合适的块,它会从操作系统中保留一个新的块(通常比你要求的大得多)。sbrk
或mmap
在 Linux 上或VirtualAlloc
在 Windows 上将是它可能用于该效果的函数的典型示例。
除了向操作系统显示意图和生成一些页表条目之外,这几乎没有什么作用。
然后分配器(从逻辑上讲,在它的书中)根据其正常操作模式将该大区域分成较小的块,找到合适的块并将其返回给您。请注意,此返回的内存甚至不一定作为物理内存存在(尽管大多数分配器将一些元数据写入每个已分配单元的前几个字节,因此它们必须预先故障页)。
与此同时,后台任务会在不可见的情况下将某个进程曾经使用但已被释放的内存页面清零。这种情况一直在发生,只是暂时的,因为迟早会有人请求内存(通常,这就是空闲任务所做的)。
一旦您第一次访问包含已分配块的页面中的地址,就会产生错误。这个尚不存在的页(它在逻辑上存在,只是在物理上不存在)的页表条目被替换为对零页池中的页的引用。在没有剩余的罕见情况下,例如,如果一直在分配大量内存,则操作系统会换出它认为不会很快被访问的页面,将其归零,然后返回该页面。
现在该页面成为您工作集的一部分,它对应于实际的物理内存,并计入您的进程配额。当您的进程正在运行时,页面可能会被移入和移出您的工作集,或者可能会被移出和移入,因为您超出了某些限制,并且根据需要多少内存以及如何访问它。
一旦你调用free
,分配器将释放的区域放回它的账簿中。它可能会告诉操作系统它不再需要内存,但通常这不会发生,因为它并不是真正需要的,保留一点额外内存并重用它会更有效。此外,释放内存可能并不容易,因为通常您分配/取消分配的单元与操作系统使用的单元不直接对应(并且,如果sbrk
它们需要以正确的顺序发生,也是)。
当进程结束时,操作系统简单地丢弃所有页表条目并将所有页添加到空闲任务将清零的页列表中。因此,物理内存可用于下一个请求的进程。