【问题标题】:What are the bounds of the heap?堆的边界是什么?
【发布时间】:2014-03-18 13:35:14
【问题描述】:

给定进程中堆的边界是什么?我知道这个问题可能没有简单的答案,所以我对以下问题的答案特别感兴趣:

  • 在 Linux on AMD64 下是否有 64 位进程的标准堆大小/位置?
  • 如果我正在实现语言运行时,我如何才能找出不允许将堆放在哪里(同样,Linux/AMD64)
  • 是否有一种可移植的方式让应用程序找出它的开始/结束位置?

【问题讨论】:

  • 恐怕这个问题不合适。如果您想了解有关如何从操作系统获取堆的更多详细信息,请继续询问。如果您想知道如何自省您的语言运行时如何组织堆,请继续询问。但就目前而言,这个问题太宽泛了。
  • 好的,我缩小了范围。
  • 事实上,这对我来说看起来好多了。
  • 只是为了记录,您正在创建语言运行时吗?
  • @brooks94 由于问题是针对赏金开放的,您不应该在赏金期结束之前接受我的回答。这可能会引起其他成员的关注,他们可以通过不同的解决方案解决这个问题。

标签: linux memory 64-bit heap-memory virtual-memory


【解决方案1】:

我假设您正在尝试在这里编写自己的堆分配器,并且从标签中假设您是在 Linux 中进行的。

SunEric 为您提供了一个有用的指示,说明您可能可以使用哪些内存,但是,您可以使用的内存是操作系统为您提供的内存. IE 要将内存放入您的进程,您需要调用操作系统将虚拟内存映射到进程空间(以及它后面的一些物理内存)。 malloc() 为您抽象了这个,并在 C 中实现了“堆”。它可以通过两种方式获取它的内存:

  1. 使用brk系统调用(映射到C库brksbrk

  2. mmapMAP_ANON 一起使用(或更准确地说是底层系统调用mmap2)。

brk 是为堆分配内存的经典方式,通常当我们谈论“堆”时,我们指的是这种方式分配的内存(尽管brk 可用于分配堆以外的内存,并且堆项目可能存在于其他地方 - 见下文)。 Here 是对 brk 分配工作原理的一个很好的回答,对此我无法改进。内存使用的位置实际上是算术的结果。堆在加载时跟随程序的 BSS - 即 BSS 的值随着堆的扩展而增长,因此开始实际上是由操作系统和动态加载器决定的。因此,堆的结尾是由这个和堆的大小决定的(即你已经把它增长到了多大)。

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

如果addrNULL,那么内核选择创建映射的地址;这是创建新映射的最便携的方法。如果addr 不是NULL,那么内核会将其作为映射放置位置的提示;在 Linux 上,映射将在附近的页面边界处创建。新映射的地址作为调用的结果返回。

因此,如果您使用mmap 为特定的堆项目获取空间(因为malloc 可能特别适用于大型对象),操作系统会选择它的位置,有或没有提示。如果您使用MAP_FIXED,它将为您提供确切的位置或失败。从这个意义上说,您的堆(或其中的项目)可能位于操作系统允许您映射内存的任何位置。

您问是否有一种可移植的方法来找出堆的开始和结束位置。可移植意味着一种语言,我假设是 C。关于brk 类型的堆,是的(相当可移植)。 man end 给:

姓名

etext, edata, end - 程序段结束

概要

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。

【讨论】:

  • 我通常不太关心否决票,但我正在努力弄清楚为什么这值得不加评论地投反对票。
【解决方案2】:

在现代 64 位 AMD64 CPU 上,并非所有地址线都可以为我们提供2^64 = 16 exabytes 的虚拟地址空间。也许在 AMD64 架构上分别启用了48 低位,从而导致地址空间的2^48 = 256TB。因此理论上架构几乎限制在256TB。因此,如果您的磁盘空间为256TB 允许进行交换分区,您可以获得256TB 的堆。如果您对交换分区的数量和大小有限制,即使可用磁盘空间很大,您的限制也小于256TB

在当前 AMD 的 48 位实现中,AMD64 CPU 能够以规范格式寻址的完整虚拟内存范围(如下图所示)分为两半,从 000007FFFFFFFFFFF 和从 FFFF800000000000 到 @ 987654331@,导致可用虚拟地址空间总计为256TB。内存区域地址空间的上半部分用于内核空间,下半部分用于代码、堆、堆栈段的用户空间。因此,随着更多虚拟地址位的可用性,下半部分地址位向上增长,从而导致更多虚拟空间用于将不同的段映射到内存中。这意味着堆可以增长到最大256TB

 0xFFFFFFFFFFFFFFFF +-----------+
                    |   Kernel  |
                    |           |
 0xFFFF800000000000 +-----------+
                    |    Non    |
                    | Canonical |
                    |   range   |
 0x00007FFFFFFFFFFF +-----------+
                    |    User   |
                    |           |
                0x0 +-----------+ 

然而,堆开始于文本段的上方,它的一端可以使用sbrk 参数为0 找到。因为当你调用malloc() 时堆是不连续的,它会从虚拟地址中的任何位置返回地址空间。

您不应该太担心它是如何从根源上工作的,因为它是在现代处理器中抽象出来的。

【讨论】:

  • “不用担心它是如何工作的”?那条线是否曾与程序员合作过?
  • @MichaelFoukarakis 显然我觉得 OP 需要着眼于解决他的问题,而不是调查一个取决于语言、编译器、操作系统、架构等的广泛问题,
猜你喜欢
  • 2019-07-24
  • 2017-03-22
  • 2012-03-22
  • 2019-04-18
  • 2015-10-08
  • 1970-01-01
  • 1970-01-01
  • 2011-12-28
  • 1970-01-01
相关资源
最近更新 更多