如何在Mac OS X上使用ulimit或每个进程更改堆栈大小,用于C或Ruby程序?

29

似乎在C程序或使用C堆栈的Ruby程序中设置堆栈大小的推荐方式是在Bash shell中使用ulimit。但是

$ ulimit -s
8192

$ ulimit -s 16384
-bash: ulimit: stack size: cannot modify limit: Operation not permitted

而且 sudo 也没什么帮助。有没有一种方法可以将它设置为16MB、32MB或64MB?我认为应该有一种方法可以针对每个程序调用进行设置,而不是设置系统范围的参数。

现在,8192 可能意味着8MB,这很小,如果与进程可以使用多达2GB的RAM相比较,那就更加明显了。

(更新注释: ulimit -a 可以显示其当前值)。

(更新2: 实际上,ulimit -s <value> 是在每个 shell 中设置的,如果第一次设置它通常会起作用。问题是当你第二次设置它时,它可能会返回一个错误)。


我想知道这个错误是否与ulimit的“硬限制”和“软限制”有关。参考链接:http://superuser.com/a/79719/153379。 - pje
ulimit堆栈大小只能在设置后降低一次,我已经尽力回答了所有问题,如果您有其他问题,请告诉我。 - Samy Vilar
5个回答

23

显然,在mac os x上有关于堆栈大小的硬限制,参考自http://lists.apple.com/archives/scitech/2004/Oct/msg00124.html。尽管这篇文章相当古老,而且我不确定现在是否仍然有效,但要简单设置它,只需调用ulimit -s hard即可,其值为65532,约为65兆字节。

我在雪豹系统,即10.6.8上进行了一些测试,似乎还是正确的。

$ ulimit -a
...
stack size              (kbytes, -s) 8192
...
$ ulimit -s 65533
-bash: ulimit: stack size: cannot modify limit: Operation not permitted
$ ulimit -s 65532
$

我还发现了这个http://linuxtoosx.blogspot.com/2010/10/stack-overflow-increasing-stack-limit.html,虽然我没有测试过,所以无法做出太多评价。

当应用程序使用几GB的内存时,通常会从堆中获取,而栈通常保留给存在于函数调用生命周期内相对较短时间的本地自动变量,大部分持久数据存在于堆中。

下面是一个快速入门教程:

#include <stdlib.h>

#define NUMBER_OF_BYTES 10000000 // about 10 megs
void test()
{
   char stack_data[NUMBER_OF_BYTES];          // allocating on the stack.
   char *heap_data = malloc(NUMBER_OF_BYTES); // pointer (heap_data) lives on the stack, the actual data lives on the heap.
}

int main()
{   
    test(); 
    // at this point stack_data[NUMBER_OF_BYTES] and *heap_data have being removed, but malloc(NUMBER_OF_BYTES) persists.
    // depending on the calling convention either main or test are responssible for resetting the stack.
    // on most compilers including gcc, the caller (main) is responssible.

    return 0;
}

$ ulimit -a
...
stack size              (kbytes, -s) 8192
...
$ gcc m.c
$ ./a.out
Segmentation fault
$ ulimit -s hard
$ ./a.out
$
  • ulimit只是暂时的,每次需要更新它,或者更新对应的bash脚本以自动设置它。
  • 一旦设置了ulimit,它只能降低而不能升高。

1
是的,如果应用程序消耗了大量内存,它们应该从堆中获取空间,而不是从栈中获取。在栈中分配巨大的对象或大型数组是不合理的。如果应用程序想要将2GB RAM用作堆栈,则应为堆保留多大的内存空间? - jclin
1
@jclin 不确定您的意思,从根本上讲,操作系统负责内存管理,无论我们将其称为堆栈或堆,因此它取决于操作系统的具体实现,一些内存方案可能非常复杂,在linux中,我们有虚拟内存映射到包含页面的页表中,其中一些页面可能无效,因此操作系统实际上不会分配2GB的堆栈,除非它确实需要,否则您将得到页面故障并导致操作系统分配新的页面,当然,如果没有更多的空闲页面,则可能会停止您的程序或崩溃。 - Samy Vilar
我理解你的观点。如果你只是指定了大小,操作系统并不会真正分配2GB,但应用程序也不会使用到2GB。操作系统按页管理内存,并根据需要映射实际页面。如果程序由于堆栈大小不足而崩溃,那么肯定意味着该应用程序确实需要更多的堆栈大小。因此,如果一个应用程序必须尽可能地运行得像2GB一样,我认为大堆栈没有意义,因为进程可以使用高达2GB的RAM堆。这就是为什么许多桌面或服务器有4GB、8GB或更多的内存空间,但每个进程默认仍然只有4MB/8MB的堆栈。 - jclin
1
操作系统除了显而易见的分段错误、栈溢出或者内存资源不足等问题外,很少会告诉你任何有趣的消息。这是因为堆栈或堆在物理内存中并非连续的,即使对于程序来说堆栈看起来是连续的,在现实中它们其实是分散在各个地方的。至于小默认堆栈,有两个原因:1)平均而言,大多数程序不使用太多的堆栈空间;2)防止无限循环,如果默认堆栈大小是无限制的,一个单独的无限循环在任何程序中都将消耗所有内存。 - Samy Vilar

8
在我看来,已经接受的答案并不完全正确,并导致了误解,更具体地说,最后一句话是不正确的。
引用如下: “一旦设置了ulimit,它只能降低而不能提高。”
确实存在软限制(可通过“ulimit -s”或“ulimit -Ss”显示)和硬限制(可通过“ulimit -Hs”显示)。 但是,虽然通过“ulimit -s”设置限制将影响软限制和硬限制值,但一旦设置了限制,则只能将其降低而不能提高,但是可以降低或提高软限制,前提是该值仍低于硬限制。
这个会起作用:
# base values
$ ulimit -s
100
$ ulimit -Hs
100
$ ulimit -Ss
100
# lower soft limit only
$ ulimit -Ss 50
$ ulimit -s
50
$ ulimit -Hs
100
$ ulimit -Ss
50
# raise soft limit only
$ ulimit -Ss 100
$ ulimit -s
100
$ ulimit -Hs
100
$ ulimit -Ss
100
# lower soft and hard limit
$ ulimit -s 50
$ ulimit -s
50
$ ulimit -Hs
50
$ ulimit -Ss
50
# then impossible to raise soft limit due to hard limit
$ ulimit -s 100
-bash: ulimit: stack size: cannot modify limit: Operation not permitted
$ ulimit -Ss 100
-bash: ulimit: stack size: cannot modify limit: Invalid argument

在Bash中,无法像你说的那样增加硬限制,但在Zsh中可以增加它,只是不能超过原始硬限制,例如假设您的硬限制为X,您可以将其减少到Y,运行smth(例如第二个zsh副本),然后将其增加回X。但第二个副本将无法超过Y。 - RiaD
此外,一些应用程序/服务的发货方式是它们无法将软限制更改为更高的值,即使访问不会阻止该操作。最好认为软限制可能是您进程的实际限制。堆栈是基于进程应用的,唯一应用于用户/会话的是ulimit参数列表中的nproc。 - laimison

1
系统默认的堆栈大小因内核版本而异。我的10.7版本是16384,因此我的Mac可以接受ulimit -s 16384命令。您可以尝试使用sysctl kern.stack_size命令查看只读堆栈大小,我的大小是16384。
您可以参考这篇技术文章http://developer.apple.com/library/mac/#qa/qa1419/_index.html,了解如何更改C程序的默认堆栈大小。对于Ruby来说,由于它是一种脚本语言,在链接Ruby解释器时必须扩大其堆栈大小。 除非有非常深的函数调用或递归,或者在堆栈中分配非常大的数组和对象,否则您的程序不应该有巨大的堆栈空间。相反,使用堆或动态分配可以使用高达2GB的RAM。

我也想知道为什么这必须在链接时完成,而不是执行时完成。如果Ruby实际上创建了一个新的线程来运行Ruby程序,并且具有堆栈大小,则可以使用命令行ruby --stack-size 16384 foo.rb设置堆栈大小。 - nonopolarity
是的。我的操作系统接受 ulimit -s 32767(我认为 ulimit 的默认值是无限制的,但操作系统内核有默认大小)。但是一旦您设置了该值,就不能设置比之前更大的值。否则,会显示错误消息“操作不允许”。 - jclin
链接时设置的默认堆栈大小是合理的,因为当操作系统加载可执行文件时,内核必须在跳转到程序之前准备好一切。链接时间选项标记了Mach-O可执行文件格式中的堆栈大小,操作系统/内核可以看到该选项以为可执行环境创建不同的堆栈大小。Ruby可以为其新线程创建不同的堆栈大小,但运行ruby本身的第一个和默认堆栈由操作系统和链接时间选项确定。 - jclin

0

我发现使用/bin/zsh而不是/bin/sh可以解决这个错误。

对我来说,这个错误发生在一个调用了ulimit -s unlimited的shell脚本中。当脚本被/bin/sh解释时(即脚本文件的第一行为#!/bin/sh),它会出现错误。相反,当将其更改为使用zsh时,一切似乎都正常工作。 zsh足够聪明,可以将unlimited解释为“给我操作系统允许我的最大限制”,并且一切都按照您所希望的方式工作。


1
你说的话听起来很奇怪,你确定吗? - David J.
@DavidJames,我也觉得这很奇怪,对于为什么会出现这种情况,我也无法解释,所以我的回答可能完全错误。我不记得如何重现这个问题或在什么情况下遇到它,所以我不确定。 很抱歉这不是很有帮助。 - D.W.

0

所有内置的ulimit控制的限制实际上都是在操作系统内核中实现的,因此您应该查看整个系统的C接口文档。这里是苹果公司关于setrlimit()的文档:https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/setrlimit.2.html

(请注意,该文档的路径似乎说的是iPhoneOS,但内容仍然涉及“Mac OS X”。如果您本地安装了适当的文档,则在终端中运行man setrlimit将会发出最新的文档。)

新创建的进程从fork()父进程或执行exec()的前一个进程继承限制。


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