【问题标题】:How to deal with external fragmentation, how paging helps with external fragmentation?如何处理外部碎片,分页如何帮助处理外部碎片?
【发布时间】:2017-12-09 14:55:32
【问题描述】:

我知道关于我在这里指出的问题有很多问题,但我找不到任何复杂的答案(无论是在 StackOverflow 上还是在其他来源)。

我想问一下堆(RAM)碎片问题。

据我了解,有两种碎片: internal - 与分配单元大小 (AU) 和分配的内存 AM 大小之间的差异有关(浪费内存等于 AM % AU), external - 与空闲内存的非连续区域有关,因此即使空闲内存区域的总和可以处理新的分配请求,如果没有连续区域则失败可以应付。

这很清楚。当“分页”出现时,问题就开始了。

有时我可以找到分页解决外部碎片问题的信息。 事实上,我同意,由于分页,操作系统能够创建内存的虚拟连续区域,分配给进程,即使内存的各个部分在物理上是分散的。

但它究竟对外部碎片有什么帮助呢? 我的意思是,假设一个页面的大小是 4kB,而我们要分配 16kB,那么我们当然只需要找到四个空的页面框架,即使这些框架在物理上不是连续区域的一部分。

但是如果分配较小呢? 我相信页面本身仍然可能是碎片化的,并且(在最坏的情况下)如果旧框架不能用于分配请求的内存,操作系统仍然需要提供新框架。

那么(假设最坏的情况)迟早,无论有没有分页,分配和释放堆内存(不同大小)的长时间工作的应用程序会因为外部碎片而陷入低内存状态?

那么问题是如何处理外部碎片? 自己实现分配算法?分页(正如我所写,不确定是否有帮助)?还有什么 ?操作系统(Windows、Linux)是否提供了一些碎片整理方法?

最根本的解决方案是禁止使用堆,但对于具有分页、虚拟地址空间、虚拟内存等的平台真的有必要...唯一的问题是应用程序需要不间断地运行数年?

还有一个问题.. 内部碎片是一个模棱两可的术语吗? 我在某处发现了内部碎片指向页面框架部分的定义,这是浪费的,因为进程不需要更多内存,但单个框架不能由多个进程拥有。

我已经把问题加粗了,所以赶时间的人不用阅读所有内容就可以找到问题。

问候!

【问题讨论】:

  • ... even if physically the frames are not a part of a continues area. 物理内存不相关。需要四个连续的未使用地址空间的“页面”。
  • 是的,在进程(虚拟)地址空间中是连续的。但这是由 mmu 组织的,帧的位置在物理上是无关紧要的。

标签: memory-management paging dynamic-memory-allocation fragmentation memory-fragmentation


【解决方案1】:

“碎片化”确实不是一个非常精确的术语。但是我们可以肯定地说,当一个正在运行的应用程序需要一个 n 字节块并且有 n 或更多字节未使用时,我们却无法获得所需的块,那么“内存太碎片化”。

但是 [分页] 究竟如何帮助外部分配 [我假设您的意思是碎片]?

这里真的没有什么复杂的。 外部碎片是分配块之间的空闲内存,“太小”无法满足任何应用程序要求。这是一个普遍的概念。 “太小”的定义取决于应用程序。尽管如此,如果分配的块可以落在任何边界上,那么在多次分配和释放之后,很容易出现大量这样的碎片。分页以两种方式帮助解决外部碎片。

  • 首先,它将内存细分为固定大小的相邻块 - 页面 - “足够大”,因此它们永远不会无用。同样,“足够大”的定义并不准确。但是大多数应用程序都会有很多要求可以通过单个 4k 页面来满足。由于分配页面或更少页面不会出现外部碎片问题,因此该问题已得到缓解。

  • 其次,分页硬件在应用程序页面和物理内存页面之间提供了一定程度的间接性。因此,任何可用的物理内存页面都可以用来帮助满足任何应用程序请求,无论它有多大。例如,假设您有 100 个物理页面,每隔一个物理页面(其中 50 个)分配一次。在没有页面映射硬件的情况下,可以满足的对连续内存的最大请求是 1 页。使用映射,它是 50 页。 (我忽略了最初分配的没有映​​射物理页面的虚拟页面。这是另一个讨论。)

但是如果分配较小呢?

同样,它非常简单。如果分配单位是页,则任何小于页的分配都会产生未使用的部分。这是内部碎片:分配的块中不可用的内存。分配单元越大(它们不必是单个页面),由于内部碎片而无法使用的内存就越多。平均而言,这将趋向于分配单元的一半。因此,尽管操作系统倾向于以页面为单位进行分配,但大多数应用程序端内存分配器向操作系统请求非常少量(通常是一个)大块(页面)。它们在内部使用更小的分配单元:4-16 字节很常见。

所以问题是如何处理外部分配[我假设你的意思是碎片]?那么(假设最坏的情况)是否迟早,无论有没有分页,分配和释放堆内存(不同大小)的长时间工作的应用程序会因为外部碎片而陷入低内存状态?

如果我的理解正确,您是在问碎片化是否不可避免。除非在非常特殊的条件下(例如应用程序只需要一种大小的块),否则答案是肯定的。但这并不意味着它一定是一个问题。

内存分配器使用智能算法非常有效地限制碎片。例如,他们可以维护具有不同块大小的“池”,使用块大小最接近给定请求的池。这往往会限制内部和外部碎片。一个有据可查的真实世界示例是dlmalloc。源码也很清楚。

当然,任何通用分配器都可能在特定条件下失败。出于这个原因,现代语言(我知道 C++ 和 Ada 是两种)让您为给定类型的对象提供特殊用途的分配器。通常 - 对于固定大小的对象 - 这些可能只是维护一个预先全部覆盖的空闲列表,因此该特定情况的碎片为零,并且分配/解除分配非常快。

另一个注意事项:使用copying/compacting garbage collection 可以完全消除碎片。当然,这需要底层语言支持,并且需要支付性能费用。复制垃圾收集器通过移动对象来压缩堆,以在运行以回收存储时完全消除未使用的空间。为此,它必须将正在运行的程序中的每个指针更新到相应对象的新位置。虽然这听起来很复杂,但我已经实现了一个复制垃圾收集器,而且还不错。算法非常酷。不幸的是,许多语言(例如 C 和 C++)的语义不允许找到正在运行的程序中的每个指针,这是必需的。

最根本的解决方案是禁止使用堆,但是对于具有分页、虚拟地址空间、虚拟内存等的平台真的有必要吗……唯一的问题是应用程序需要在一段时间内不间断地运行年?

虽然通用分配器很好,但不能保证。对于安全关键或硬实时受限的系统来说,完全避免堆使用并不罕见。另一方面,当不需要绝对保证时,通用分配器通常就可以了。有许多系统可以使用通用分配器长时间在高负载下完美运行:碎片达到可接受的稳定状态并且不会导致问题。

还有一个问题.. 内部碎片化是一个模棱两可的术语吗?

该术语没有歧义,但用于不同的上下文。不变的是它指的是分配块内未使用的内存。

OS 文献倾向于假设分配单元是页。例如,Linux sbrk 允许您请求在任何地方设置数据段的结尾,但 Linux 分配的是页面,而不是字节,因此从操作系统的角度来看,最后一页的未使用部分是内部碎片。

面向应用的讨论倾向于假设分配是在任意大小的“块”或“块”中。 dlmalloc 使用大约 128 个离散的块大小,每个块都保存在自己的空闲列表中。 另外,它将使用操作系统内存映射系统调用自定义分配非常大的块,因此请求和实际分配之间最多有一个页面大小(减去 1 个字节)不匹配。显然,尽量减少内部碎片会带来很多麻烦。给定分配造成的碎片是请求和实际分配的块之间的差异。由于块大小如此之多,因此这种差异受到严格限制。另一方面,许多块大小增加了外部碎片问题的可能性:空闲内存可能完全由 dlmalloc 管理良好的块组成,但太小而无法满足应用程序的要求。

【讨论】:

  • 嗨 Gene,非常感谢您的回答。还有一点我不明白。正如您所写:“它们在内部使用更小的分配单元:4-16 字节很常见。”。假设应用程序分配大小小于一页的块,并且默认内存分配器的分配单元大小也小于一页(因此可以在单个页内完成多次分配),经过一段时间的分配和释放,由操作系统提供的单个页面可以进行碎片整理,以至于应用程序分配器会向操作系统请求新页面?
  • @user1770426 是的。您可以在 dlmalloc 中看到这种情况。所有块列表都已经“合并”。也就是说,每个块都以当前分配的内存为界。如果一切都被释放,那么整个竞技场将成为一个列表中的一个块。因此,如果请求到达并且没有足够大的块,它会调用操作系统以获得更多。另请注意,当我在上面说“应用程序”时,我包括malloc 和类似的默认分配器,因为它在应用程序线程中运行(通常不会的副操作系统分配)。根据我的经验,很少有应用程序自己实现自定义分配器。
  • 我查过了。 dlmalloc 的最小块是(使用最常见的 C 数据类型约定)在 32 位机器上为 16 字节,在 64 位机器上为 32 字节
  • 感谢基因!我肯定得到了所有的答案:-)
猜你喜欢
  • 2015-12-21
  • 1970-01-01
  • 1970-01-01
  • 2013-04-20
  • 2012-11-19
  • 2017-06-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多