Linux克隆调用的最小堆栈大小?
Posted
技术标签:
【中文标题】Linux克隆调用的最小堆栈大小?【英文标题】:Minimal stack size for Linux clone call? 【发布时间】:2016-12-20 17:57:03 【问题描述】:我一直在摆弄clone
调用,我注意到不同子线程堆栈分配的三种不同结果。以下演示分配了一个 n-bytes 大的堆栈,其中 n 作为参数传递,然后尝试克隆。
foo.c:
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sched.h>
#include <errno.h>
int child(void *arg)
(void)arg;
write(STDOUT_FILENO, "carpe momentum\n", 15);
return 0;
int main(int argc, char **argv)
long stacksize;
pid_t pid;
void *stack;
if (argc < 2)
return 1;
errno = 0;
stacksize = strtol(argv[1], NULL, 0);
if (errno != 0)
return 1;
stack = malloc(stacksize);
if (stack == NULL)
return 1;
pid = clone(child, stack + stacksize, 0, NULL);
if (pid == -1)
return 1;
write(STDOUT_FILENO, "success\n", 8);
return 0;
以下是我的观察:
$ cc -o foo foo.c
$ ./foo 0
Segmentation fault
$ ./foo 23
Segmentation fault
$ ./foo 24
success
$ ./foo 583
success
$ ./foo 584
success
carpe momentum
$ ./foo 1048576 #1024 * 1024, amount suggested by man-page example
success
carpe momentum
0 到 23 之间的所有样本都有段错误,对于 24 到 583 之间的所有样本,父级成功但子级保持沉默。任何高于 584 的合理因素都会导致两者都成功。
反汇编表明child
只使用了 16 个字节的堆栈空间,再加上至少 16 个字节来调用write
。但这已经超过了停止段错误所需的 24 个字节。
$ objdump -d foo
# ...
080484cb <child>:
80484cb: 55 push %ebp
80484cc: 89 e5 mov %esp,%ebp
80484ce: 83 ec 08 sub $0x8,%esp
80484d1: 83 ec 04 sub $0x4,%esp
80484d4: 6a 0f push $0xf
80484d6: 68 50 86 04 08 push $0x8048650
80484db: 6a 01 push $0x1
80484dd: e8 be fe ff ff call 80483a0 <write@plt>
80484e2: 83 c4 10 add $0x10,%esp
80484e5: b8 00 00 00 00 mov $0x0,%eax
80484ea: c9 leave
80484eb: c3 ret
# ...
这会提示几个重叠的问题。
为什么clone
不会在 24 和 583 字节的堆栈之间出现段错误?
child
如何在堆栈太少的情况下静默失败?
所有堆栈空间都用来做什么?
24 和 584 字节的意义是什么?它们在不同的系统和实施中有何不同?
我可以计算最低堆栈要求吗?我应该吗?
我在 i686 Debian 系统上:
$ uname -a
Linux REDACTED 3.16.0-4-686-pae #1 SMP Debian 3.16.7-ckt25-2+deb8u3 (2016-07-02) i686 GNU/Linux
【问题讨论】:
这是一个有趣的观察,但我想到了一堆问题:什么操作系统? CPU架构?编译器? 32 位还是 64 位?您是否尝试过平台上的细微差别?根本差异?有趣的事实:30 年前,我在 Sun Microsystems C 编译器中发现了一个 100% 可重现的错误,他们不相信我。我说,“与错误信息交谈。” @PeterRowell 添加了uname
将其输出到问题中;谢谢。我没有在任何其他系统上尝试过。
【参考方案1】:
为什么不克隆 24 到 583 字节堆栈之间的段错误?
确实如此,但因为它是一个单独的过程,所以您看不到它。在 24 岁之前,段错误不是孩子,而是试图设置孩子的父母。尝试使用 strace -ff 来查看这种情况。
如果堆栈太少,孩子如何静默失败?
当孩子去世时,会通知父母。在这种情况下,父级(执行clone()
调用的父级)对此通知不做任何事情。它在 24 岁以下不是“沉默”的原因是因为那是父母去世的时候,在这种情况下,你的 shell 会收到通知。
所有堆栈空间都用来做什么? 24 和 584 字节的意义是什么?它们在不同的系统和实施中有何不同?
前 24 个(还有一点)用于设置对child
的函数调用。因为它是一个普通函数,完成后它会返回调用函数。这意味着clone
必须设置一个调用函数来返回(一个只是干净地终止子进程的函数)。
584(还有一点)显然是调用函数、你的函数、write
和任何write
调用的局部变量所需的内存量。
我写“(和一点)”的原因是因为stack
之前可能有一点内存可用,并且在空间用完时被clone
或child
滥用。尝试在克隆后添加free(stack)
以查看滥用的结果。
我可以计算最低堆栈要求吗?我应该吗?
一般而言,您可能不应该这样做。它需要对你的函数和那些使用的外部函数进行相当深入的分析。就像“普通”程序一样,我建议使用默认值(如果我没记错的话,Linux 上是 8MB)。只有当你有严格的内存要求(或堆栈溢出问题)时,你才应该开始担心这些事情。
【讨论】:
child
在对write
的调用中使用了大部分 584 字节吗?另外,为什么设置 child
需要 24 个字节而不是 16 个字节?
@nebuch 可能;正如您所展示的,孩子本身并没有使用太多。尽管“调用函数”也可以进行一些额外的调用,这些调用会导致这种用法。必须进行更多调试才能准确找出答案。
假设我确实有严格的内存要求,有没有办法从调用树或类似的东西计算最大堆栈深度?
@nebuch 看起来这里已经回答了:***.com/a/6390984/1306666
8mb 仅适用于 glibc,musl 在 Linux 上使用的默认堆栈大小要小得多,为 80kb。见git.musl-libc.org/cgit/musl/tree/src/internal/…以上是关于Linux克隆调用的最小堆栈大小?的主要内容,如果未能解决你的问题,请参考以下文章