关于 Linux 程序内存布局模式的问题

smw*_*dia 1 linux memory-management linux-kernel

下图引自《从头开始编程》一书。

在此处输入图片说明

  • 为什么程序内存区域被限制在0xbfffffff和之间0x8048000?这个选择背后的理由是什么?这个区域之外是什么?

  • 这张图说明的应该是一个 32 位的程序。64 位程序的内存布局是什么?

  • 图片中提到了“启动时”,那么在运行过程中布局会发生变化吗?

  • 最后但并非最不重要的一点是,Linux 内核是否也遵循这种布局?

eph*_*ent 5

  • 32 位 x86 上的 Linux 传统上具有 3G/1G 用户/内核拆分。

    • 用户空间内存始终映射在 0x00000000-0xBFFFFFFF 范围内(即,在 32 位模式下可以直接寻址的 4GB 中的较低 3GB)。在进程之间切换时,整个空间都会重新映射。

    • 内核内存始终映射在 0xC0000000-0xFFFFFFFF 范围内(上 1GB)。它在用户空间的特权级别是不可访问的,但是当上下文切换到内核时,它可以访问其所有内存而无需重新映射任何内容。

    我有一个较旧的答案,其中更详细地介绍了这一点。

  • 在 x86 上,堆栈向下增长(从高地址开始并向低地址移动)。这是相当多的CPU设计(任意决定,这是什么push/ pop/ call/ret指令做%esp)。Linux 在范围的顶部启动它。

    相比之下,节目数据默认映射到低端。从历史上看,内核映射在 下方0x08000000,因此可供用户空间使用的最低地址高于该地址。这不再是真的,但它解释了原始0x08040000加载地址。

  • 中间的空间用于堆。"Break" 标记通过调用brk()or向上/向下移动sbrk();它下面的内存可供程序使用。从历史上看,C 运行时将程序拆分以满足malloc.

  • 在当前的 64 位 x86 CPU 上,只有地址 0x0000000000000000-0x00007FFFFFFFFFFF 和 0xFFFF800000000000-0xFFFFFFFFFFFFFFFF 是规范的。64 位指针,但“只有”48 位的可用地址空间。(这是 256TB,所以在我们达到该限制之前还需要一段时间。)如果您尝试访问任何非规范地址 (0x0000800000000000-0xFFFF7FFFFFFFFFFFF),硬件将触发故障。Linux 在下半部分映射用户空间,在上半部分映射内核。

    堆栈仍然向下增长,因此 Linux 仍然从顶部开始,固定映射从底部开始,堆在它们上方增长。

  • 是的。除了程序break随着堆的增长和收缩而上下移动,程序还可以使用mmap/munmap将各种东西映射/取消映射到它的地址空间,比如文件、共享内存、匿名内存等。 事实上,C这些天的运行时除了或代替操纵程序中断之外,还以块的形式映射匿名内存。

  • 内核本身位于 32 位 x86 上的上层 1G 和 64 位 x86 上的上层 128TB。它的布局对于用户空间来说大多不重要(并且不可见),但包括诸如每个内核线程的堆栈和每个用户线程的内核端、页表、缓存、DMA 缓冲区等内容。

其他注意事项:

所有这些都是关于虚拟地址,而不是物理地址。

用户空间可访问的最低地址不一定是 0。C 程序期望NULL是一个坏地址(如果它实际上有效,则可能导致漏洞利用),并且内核/proc/sys/vm/mmap_min_addr现在强制执行默认为 64k。

现在的 Linux(现在已经有十多年了)映射了VDSO一个极高的地址,因此堆栈开始于该地址之下。曾几何时,vsyscall 页面也在那里,但现在它位于内核空间的页面中。

由于ASLR ,所有地址都可能被打乱。这在 32 位地址空间上不是很有效(由于页面对齐,您不能随机化低 12 位,可能更多是由于其他限制),但在 64 位模式下有很多位。