Linux 克隆调用的最小堆栈大小?

neb*_*uch 5 c linux fork clone

我一直在摆弄clone调用,我注意到不同子线程堆栈分配的三种不同结果。以下演示分配了一个n字节大的堆栈,其中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;
}
Run Code Online (Sandbox Code Playgroud)

以下是我的观察:

$ 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
Run Code Online (Sandbox Code Playgroud)

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
# ...
Run Code Online (Sandbox Code Playgroud)

这提示了几个重叠的问题。

  • 为什么不在clone24 到 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
Run Code Online (Sandbox Code Playgroud)

mwe*_*den 4

  • 为什么不在 24 到 583 字节堆栈之间克隆段错误?

确实如此,但由于它是一个单独的过程,因此您看不到它。在 24 岁之前,不是子进程出现段错误,而是父进程试图设置子进程。尝试使用 strace -ff 来查看这种情况的发生。

  • 由于堆栈太少,孩子如何默默地失败?

当孩子去世时,父母会收到通知。在这种情况下,父级(执行调用的父级clone())不会对此通知执行任何操作。低于 24 时它不会“沉默”的原因是因为此时父进程死亡,在这种情况下您的 shell 将收到通知。

  • 这些堆栈空间是用来做什么的?
  • 24和584字节有什么意义?它们在不同的系统和实现上有何不同?

前 24 个(还有一点)用于设置对 的函数调用child。因为它是一个普通函数,所以完成后它将返回到调用函数。这意味着clone必须设置一个要返回的调用函数(一个干净地终止子进程的函数)。

write584(还有一点)显然是调用函数、您的函数以及任何调用的局部变量所需的内存量write。

我写“(和一点)”的原因是因为在这之前可能会有一点内存stack可用并被空间耗尽clone或child空间不足时被滥用。尝试在克隆后添加 来free(stack)查看滥用的结果。

  • 我可以计算最低堆栈要求吗?我是不是该?

一般来说,你可能不应该这样做。它需要对您的函数以及它们使用的外部函数进行相当深入的分析。就像“普通”程序一样,我建议使用默认值(如果我没记错的话,Linux 上是 8MB)。只有当您有严格的内存要求(或堆栈溢出问题)时,您才应该开始担心这些事情。