【问题标题】:Questions about the Linux program memory layout schema关于Linux程序内存布局架构的问题
【发布时间】:2017-06-20 07:52:06
【问题描述】:
下图引自书Programming from the Ground Up。
为什么程序内存区域被限制在0xbfffffff和0x8048000之间?这种选择背后的理由是什么?这个区域之外有什么?
这张图片说明的应该是一个32位的程序。 64位程序的内存布局是什么?
图片中提到了“at startup”,那么运行时布局会不会发生变化?
最后但并非最不重要的一点是,Linux 内核是否也遵循这种布局?
【问题讨论】:
标签:
linux
memory-management
linux-kernel
【解决方案1】:
-
32 位 x86 上的 Linux 传统上具有 3G/1G 用户/内核拆分。
我有一个older answer 对此进行了更详细的介绍。
-
在 x86 上,堆栈向下增长(从高处开始向低地址移动)。这几乎是 CPU 设计中的任意决定(这就是 push/pop/call/ret 指令对 %esp 所做的事情)。 Linux 在范围的顶部启动它。
相比之下,程序数据默认映射到低端。从历史上看,内核映射在0x08000000 之下,因此用户空间可用的最低地址在其之上。这不再是真的,但它解释了原来的0x08040000 加载地址。
中间的空间用于堆。 “中断”标记通过调用brk() 或sbrk() 向上/向下移动;它下面的内存可供程序使用。从历史上看,C 运行时移动程序分解以满足malloc 的空间要求。
-
在当前的 64 位 x86 CPU 上,只有地址 0x0000000000000000-0x00007FFFFFFFFFFF 和 0xFFFF800000000000-0xFFFFFFFFFFFFFFFF 是规范的。 64 位指针,但“仅”48 位可用地址空间。 (这是 256TB,因此我们需要一段时间才能达到该限制。)如果您尝试访问任何非规范地址(0x0000800000000000-0xFFFF7FFFFFFFFFFF),硬件将触发故障。 Linux 在下半部分映射用户空间,在上半部分映射内核。
堆栈仍然向下增长,因此 Linux 仍然从范围顶部开始,固定映射从底部开始,堆在它们上方增长。
是的。除了随着堆的增长和缩小而上下移动的程序中断,程序还可以使用mmap/munmap将各种东西映射/取消映射到它的地址空间,例如文件、共享内存、匿名内存、等等。事实上,如今的 C 运行时除了或代替操纵程序中断之外,还以块的形式映射匿名内存。
内核本身在 32 位 x86 上位于上层 1G,在 64 位 x86 上位于上层 128TB。它的布局对用户空间几乎不重要(并且不可见),但包括诸如每个内核线程的堆栈和每个用户线程的内核端、页表、缓存、DMA 缓冲区等。
其他说明:
所有这些都是关于虚拟地址,而不是物理地址。
用户空间可访问的最低地址不一定是 0。C 程序期望 NULL 是一个错误地址(如果它实际上是有效的,这可能会导致 exploits),并且内核强制执行 /proc/sys/vm/mmap_min_addr,它默认为现在 64k。
现在的Linux(现在已经十多年了)将VDSO 映射到一个极高的地址,因此堆栈从该地址下方开始。曾几何时,vsyscall 页面也在上面,但现在它位于内核空间的页面中。
由于ASLR,所有地址可能会被打乱。这在 32 位地址空间上不是很有效(由于页面对齐,您不能随机化低 12 位,可能更多是由于其他限制),但是在 64 位模式下有很多位。
【讨论】:
-
我有另一个old answer 涉及初始堆栈布局。 Linux 使用堆栈上的环境变量和命令行参数启动所有程序,C 运行时安排在 main() 之前。