如何在Linux上对clone()系统调用进行mmap映射?

Linux上的clone()系统调用需要一个指向堆栈的参数,供新创build的线程使用。 做到这一点的显而易见的方法是简单地malloc一些空间,并通过它,但是你必须确保你已经malloc'd尽可能多的堆栈空间,因为该线程将使用(很难预测)。

我记得使用pthreads时,我不必这样做,所以我很好奇它做了什么。 我遇到这个网站 ,它解释说:“Linux pthreads实现使用的最好的解决scheme是使用mmap分配内存,标记指定了使用时分配的内存区域,这样内存分配给如果系统不能分配额外的内存,则会发生分段违规。

我曾经听过mmap用过的唯一的上下文是将文件映射到内存中,并且确实读取了mmap手册页,它使用文件描述符。 这怎么可以用来分配一个dynamic长度的堆栈给clone()? 这个网站是疯了吗? ;)

无论哪种情况,内核都不需要知道如何为新的堆栈find一堆免费的内存,因为这是用户启动新进程时必须要做的事情。 为什么一个堆栈指针甚至需要首先指定,如果内核已经可以解决这个问题呢?

约瑟夫,回答你的最后一个问题:

当用户创建一个“正常”的新进程时,由fork()完成。 在这种情况下,内核根本就不用担心创建一个新的堆栈,因为新的进程是旧堆栈的完整副本,直到堆栈。

如果用户使用exec()替换当前正在运行的进程,那么内核确实需要创建一个新的堆栈 – 但在这种情况下,这很容易,因为它从一个空白的板块开始。 exec()清除进程的内存空间并重新初始化它,所以内核可以说“在exec()之后,堆栈总是在这里”。

但是,如果我们使用clone(),那么我们可以说新进程将与旧进程(CLONE_VM)共享一个内存空间。 在这种情况下,内核不能像调用过程那样离开堆栈(就像fork()那样),因为这样我们的两个进程就会在对方的堆栈上跺脚。 内核也不能把它放在一个默认的位置(如exec()),因为这个位置已经被占用在这个内存空间中。 唯一的解决方法是让调用进程为它找到一个地方,这就是它的作用。

在他们的成长空间中,堆叠不是也不可能是无限的。 和其他所有东西一样,它们生活在进程的虚拟地址空间中,它们的增长量总是受到到相邻映射内存区域的距离的限制。

当人们谈论动态增长的堆栈时,它们可能意味着两件事之一:

  • 堆栈的页面可能是写时复制的零页面,在执行第一次写入之前,这些页面不会被私人复制。
  • 堆栈区域的较低部分可能还没有被保留(并且因此不计入该进程的提交费用,即内核已经为该进程预留的物理内存/交换量),直到命中一个防护页面为止如果内核提交更多并移动守卫页面,或者在没有剩余内存的情况下杀死进程。

试图依赖MAP_GROWSDOWN标志是不可靠的和危险的,因为它不能保护你免受mmap创建一个新的映射,只是在你的堆栈旁边,然后将被破坏。 (请参阅http://lwn.net/Articles/294001/ )对于主线程,内核会自动保留堆栈下的地址空间 (而不是内存 )的堆栈大小ulimit ,并阻止mmap分配它。 (但要注意,一些破坏的供应商补丁内核会禁止这种行为,导致随机内存损坏!)对于其他线程,您只需创建线程在创建时可能需要的全部地址空间范围。 没有别的办法。 你可以使它大部分是不可写/不可读的,并改变错误,但是你需要信号处理程序,这个解决方案在POSIX线程实现中是不可接受的,因为它会干扰应用程序的信号处理程序。 (注意,作为一个扩展,内核可以提供特殊的MAP_标志来传递一个不同的信号,而不是非法访问映射的SIGSEGV ,然后线程的实现可以捕捉到这个信号,并且可以执行这个信号。特征。)

最后,请注意clone系统调用不需要堆栈指针参数,因为它不需要它。 系统调用必须从汇编代码执行,因为用户空间包装器需要更改“子”线程中的堆栈指针指向所需的堆栈,并避免将任何内容写入父代的堆栈。

实际上, clone确实需要一个堆栈指针参数,因为在返回用户空间之后等待在“子”中更改堆栈指针是不安全的。 除非信号全部被阻塞,否则信号处理程序可以立即在错误的堆栈上运行,并且在某些体系结构中,堆栈指针必须是有效的,并指向一个安全的区域以便随时写入。

不仅从C中修改堆栈指针是不可能的,而且你也无法避免编译器在系统调用之后但在堆栈指针被改变之前破坏父堆栈的可能性。

你需要MAP_ANONYMOUS标志为mmap。 和MAP_GROWSDOWN,因为你想使用它作为一个堆栈。

就像是:

 void *stack = mmap(NULL,initial_stacksize,PROT_WRITE|PROT_READ,MAP_PRIVATE|MAP_GROWSDOWN|MAP_ANONYMOUS,-1,0); 

有关更多信息,请参阅mmap手册页。 请记住,克隆是一个低层次的概念,除非你真的需要它提供的东西,否则你并不打算使用它。 它提供了很多的控制,比如设置它自己的堆栈,以防万一你想做一些技巧(比如在所有相关的进程中都可以访问堆栈)。 除非你有很好的理由来使用克隆,否则坚持fork或pthreads。

请注意, clone系统调用不会为堆栈位置采用参数。 它实际上就像fork一样工作。 这只是采取这个论据的glibc包装。

mmap不仅仅是将一个文件映射到内存中。 实际上,一些malloc实现将使用mmap进行大的分配。 如果你阅读了精细的手册页,你会注意到MAP_ANONYMOUS标志,你会发现你根本不需要提供文件描述符。

至于为什么内核不能“找到一堆可用的内存”,那么如果你想让别人为你做这个工作,可以用fork来代替,或者使用pthread。

我认为堆栈增长向下,直到它不能增长,例如,当它增长到之前分配的内存时,可能会通知故障。可以看出,默认值是最小可用堆栈大小,如果有冗余空间当堆栈满时向下,可以向下增长,否则系统可能会通知故障。

这里是mmap一个堆栈区域的代码,指示克隆系统调用将这个区域用作堆栈。

 #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"); } }