堆的范围是什么?

11

在给定进程中堆的范围是什么?我明白这个问题可能没有简单的答案,所以我特别感兴趣以下问题的答案:

  • 在AMD64 Linux下,64位进程是否有标准的堆大小/位置?
  • 如果我正在实现一种语言运行时,如何找到我不能放置堆的位置(再次强调Linux/AMD64)?
  • 有没有一种便携式的方法让应用程序找出它的开始和结束位置?

很抱歉,这个问题目前不太适合。如果您想了解如何从操作系统中获取堆栈的更多细节,请继续提问。如果您想知道如何检查您的语言运行时如何组织堆栈,请继续提问。但是,就目前而言,这个问题还是太宽泛了。 - Jonas Schäfer
好的,我缩小了范围。 - brooks94
实际上,这对我来说看起来好多了。 - Jonas Schäfer
只是为了记录,你是否正在创建一种语言运行时? - corazza
@brooks94,由于该问题已经开放了悬赏,您在悬赏期结束之前就接受了我的答案,这可能会导致其他成员忽略该问题并提供不同的解决方案。 - Sunil Bojanapally
2个回答

6
我假设您正在尝试编写自己的堆分配器,并且从标签中可以看出您是在Linux上进行。SunEric为您提供了有用的指示,告诉您可能可以使用哪些内存,但是您可以使用的内存是操作系统给您的内存。也就是说,要将内存映射到进程空间中(以及其后面的一些物理内存),您需要调用操作系统来获得内存。malloc()在C语言中实现了“堆”,并为您抽象了这个过程。它可以通过以下两种方式获取内存:使用brk系统调用(映射到C库的brk或sbrk)或使用带有MAP_ANON的mmap(更准确地说是底层系统调用mmap2)。

brk是分配堆内存的传统方式,通常我们谈到“堆”时指的是使用这种方式分配的内存(尽管brk也可用于分配除堆之外的内存,而堆项可能存放在其他地方-请参见下文)。 这里是一个关于brk分配工作原理的很好的答案,我无法改进。内存使用的位置实际上是算术结果。当程序加载时,堆遵循BSS(未初始化数据段) - 即随着堆的扩展,BSS的值会增长,因此起始位置实际上由操作系统和动态加载器确定。因此,堆的结尾由此以及堆的大小(即您将其扩展到多大)确定。

mmap不太明确。它需要一个addr参数:

如果addrNULL,那么内核会选择创建映射的地址;这是创建新映射最便携的方法。如果addr不为NULL,那么内核会把它作为放置映射的提示;在Linux上,映射将在附近的页面边界处创建。新映射的地址作为调用的结果返回。因此,如果您使用mmap为特定的堆项获取空间(如malloc可能为大对象特别做的),则操作系统会选择其位置,无论是否有提示。如果您使用MAP_FIXED,它将给您确切的位置或失败。从这个意义上说,您的堆(或其中的项目)可以是操作系统允许您映射内存的任何地方。您问是否有一种便携式的方法来找出堆的起始和结束位置。便携式意味着一种语言,我将假设是C语言。关于brk类型的堆,是的,有(相当便携)。man end给出了:

名称

etextedataend - 程序段的结尾

概要

extern etext;

extern edata;

extern end;

描述

这些符号的地址指示各种程序段的结尾:

  • etext:这是文本段(程序代码)结束后的第一个地址。

  • edata:这是初始化数据段结束后的第一个地址。

  • end:这是未初始化数据段(也称为BSS段)结束后的第一个地址。

由于堆从BSS的末尾在加载时运行到BSS的顶部在运行时,一种方法是将加载时的end值作为堆底部的起点,将评估时的end值作为堆顶部的结束点。这样会忽略libc本身和共享库可能在调用main()之前分配的内容。因此,更保守的方法是说它是edataend之间的区域,尽管严格来说,这可能包括不在堆上的内容。
如果您不是指C语言,则需要使用类似的技术。取“程序中断”(即内存空间的顶部)并减去您为堆分配的最低地址。
如果您想查看任意进程的堆内存分配情况:
$ cat /proc/$$/maps | fgrep heap
01fe6000-02894000 rw-p 00000000 00:00 0                                  [heap]

$$替换为您想要检查的进程的PID。


我通常不太在意负评,但我正在努力弄清楚为什么这篇文章没有得到评论就被负评了。 - abligh

3
在现代的64位AMD64 CPU中,并不是所有的地址线都被启用,以便为我们提供16EB的虚拟地址空间。也许在AMD64架构中,有48个较低的位被启用,因此可获得256TB的地址空间。因此从理论上讲,架构限制接近于256TB。因此,如果您有256TB的磁盘空间可用于交换分区,那么您可以获得256TB的堆空间。但如果您在交换分区的数量和大小上受到限制,则即使可用磁盘空间较大,您的限制也会小于256TB。
在当前AMD的48位实现中,AMD64 CPU能够以规范格式(如下图所示)寻址的完整虚拟内存范围分为两半,一半范围从0到00007FFFFFFFFFFF,另一半范围从FFFF800000000000到FFFFFFFFFFFFFFFF,因此可获得总计256TB的可用虚拟地址空间。上半部分内存区域的地址空间是用于内核空间的,而下半部分是用于代码、堆、栈段的用户空间。因此,随着更多虚拟地址位的可用性,较低的半部分地址位向上增长,导致更多的虚拟空间映射到内存中。这意味着堆可增长至最大256TB。
 0xFFFFFFFFFFFFFFFF +-----------+
                    |   Kernel  |
                    |           |
 0xFFFF800000000000 +-----------+
                    |    Non    |
                    | Canonical |
                    |   range   |
 0x00007FFFFFFFFFFF +-----------+
                    |    User   |
                    |           |
                0x0 +-----------+ 

然而堆起始于代码段之上,向上生长,可以使用参数为0的sbrk查找其末尾。由于堆不连续,调用malloc()时返回的地址可以在虚拟地址空间的任何地方。

现代处理器已将其深层工作抽象化,您不必过于担心。


1
"不用担心它是如何工作的"?这句话对程序员起过作用吗? - Michael Foukarakis
@MichaelFoukarakis 看起来我认为OP需要解决他的问题,而不是调查一个广泛的问题,这取决于语言、编译器、操作系统、架构等。 - Sunil Bojanapally

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接