在给定进程中堆的范围是什么?我明白这个问题可能没有简单的答案,所以我特别感兴趣以下问题的答案:
- 在AMD64 Linux下,64位进程是否有标准的堆大小/位置?
- 如果我正在实现一种语言运行时,如何找到我不能放置堆的位置(再次强调Linux/AMD64)?
- 有没有一种便携式的方法让应用程序找出它的开始和结束位置?
在给定进程中堆的范围是什么?我明白这个问题可能没有简单的答案,所以我特别感兴趣以下问题的答案:
brk
是分配堆内存的传统方式,通常我们谈到“堆”时指的是使用这种方式分配的内存(尽管brk
也可用于分配除堆之外的内存,而堆项可能存放在其他地方-请参见下文)。 这里是一个关于brk
分配工作原理的很好的答案,我无法改进。内存使用的位置实际上是算术结果。当程序加载时,堆遵循BSS(未初始化数据段) - 即随着堆的扩展,BSS的值会增长,因此起始位置实际上由操作系统和动态加载器确定。因此,堆的结尾由此以及堆的大小(即您将其扩展到多大)确定。
mmap
不太明确。它需要一个addr
参数:
addr
为NULL
,那么内核会选择创建映射的地址;这是创建新映射最便携的方法。如果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()
之前分配的内容。因此,更保守的方法是说它是edata
和end
之间的区域,尽管严格来说,这可能包括不在堆上的内容。$ cat /proc/$$/maps | fgrep heap
01fe6000-02894000 rw-p 00000000 00:00 0 [heap]
将$$
替换为您想要检查的进程的PID。
0xFFFFFFFFFFFFFFFF +-----------+
| Kernel |
| |
0xFFFF800000000000 +-----------+
| Non |
| Canonical |
| range |
0x00007FFFFFFFFFFF +-----------+
| User |
| |
0x0 +-----------+
然而堆起始于代码段之上,向上生长,可以使用参数为0的sbrk
查找其末尾。由于堆不连续,调用malloc()
时返回的地址可以在虚拟地址空间的任何地方。
现代处理器已将其深层工作抽象化,您不必过于担心。