【发布时间】:2011-03-24 12:40:42
【问题描述】:
我对堆栈和动态内存分配有疑问。
1) 内核是在运行时动态确定堆栈的大小还是在加载时间之前设置大小?如果堆栈大小是动态分配的,如何发生堆栈溢出(因为如果堆栈大小超出限制,页面处理程序将分配空间来增长堆栈)。另外,如果动态分配,堆栈如何从高地址增长到低地址(因为动态分配的存储空间总是虚拟地址递增?)
2) 此外,如果内存是使用 malloc 动态分配的,那么数据区域的大小会增长吗?
感谢和问候,
鼠标。
【问题讨论】:
我对堆栈和动态内存分配有疑问。
1) 内核是在运行时动态确定堆栈的大小还是在加载时间之前设置大小?如果堆栈大小是动态分配的,如何发生堆栈溢出(因为如果堆栈大小超出限制,页面处理程序将分配空间来增长堆栈)。另外,如果动态分配,堆栈如何从高地址增长到低地址(因为动态分配的存储空间总是虚拟地址递增?)
2) 此外,如果内存是使用 malloc 动态分配的,那么数据区域的大小会增长吗?
感谢和问候,
鼠标。
【问题讨论】:
堆栈大小
通常,每个线程在创建线程时都有一个固定的堆栈。运行 ulimit -a 以查看系统的默认堆栈大小。在我的系统上,它是 8 MiB。当您创建新线程时,您可以为它们提供更小或更大的堆栈(请参阅pthread_attr_setstacksize)。
当堆栈超过 8 MiB 时,程序将写入无效的内存位置并崩溃。内核确保堆栈旁边的内存位置都是无效的,以确保程序在堆栈溢出时崩溃。
您可能认为固定大小是一种浪费,但 8 MiB 是虚拟内存,而不是物理内存。区别很重要,见下文。
Malloc
在 Unix 系统上,内存分配有两层。用户空间层是malloc(和calloc、realloc、free)。这只是 C 库的一部分,可以用您自己的代码替换——Firefox 会这样做,并且许多编程语言使用它们自己的分配方案而不是 malloc。各种malloc 实现是跨平台的。
下层是mmap(和sbrk)。 mmap 函数是一个改变程序地址空间的系统调用。它可以做的一件事就是将新的匿名私有页面添加到程序的内存中。
malloc 的目的是使用mmap(或sbrk)从内核获取大量虚拟内存,并为您的程序有效地划分它们。 mmap 系统调用仅适用于 4 KiB 的倍数(在大多数系统上)。
内存:虚拟与真实
请记住,mmap 返回的堆栈和所有内存只是虚拟内存,而不是物理 RAM。在您实际使用它之前,内核不会为您的进程分配物理 RAM。
当您从内核获取匿名内存时,无论是在堆上还是在堆栈上,它都会被零填充。然而,内核并没有为您提供数百页预填充零的物理 RAM,而是使所有虚拟内存共享一个物理 RAM 页。虚拟内存被标记为只读。一旦你写它,CPU就会抛出一个异常,将控制权转移给内核,内核为你的程序分配一个新的、可写的、归零的页面。
这解释了原因:
calloc 比 malloc + memset 快(因为 calloc 知道 mmap 的页面已预置零,而 memset 强制分配物理 RAM)【讨论】:
mmap或sbrk,但是有内存映射。
1) 这有点取决于操作系统,但一个典型的方案是操作系统为您提供一页虚拟内存(页面通常为 4 KB)用于堆栈,然后立即将虚拟内存页面标记为“保护页”。这是一个特殊标志,当应用程序尝试写入时会触发低级异常。
操作系统会在您尝试写入保护页面时处理异常(当您将堆栈增长到超过初始分配大小时会发生这种情况),为您分配该页面(即,将其映射到物理内存页面)并重新运行您的程序从发生错误写入的指令开始。这次它会工作,因为页面已经被真实内存支持。
超过某个点(通常为 1 MB),操作系统将停止执行此操作并触发堆栈溢出异常。这只是因为这些通常表示程序错误,而真正需要巨大堆栈的代码可以为堆上的堆栈数据分配内存。
2) 数据段并没有真正“增长”。现代程序具有固定的虚拟内存地址空间。 malloc() 使用某种方案来分割这个空间,并用真实的物理内存返回它的一部分。
我认为您的两个问题都暗示您希望更好地了解操作系统如何为您的程序提供物理内存。现代系统中的关键概念是虚拟内存。 Wikipedia's page on virtual memory 是一个很好的起点。
如果您想发展详细的知识,操作系统教科书将是一个不错的起点。最好是比我在大学学习操作系统课程时所拥有的更好:)
【讨论】:
1) 动态内存分配来自堆,而不是堆栈。堆栈是每个线程的内存块,它还在函数调用期间处理局部变量和返回地址。堆只是从操作系统分配的一个或多个内存块,C RTL 根据需要对其进行细分以满足 malloc 调用。
至于堆栈,它通常具有固定大小,因为在没有无限递归的程序中,您应该只需要有限的数量。如果你继续递归,你会溢出,这是一件好事,因为它是一个干净的失败。至于内存增长,那是CPU的实现细节。
2) 不一定。只要操作系统当前分配的内存足够,malloc 就会使用它。一旦它消失,它可能会导致额外的分配。
我知道您想在这里获得更多详细信息,但我不确定什么有用。我可以讨论堆通常如何实现为空闲块的链接列表,其中包含定义其大小的 arena 标头,或者一些系统如何使用固定大小的块进行小分配以限制碎片,甚至当没有单个内存气泡时如何合并找到足够大的块。但是,所有这些都是实施细节,很可能不适用于您的情况,至少不完全适用。
【讨论】:
alloca,或可变长度数组。只是通常不会这样做。