我正在编写一个内核并且需要(并希望)将多个堆栈和堆放入虚拟内存中,但我无法弄清楚如何有效地放置它们。普通程序如何做到这一点?
32位系统提供的有限虚拟内存中的堆栈和堆是如何(或在哪里),以便它们具有尽可能多的增长空间?
例如,当一个简单的程序加载到内存中时,其地址空间的布局可能如下所示:
[ Code Data BSS Heap-> ... <-Stack ]
在这种情况下,堆可以像虚拟内存允许的那样增长(例如,直到堆栈),我相信这就是堆如何适用于大多数程序。没有预定义的上限。
许多程序都将共享库放在虚拟地址空间的某个位置。 然后有多线程程序,有多个堆栈,每个线程一个。 .NET程序有multiple heaps,所有这些都必须能够以某种方式增长。
我没有看到如何在不对所有堆和堆栈的大小设置预定义限制的情况下如何有效地完成此操作。
答案 0 :(得分:2)
我假设您已经完成了内核中的基础知识,这是一个可以将虚拟内存页映射到RAM的页面错误的陷阱处理程序。接下来,您需要一个虚拟内存地址空间管理器,用户模式代码可以从中请求地址空间。选择一个可以防止过多碎片的段粒度,64KB(16页)是一个很好的数字。允许usermode代码同时保留空间和提交空间。一个4GB / 64KB = 64K x 2位的简单位图可以跟踪段状态,从而完成工作。页面错误陷阱处理程序还需要查询此位图以了解页面请求是否有效。
堆栈是固定大小的VM分配,通常为1兆字节。一个线程通常只需要一些页面,具体取决于函数嵌套级别,因此保留1MB并仅提交前几页。当线程更深入时,它将触发页面错误,内核可以简单地将额外页面映射到RAM以允许线程继续。你需要将底部的几个页面标记为特殊页面,当线程页面出现故障时,你声明这个网站的名称。
堆管理器最重要的工作是防止碎片。最好的方法是创建一个旁视列表,按大小对堆请求进行分区。少于8个字节的所有内容都来自第一个段列表。第二个是8到16,第三个是16到32,等等。你上去时增加桶的大小。您必须使用铲斗尺寸才能获得最佳平衡。非常大的分配直接来自VM地址管理器。
第一次点击后备列表中的条目时,您将分配一个新的VM段。您将段细分为具有链接列表的较小块。释放此类分配后,将块添加到空闲块列表中。无论程序请求如何,所有块都具有相同的大小,因此不会出现任何碎片。当段完全使用且没有空闲块可用时,您将分配一个新段。当一个段只包含空闲块时,您可以将其返回给VM管理器。
此方案允许您创建任意数量的堆栈和堆。
答案 1 :(得分:0)
简单地说,因为你的系统资源总是有限的,所以你不能无限制。
内存管理总是由几个层组成,每个层都有明确的职责。从程序的角度来看,应用程序级管理器是可见的,通常只涉及它自己的单个分配堆。上面的级别可以处理如果需要从一个全局堆中创建多个堆并将它们分配给子程序(每个都有自己的内存管理器)。以上可能是它使用的标准malloc()
/ free()
以及处理每个进程的页面和实际内存分配的操作系统(它基本上不仅关注多个堆,甚至用户)一般来说就是堆积。)
内存管理成本高昂,因此陷入内核。将两者结合起来可能会带来严重的性能损失,因此从应用程序的角度看,实际的堆管理实际上是出于性能的原因在用户空间(C运行时库)中实现的(其他原因现在超出了范围) )。
当加载共享(DLL)库时,如果它在程序启动时加载,它当然很可能被加载到CODE / DATA / etc,因此不会发生堆碎片。另一方面,如果它是在运行时加载的,那么除了占用堆空间之外几乎没有其他机会。 当然,静态库只是链接到CODE / DATA / BSS / etc部分。
在一天结束时,您需要对堆和堆栈施加限制,以便它们不会溢出,但您可以分配其他堆。 如果需要增长超过该限制,您可以
free()
通常表现不佳的原因。考虑到平均每call
个非常大的1KB堆栈帧(如果应用程序开发人员没有经验,可能会发生),10MB堆栈对于10240嵌套call
-s就足够了。顺便说一句,除此之外,每个线程几乎不需要多个堆栈和堆。