Linux上的clone()系统调用接受一个指向堆栈的参数,以供新创建的线程使用。显而易见的方法是简单地malloc一些空间并传递它,但是你必须确保你已经使用了大量的堆栈空间,因为该线程将使用(很难预测)。
我记得在使用pthreads时我不必这样做,所以我很好奇它做了什么。我遇到了this site,它解释说,“Linux pthreads实现使用的最佳解决方案是使用mmap来分配内存,标志指定在使用时分配的内存区域。这样,内存根据需要为堆栈分配,如果系统无法分配额外的内存,则会发生分段违规。“
我曾经听过mmap使用的唯一上下文是将文件映射到内存,实际上读取mmap手册页需要一个文件描述符。如何使用它来分配一堆动态长度来给clone()?这个网站真的很疯狂吗? ;)
在任何一种情况下,内核都不需要知道如何为新堆栈找到一堆免费内存,因为这是用户启动新进程时必须始终做的事情吗?如果内核已经可以解决这个问题,为什么甚至需要首先指定堆栈指针?
答案 0 :(得分:5)
您需要mmap的MAP_ANONYMOUS标志。而MAP_GROWSDOWN因为你想把它当作一个堆栈使用。
类似的东西:
void *stack = mmap(NULL,initial_stacksize,PROT_WRITE|PROT_READ,MAP_PRIVATE|MAP_GROWSDOWN|MAP_ANONYMOUS,-1,0);
有关详细信息,请参阅mmap手册页。请记住,克隆是一个低级概念,除非你真的需要它提供的东西,否则你不打算使用它。它提供了很多控制 - 比如设置它自己的堆栈 - 以防你想要做一些欺骗(比如在所有相关进程中都可以访问堆栈)。除非你有充分的理由使用克隆,否则坚持使用fork或pthreads。
答案 1 :(得分:5)
堆栈在他们的增长空间中不是,也绝不可能是无限的。与其他所有东西一样,它们存在于进程的虚拟地址空间中,它们可以增长的数量总是受到到相邻映射内存区域的距离的限制。
当人们谈论堆栈动态增长时,他们可能意味着两件事之一:
尝试依赖MAP_GROWSDOWN
标记是不可靠的并且危险因为它无法保护您免受mmap
在您的堆栈旁边创建新映射的影响重挫。 (参见http://lwn.net/Articles/294001/)对于主线程,内核自动保留下面的堆栈大小ulimit
值地址空间(不是内存)堆叠并阻止mmap
分配它。 (但要注意!一些破坏供应商修补的内核会禁用此行为导致随机内存损坏!)对于其他线程,您只需必须 mmap
线程可能需要的整个地址空间范围堆叠时创建它。没有其他办法。您可以使其大部分最初不可写/不可读,并在故障时更改它,但是您需要信号处理程序并且这个解决方案在POSIX线程实现中是不可接受的,因为它会干扰应用程序的信号处理程序。 (注意,作为扩展,内核可以提供特殊的MAP_
标志,以便在非法访问映射时提供不同的信号而不是SIGSEGV
,然后执行线程可以抓住这个信号并采取行动。但Linux目前还没有这样的功能。)
最后,请注意 clone
系统调用不接受堆栈指针参数,因为它不需要它。系统调用必须从汇编代码执行,因为用户空间包装器需要更改“子”线程中的堆栈指针以指向所需的堆栈,并避免向父堆栈写入任何内容。
实际上,clone
确实采用了堆栈指针参数,因为在返回用户空间后等待更改“子”中的堆栈指针是不安全的。除非信号全部被阻止,否则信号处理程序可以立即在错误的堆栈上运行,并且在某些体系结构上,堆栈指针必须有效并指向一个可以随时写入的区域。
不仅无法从C修改堆栈指针,而且还无法避免编译器在系统调用之后但在堆栈指针被更改之前破坏父堆栈的可能性。
答案 2 :(得分:2)
约瑟夫,回答你的上一个问题:
当用户创建一个“正常”的新进程时,这是由fork()完成的。在这种情况下,内核根本不必担心创建一个新堆栈,因为新进程是旧进程的完全重复,直到堆栈。
如果用户使用exec()替换当前正在运行的进程,那么内核确实需要创建一个新的堆栈 - 但在这种情况下这很容易,因为它可以从空白的平板开始。擦除进程的内存空间并重新初始化它,所以内核会说“在exec()之后,堆栈总是存在于此”。
但是,如果我们使用clone(),那么我们可以说新进程将与旧进程(CLONE_VM)共享一个内存空间。在这种情况下,内核不能像调用进程那样离开堆栈(就像fork()那样),因为那时我们的两个进程就会在彼此的堆栈上踩踏。内核也不能只将其置于默认位置(如exec()),因为该位置已经在此内存空间中占用。唯一的解决方案是允许调用进程为它找到一个位置,这就是它的作用。
答案 3 :(得分:1)
请注意,clone
系统调用不接受堆栈位置的参数。它实际上就像fork
一样。这只是glibc包装器,它接受了这个论点。
答案 4 :(得分:1)
这是mmaps堆栈区域和代码的代码。指示克隆系统调用将此区域用作堆栈。
#include sys/mman.h>
#include stdio.h>
#include string.h>
#include sched.h>
int execute_clone(void *arg)
{
printf("\nclone function Executed....Sleeping\n");
fflush(stdout);
return 0;
}
int main()
{
void *ptr;
int rc;
void *start =(void *) 0x0000010000000000;
size_t len = 0x0000000000200000;
ptr = mmap(start, len, PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE|MAP_FIXED|MAP_GROWSDOWN, 0, 0);
if(ptr == (void *)-1)
{
perror("\nmmap failed");
}
rc = clone(&execute_clone, ptr + len, CLONE_VM, NULL);
if(rc <= 0)
{
perror("\nClone() failed");
}
}
答案 5 :(得分:0)
mmap不仅仅是将文件映射到内存中。实际上,一些malloc实现将使用mmap进行大量分配。如果您阅读了精美的手册页,您会注意到MAP_ANONYMOUS标志,您将看到根本不需要提供文件描述符。
至于为什么内核不能只是“找到一堆可用内存”,如果你想让某人为你做这项工作,可以使用fork代替,也可以使用pthreads。
答案 6 :(得分:0)
我认为堆栈向下增长直到它无法增长,例如当它增长到之前已分配的内存时,可能会通知错误。可以看出默认值是最小可用堆栈大小,如果有的话当堆栈已满时冗余空间向下,它可以向下增长,否则,系统可能会通知故障。