Linux进程的堆栈大小是否有限制?

20

在Linux中,进程的堆栈大小是否有限制?这与机器的RAM有关吗?

我想知道这个问题是为了限制递归调用函数的深度。


你总是可以快速地编写一个测试来查看堆栈的深度。CountDepth(int d) { CountDepth(d + 1); } - Martin
2
@Martin:尽管在函数中使用变量会降低“深度”。 - GManNickG
这是正确的,但这是一个很好的估计。如果你真的想要,你可以数一下临时变量和方法参数中使用的位数,并为给定的函数精确计算出来。 - Martin
4个回答

35

堆栈通常受资源限制的影响。您可以使用 ulimit -a 命令查看安装的默认设置:

stack size              (kbytes, -s) 8192

(这表明我的是8MB,非常大)。

如果您删除或增加该限制,仍然无法在堆栈中使用机器中的所有RAM-堆栈从进程地址空间顶部附近的一点向下增长,并且在某个时候它将遇到您的代码、堆或已加载库。


2
在某个时候,它会遇到你的代码、堆或已加载的库。我怀疑这是否会成为像 Linux x86_64 这样的 64 位系统的问题? - Sam Watkins
1
@SamWatkins:没错 - 这个答案是5年前写的,而且是在32位环境下的情况下。 - caf
如果要遵循 i386 的 ABI,这是不正确的。在其中,堆栈位于其他所有内容下面,理论上可以增长到略高于 128MiB。在 AMD64 ABI 中,它从 128GiB 标记下方增长,略低于共享库。因此,它理论上可以覆盖代码/数据,但仅限于主程序或其堆。 - ivan_pozdeev
1
@ivan_pozdeev:在Linux上,堆栈是最后一个 - 在x86 Linux安装中检查/proc/<pid>/maps。例如,参见此提交消息,描述了2004年实施的新布局 - 旧布局和新布局都将堆栈放在最后。 - caf

6

管理员可以设置限制。

请参考man ulimit

可能有一个默认值,您无法跨越。如果您需要担心堆栈限制,我建议您重新考虑设计,也许编写迭代版本?


1
或者编写一个递归版本,使用手动管理的堆栈而不是函数调用堆栈。 - Roger Pate
2
@Roger Pate: 那基本上就是迭代版本。 - slebetman

5

这在很大程度上取决于您使用的架构(32位或64位)以及是否支持多线程。

默认情况下,在单线程进程中,也就是由操作系统在exec()时创建的主线程中,堆栈通常会增长,直到它触碰到地址空间中的其他东西。这意味着在32位机器上,通常可以有1G的堆栈大小。

但是,在多线程32位进程中,情况绝对不是这样的。在多线程进程中,堆栈共享地址空间,因此需要进行分配,因此它们通常被给予一小部分地址空间(例如1M),以便可以创建许多线程而无需耗尽地址空间。

因此,在多线程进程中,堆栈相对较小且有限,在单线程进程中,堆栈基本上可以增长至触碰地址空间中的其他部分(默认分配机制试图确保不会太快发生)。

在64位机器上,当然有更多的地址空间可供使用。

无论如何,您始终可能会用尽虚拟内存,在这种情况下,您将收到SIGBUS或SIGSEGV等信号。


1

我原本想评论已接受的答案,但显然我的声望不够....

真正的堆栈溢出有时可能很微妙,并且不会引起任何错误消息或警告。我曾经遇到这样一种情况,唯一的症状是套接字连接会失败,并显示奇怪的SSL错误。其他一切都正常工作,线程可以malloc()、抓取锁、与数据库通信等等。但新的连接在SSL层面上会失败。

由于来自GnuTLS内部的堆栈跟踪,我对真正的原因感到困惑。在花费大量时间尝试弄清楚之后,几乎向他们的团队报告了这些跟踪信息。

最终发现,堆栈大小被设置为8Mb,一旦将其提高,问题就消失了。将堆栈降低回8Mb会带回问题(ABA)。

因此,如果您正在排除似乎没有其他警告或未初始化内存错误的奇怪套接字错误,那么可能是堆栈溢出。


3
堆栈不应该跟随未映射/不可写的块(上下),以防止这种灾难发生吗?如果我没错的话,那么堆栈溢出怎么可能不会导致SEGV或类似的故障呢?我建议你的bug可能是其他原因引起的,而堆栈变化只是巧合地解决了它(准-海森巴格)。 - Sam Watkins
抱歉回复晚了,这取决于操作系统的守卫页面布局和溢出行为。如果以特定方式溢出堆栈,则不会出现段错误。一些旧平台没有使用守卫页面,或者如果使用了,它们没有像今天这样放置在相同的位置。段错误仍然是可能不会发生的,这取决于在特定情况下星星如何排列。 - AcidTonic
@SamWatkins:足够大的alloca或C99 VLA可以跳过守卫页,进入另一个区域。(如果恶意地对一个在使用本地数组之前不检查大小的程序进行此操作,则会发生堆栈冲突攻击。有关更多编译器选项以加强防护的信息,请参见Linux进程堆栈被本地变量(堆栈保护)溢出,例如通过堆栈探针确保在增加超过一页的堆栈时触摸中间页面,即使在不需要的Linux上也是如此。) - Peter Cordes
还有堆栈保护和堆栈溢出保护 - 哨兵,内存。 但是是的,应该在堆栈下方设置守卫页面以捕获任何“无意”的堆栈溢出。 在虚拟地址空间狭窄的32位系统上,可能会在线程堆栈正下方发现某些内容。 对于64位系统,特别是对于主线程来说,这应该不是问题。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接