0

我有一个关于动态内存分配的低级问题。我知道可能有不同的实现,但我需要了解基本思想。

所以,

当现代操作系统内存分配器或等效分配器分配一块内存时,需要释放该块。

但是,在此之前,需要存在一些系统来控制分配过程。

我需要知道:

  • 该系统如何跟踪已分配和未分配的内存。我的意思是,系统需要知道哪些块已经被分配以及它们的大小是多少,以便在分配和释放过程中使用这些信息。

现代硬件是否支持此过程,例如分配位或类似的东西?或者是某种用于存储分配信息的数据结构。如果存在数据结构,与分配的内存相比,它使用了多少内存?

以大块而不是小块分配内存是否更好,为什么?

任何可以帮助揭示基本实现细节的答案都值得赞赏。

如果需要代码示例,C 或 C++ 就可以了。

4

3 回答 3

2

“这个系统如何跟踪已分配和未分配的内存。” 对于具有操作系统的非嵌入式系统,操作系统负责组织的虚拟页表(当然还有硬件TLB支持)跟踪程序的内存使用情况。

据我所知(如果我弄错了,社区肯定会对我大喊大叫),跟踪个体malloc()大小和位置有很多实现,并且依赖于运行时库。一般来说,每当您调用malloc()时,大小和位置都会存储在一个表中。每当您调用free()时,都会查找所提供指针的表条目。如果找到,则删除该条目。如果未找到,free()则忽略 (这也表明可能存在内存泄漏)。

malloc()虚拟页面中的所有条目都被释放时,该虚拟页面随后被释放回操作系统(这也意味着free()并不总是将内存释放回操作系统,因为虚拟页面中可能仍有其他malloc()条目)。如果给定的虚拟页面中没有足够的空间来支持另一个malloc()指定大小的页面,则从操作系统请求另一个虚拟页面。

嵌入式处理器通常没有操作系统、虚拟页表,也没有多个进程。在这种情况下,不使用虚拟内存。相反,嵌入式处理器的整个内存被视为一个大的虚拟页面(尽管地址实际上是物理地址),并且内存管理遵循与前面描述的类似过程。

是一个类似的堆栈溢出问题,有更深入的答案。

“以大块而不是小块分配内存更好吗?为什么?” 根据需要分配尽可能多的内存,不多也不少。编译器优化非常聪明,内存管理几乎总是比程序员手动操作更有效(即减少内存碎片)。在非嵌入式环境中尤其如此。

是一个类似的堆栈溢出问题,并提供了更深入的答案(请注意,它与 C 而不是 C++ 相关,但它仍然与此讨论相关)。

于 2013-06-03T17:30:01.553 回答
1

通常存在两个不同的层。

一层位于应用程序级别,通常作为 C 标准库的一部分。这就是您通过mallocfree(或operator new在 C++ 中通常调用malloc)之类的函数所调用的内容。该层负责您的分配,但不知道内存或它来自哪里。

在操作系统级别的另一层不知道也不关心您的分配。它只维护一个已保留、分配和访问的固定大小内存页面的列表,以及每个页面的信息,例如它映射到的位置。

每一层都有许多不同的实现,但一般来说它是这样工作的:
当你分配内存时,分配器(“应用程序级部分”)会查看它是否在它的书的某个地方有一个匹配的块,它可以给你(如果需要,一些分配器会将更大的块分成两部分)。

如果它没有找到合适的块,它会从操作系统中保留一个新的块(通常比你要求的大得多)sbrkmmap在 Linux 上或VirtualAlloc在 Windows 上将是它可能用于该效果的函数的典型示例。
除了向操作系统显示意图和生成一些页表条目之外,这几乎没有什么作用。
然后分配器(从逻辑上讲,在它的书中)根据其正常操作模式将该大区域分成较小的块,找到合适的块并将其返回给您。请注意,此返回的内存甚至不一定作为物理内存存在(尽管大多数分配器将一些元数据写入每个已分配单元的前几个字节,因此它们必须预先故障页)。

与此同时,后台任务会在不可见的情况下将某个进程曾经使用但已被释放的内存页面清零。这种情况一直在发生,只是暂时的,因为迟早会有人请求内存(通常,这就是空闲任务所做的)。

一旦您第一次访问包含已分配块的页面中的地址,就会产生错误。这个尚不存在的页(它在逻辑上存在,只是在物理上不存在)的页表条目被替换为对零页池中的页的引用。在没有剩余的罕见情况下,例如,如果一直在分配大量内存,则操作系统会换出它认为不会很快被访问的页面,将其归零,然后返回该页面。
现在该页面成为您工作集的一部分,它对应于实际的物理内存,并计入您的进程配额。当您的进程正在运行时,页面可能会被移入和移出您的工作集,或者可能会被移出和移入,因为您超出了某些限制,并且根据需要多少内存以及如何访问它。

一旦你调用free,分配器将释放的区域放回它的账簿中。它可能会告诉操作系统它不再需要内存,但通常这不会发生,因为它并不是真正需要的,保留一点额外内存并重用它会更有效。此外,释放内存可能并不容易,因为通常您分配/取消分配的单元与操作系统使用的单元不直接对应(并且,如果sbrk它们需要以正确的顺序发生,也是)。

当进程结束时,操作系统简单地丢弃所有页表条目并将所有页添加到空闲任务将清零的页列表中。因此,物理内存可用于下一个请求的进程。

于 2013-06-03T20:24:51.677 回答
1

好吧,实现这一目标的方法不止一种。我曾经不得不为教育目的编写一个malloc()(和free())实现。

这是根据我的经验,现实世界的实施肯定会有所不同。

我使用了一个双链表。调用后返回给用户的内存块malloc()实际上是一个struct包含与我的实现相关的信息(即nextandprev指针和一个 is_used 字节)。

因此,当用户请求 N 个字节时,我分配了 N + sizeof(my_struct) 个字节,在块的开始处隐藏nextprev指针,并将剩余的内容返回给用户。

当然,对于使用大量小分配的程序来说,这是一个糟糕的设计(因为每个分配占用 N + 2 个指针 + 1 个字节)。

对于现实世界的实现,您可以查看良好且众所周知的内存分配器的代码。

于 2013-06-03T17:09:26.290 回答