Jos*_*vin 10 linux stack multithreading clone mmap
Linux上的clone()系统调用接受一个指向堆栈的参数,以供新创建的线程使用.显而易见的方法是简单地malloc一些空间并传递它,但是你必须确保你已经使用了大量的堆栈空间,因为该线程将使用(很难预测).
我记得在使用pthreads时我不必这样做,所以我很好奇它做了什么.我遇到了这个网站,它解释说,"Linux pthreads实现使用的最佳解决方案是使用mmap来分配内存,标志指定在使用时分配的内存区域.这样,内存分配给根据需要使用堆栈,如果系统无法分配额外的内存,则会发生分段违规."
我曾经听过mmap使用的唯一上下文是将文件映射到内存,实际上读取mmap手册页需要一个文件描述符.如何使用它来分配一堆动态长度来给clone()?这个网站真的很疯狂吗?;)
在任何一种情况下,内核都不需要知道如何为新堆栈找到一堆免费内存,因为在用户启动新进程时,它必须始终做什么?如果内核已经能够解决这个问题,为什么首先需要首先指定堆栈指针?
堆栈在其增长空间中不是,也绝不可能是无限的.与其他所有内容一样,它们位于进程的虚拟地址空间中,并且它们可以增长的量总是受到到相邻映射内存区域的距离的限制.
当人们谈论堆栈动态增长时,它们可能意味着两件事之一:
试图依赖该MAP_GROWSDOWN
标志是不可靠和危险的,因为它无法保护您不会mmap
在您的堆栈旁边创建一个新的映射,然后会被破坏.(参见http://lwn.net/Articles/294001/)对于主线程,内核自动保留堆栈下方的堆栈大小ulimit
的地址空间(不是内存),并防止mmap
分配它.(但要注意!有些破供应商修补内核禁用此行为导致随机内存损坏!)对于其他线程,你只是必须 mmap
的地址空间时创建它的线程可能需要为堆的整个范围.没有其他办法.你可以使它的大部分最初是不可写/不可读的,并在故障时改变它,但是你需要信号处理程序,这个解决方案在POSIX线程实现中是不可接受的,因为它会干扰应用程序的信号处理程序.(注意,作为扩展,内核可以提供特殊MAP_
标志来传递不同的信号,而不是SIGSEGV
非法访问映射,然后线程实现可以捕获并对此信号起作用.但Linux目前没有这样的功能. )
最后,请注意,clone
系统调用不会使用堆栈指针参数,因为它不需要它.系统调用必须从汇编代码执行,因为用户空间包装器需要更改"子"线程中的堆栈指针以指向所需的堆栈,并避免向父堆栈写入任何内容.
实际上,clone
确实采用了堆栈指针参数,因为在返回用户空间后等待更改"子"中的堆栈指针是不安全的.除非信号全部被阻塞,否则信号处理程序可以立即在错误的堆栈上运行,并且在某些体系结构上,堆栈指针必须是有效的并指向一直可以安全写入的区域.
不仅无法从C修改堆栈指针,而且还无法避免编译器在系统调用之后但在堆栈指针被更改之前破坏父堆栈的可能性.
你想要mmap的MAP_ANONYMOUS标志.而MAP_GROWSDOWN因为你想把它当作一个堆栈使用它.
就像是:
void *stack = mmap(NULL,initial_stacksize,PROT_WRITE|PROT_READ,MAP_PRIVATE|MAP_GROWSDOWN|MAP_ANONYMOUS,-1,0);
Run Code Online (Sandbox Code Playgroud)
有关详细信息,请参见mmap手册页.请记住,克隆是一个低级概念,除非你真的需要它提供的东西,否则你不打算使用它.它提供了很多控制 - 比如设置它自己的堆栈 - 以防你想要做一些欺骗(比如在所有相关进程中都可以访问堆栈).除非你有充分的理由使用clone,否则坚持使用fork或pthreads.
约瑟夫,回答你的最后一个问题:
当用户创建“正常”新进程时,这是通过 fork() 完成的。在这种情况下,内核根本不必担心创建新堆栈,因为新进程是旧进程的完整复制品,一直到堆栈。
如果用户使用 exec() 替换当前正在运行的进程,那么内核确实需要创建一个新的堆栈 - 但在这种情况下这很容易,因为它从一张白纸开始。exec() 会清除进程的内存空间并重新初始化它,因此内核会说“在 exec() 之后,堆栈始终驻留在此处”。
然而,如果我们使用clone(),那么我们可以说新进程将与旧进程共享内存空间(CLONE_VM)。在这种情况下,内核不能像在调用进程中那样离开堆栈(就像 fork() 所做的那样),因为这样我们的两个进程就会互相踩踏对方的堆栈。内核也不能只是将其放在默认位置(如 exec()),因为该位置已占用此内存空间。唯一的解决方案是允许调用进程为它找到一个位置,这就是它所做的。
归档时间: |
|
查看次数: |
5655 次 |
最近记录: |