6

我正在编写一个内核并且需要(并且想要)将多个堆栈和堆放入虚拟内存中,但我无法弄清楚如何有效地放置它们。普通程序是如何做到的?

堆栈和堆是如何(或在哪里)放置到 32 位系统提供的有限虚拟内存中的,以便它们拥有尽可能多的增长空间?

例如,当一个普通程序加载到内存中时,其地址空间的布局可能如下所示:

[  Code  Data  BSS  Heap->  ...  <-Stack  ]

在这种情况下,堆可以增长到虚拟内存允许的大小(例如,直到堆栈),我相信这就是堆对大多数程序的工作方式。没有预定义的上限。

许多程序都有共享库,它们放在虚拟地址空间的某个地方。然后是具有多个堆栈的多线程程序,每个线程一个堆栈。.NET 程序有多个堆,所有堆都必须能够以一种或另一种方式增长。

如果不对所有堆和堆栈的大小设置预定义限制,我只是看不出这是如何合理有效地完成的。

4

2 回答 2

3

我假设你已经完成了内核中的基础知识,一个用于页面错误的陷阱处理程序,可以将虚拟内存页面映射到 RAM。再上一层,您需要一个虚拟内存地址空间管理器,用户模式代码可以从中请求地址空间。选择一个可以防止过多碎片的段粒度,64KB(16 页)是一个不错的数字。允许用户模式代码保留空间和提交空间。用于跟踪段状态的 4GB/64KB = 64K x 2 位的简单位图可以完成工作。页面错误陷阱处理程序还需要查阅此位图以了解页面请求是否有效。

堆栈是固定大小的 VM 分配,通常为 1 兆字节。一个线程通常只需要少数几页,具体取决于函数嵌套级别,因此保留 1MB 并仅提交前几页。当线程嵌套更深时,它将触发页面错误,内核可以简单地将额外的页面映射到 RAM 以允许线程继续。您需要将底部的几页标记为特殊页面,当这些页面出现错误时,您声明该网站的名称。

堆管理器最重要的工作是防止碎片。最好的方法是创建一个按大小对堆请求进行分区的后备列表。小于 8 字节的所有内容都来自第一个段列表。从第二个到 8 到 16,从第三个到 16 到 32,等等。随着你的上升,增加桶的大小。您必须使用存储桶大小来获得最佳平衡。非常大的分配直接来自 VM 地址管理器。

第一次命中后备列表中的条目时,您分配一个新的 VM 段。您使用链表将段细分为更小的块。当这样的分配被释放时,您将该块添加到空闲块列表中。无论程序请求如何,所有块都具有相同的大小,因此不会有任何碎片。当段被完全使用并且没有空闲块可用时,您分配一个新段。当一个段只包含空闲块时,您可以将其返回给 VM 管理器。

此方案允许您创建任意数量的堆栈和堆。

于 2013-04-30T01:20:16.930 回答
0

简而言之,由于您的系统资源始终是有限的,因此您不可能无限。

内存管理总是由几个层组成,每个层都有其明确定义的职责。从程序的角度来看,应用程序级管理器是可见的,它通常只关心它自己的单个分配堆。如果需要,上一层可以处理从(它的)一个全局堆中创建多个堆并将它们分配给子程序(每个子程序都有自己的内存管理器)。高于它可能是它使用的标准malloc()/free()高于那些处理页面和每个进程的实际内存分配的操作系统(它基本上不仅关心多个堆,而且通常甚至不关心用户级堆)。

内存管理成本很高,陷入内核也是如此。将两者结合起来可能会对性能造成严重影响,因此从应用程序的角度来看,实际的堆管理实际上是在用户空间(C 运行时库)中实现的,以提高性能(以及其他暂时超出范围的原因) )。

在加载共享(DLL)库时,如果它是在程序启动时加载的,那么它当然很可能会加载到 CODE/DATA/etc 中,因此不会发生堆碎片。另一方面,如果它是在运行时加载的,除了用完堆空间之外几乎没有其他机会。当然,静态库只是简单地链接到 CODE/DATA/BSS/etc 部分。

归根结底,您需要对堆和堆栈施加限制,以使它们不太可能溢出,但您可以分配其他的。如果一个人需要超过这个限制,你可以

  • 错误终止应用程序
  • 让内存管理器为该堆栈/堆分配/调整大小/移动内存块,然后很可能对堆(它自己的级别)进行碎片整理;这就是为什么free()通常表现不佳的原因。

考虑到一个相当大的,call平均每个 1KB 堆栈帧(如果应用程序开发人员没有经验,可能会发生),10MB 堆栈对于 10240 个嵌套call-s 就足够了。顺便说一句,除此之外,每个线程几乎不需要多个堆栈和堆。

于 2013-04-25T11:12:58.173 回答