堆栈的增长空间不是,也永远不可能是无限的。像其他所有东西一样,它们存在于进程的虚拟地址空间中,它们可以增长的数量总是受到与相邻映射内存区域的距离的限制。
当人们谈论堆栈动态增长时,他们的意思可能是以下两件事之一:
- 堆栈的页面可能是写时复制零页面,在执行第一次写入之前不会生成私有副本。
- 堆栈区域的较低部分可能尚未保留(因此不计入进程的提交费用,即内核已占为进程保留的物理内存/交换量),直到命中保护页面,其中如果内核提交更多并移动保护页面,或者如果没有剩余内存可以提交,则终止进程。
尝试依赖MAP_GROWSDOWN
标志是不可靠且危险的,因为它无法保护您免受mmap
创建与堆栈相邻的新映射,然后该映射将被破坏。(参见http://lwn.net/Articles/294001/)对于主线程,内核自动保留堆栈大小ulimit
的地址空间(不是内存),并阻止mmap
分配它。(但要小心!一些损坏的供应商补丁内核禁用了这种行为,从而导致随机内存损坏!)对于其他线程,您只需在创建线程时必须 mmap
获得线程可能需要用于堆栈的整个地址空间范围。没有其他办法。你可以使其大部分最初不可写/不可读,并在出现故障时对其进行更改,但随后您需要信号处理程序,并且此解决方案在 POSIX 线程实现中是不可接受的,因为它会干扰应用程序的信号处理程序。(注意,作为扩展,内核可以提供特殊MAP_
标志来传递不同的信号,而不是SIGSEGV
非法访问映射,然后线程实现可以捕获并处理这个信号。但是 Linux 目前没有这样的功能。 )
最后,请注意clone
系统调用不接受堆栈指针参数,因为它不需要它。系统调用必须从汇编代码中执行,因为用户空间包装器需要更改“子”线程中的堆栈指针以指向所需的堆栈,并避免将任何内容写入父堆栈。
实际上,clone
确实需要一个堆栈指针参数,因为在返回用户空间后等待更改“子”中的堆栈指针是不安全的。除非信号全部被阻塞,否则信号处理程序可能会立即在错误的堆栈上运行,并且在某些体系结构上,堆栈指针必须有效并且始终指向可以安全写入的区域。
不仅无法从 C 中修改堆栈指针,而且您也无法避免编译器在系统调用之后但堆栈指针更改之前破坏父堆栈的可能性。