如何处理外部碎片,分页如何帮助外部碎片?

use*_*426 1 paging memory-management fragmentation dynamic-memory-allocation memory-fragmentation

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

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

据我所知,有两种碎片: 内部- 与分配单元大小(AU)和分配内存大小 AM 之间的差异有关(浪费内存等于 AM % AU), 外部- 与空闲的非连续区域有关内存,所以即使空闲内存区域的总和可以处理新的分配请求,如果没有可以处理的继续区域,它也会失败。

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

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

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

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

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

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

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

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

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

问候!

Gen*_*ene 5

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

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

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

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

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

但是如果分配较小呢?

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

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

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

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

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

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

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

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

还有一个问题……内部碎片化是一个含糊不清的术语吗?

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

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

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

  • 我检查了。dlmalloc 的最小块(使用最常见的 C 数据类型约定)在 32 位机器上为 16 字节,在 64 位机器上为 32 字节 (2认同)