我可以从上面的引号中推断出堆栈是映射的
字面意思只是分配了内存。即存在从这些虚拟地址到物理页面的逻辑映射。我们知道这一点,因为您可以在 _start 中使用 push 或 call 指令,而无需从用户空间进行系统调用来分配堆栈。
事实上,x86-64 System V ABI 指定 argc、argv 和 envp 在进程启动时位于堆栈中。
问题是“主线程”的堆栈是使用MAP_GROWSDOWN | MAP_STACK 映射还是使用sbrk?
ELF 二进制加载程序为主线程的堆栈设置_GROWSDOWN 标志,但不设置MAP_STACK 标志。这是内核内部的代码,它不通过常规的mmap系统调用接口。
(用户空间中没有任何东西使用mmap(MAP_GROWSDOWN),所以通常主线程堆栈是内核中唯一具有VM_GROWSDOWN标志的映射。)
用于堆栈的虚拟内存区域 (VMA) 的标志的内部名称称为VM_GROWSDOWN。如果您有兴趣,这里是用于主线程堆栈的所有标志:VM_GROWSDOWN、VM_READ、VM_WRITE、VM_MAYREAD、VM_MAYWRITE 和 VM_MAYEXEC。此外,如果指定 ELF 二进制文件具有可执行堆栈(例如,通过使用 gcc -z execstack 编译),则还使用 VM_EXEC 标志。请注意,在支持向上增长的堆栈的架构上,如果内核编译时定义了CONFIG_STACK_GROWSUP,则使用VM_GROWSUP 而不是VM_GROWSDOWN。在 Linux 内核中指定这些标志的代码行可以找到 here。
/proc/.../maps 和 pmap 不使用 VM_GROWSDOWN - 它们依赖于地址比较。因此,他们可能无法准确确定主线程堆栈占用的虚拟地址空间的确切范围(参见example)。另一方面,/proc/.../smaps 查找VM_GROWSDOWN 标志并将具有此标志的每个内存区域标记为gd。 (虽然它似乎忽略了VM_GROWSUP。)
所有这些工具/文件都会忽略MAP_STACK 标志。事实上,整个 Linux 内核都忽略了这个标志(这可能是程序加载器不设置它的原因。)用户空间只将它传递给未来验证,以防内核确实想要启动专门处理线程堆栈分配。
sbrk 在这里毫无意义;堆栈与“中断”不连续,brk 堆无论如何都会向上向堆栈增长。 Linux 将堆栈置于非常接近虚拟地址空间的顶部。所以当然不能用sbrk(在内核中的等价物)分配主堆栈。
不,没有任何东西使用MAP_GROWSDOWN,甚至没有辅助线程堆栈,因为它通常不能安全使用。
mmap(2) 手册页上写着 MAP_GROWSDOWN 是“用于堆栈”,这可笑地过时了,而且具有误导性。见How to mmap the stack for the clone() system call on linux?。作为 Ulrich Drepper explained in 2008,使用 MAP_GROWSDOWN 的代码通常会被破坏,并建议从 Linux mmap 和 glibc 标头中删除标志。 (这显然没有发生,但 pthreads 在那之前就没有使用过它,如果有的话。)
MAP_GROWSDOWN 为内核内部的映射设置VM_GROWSDOWN 标志。主线程也使用该标志来启用增长机制,因此线程堆栈可能能够以与主堆栈相同的方式增长:任意远(最多 ulimit -s?)如果堆栈指针位于缺页位置下方。 (Linux 不需要“堆栈探测器”来接触大型多页堆栈数组或alloca 的每一页。)
(线程堆栈在前面完全分配;只有物理页面的正常延迟分配才能支持该虚拟分配,以避免浪费线程堆栈空间。)
MAP_GROWSDOWN 映射也可以按照mmap 手册页描述的方式增长:访问最低映射页面下方的“保护页面”也会触发增长,即使它低于红色区域的底部。
但是主线程的堆栈有你不用mmap(MAP_GROWSDOWN)获得的魔力。它保留了高达ulimit -s的增长空间,以防止随机选择@ 987654380@ 地址从创建障碍到堆栈增长。这种魔法仅适用于内核程序加载器,它在execve() 期间映射主线程的堆栈,使其免受mmap(NULL, ...) 随机阻塞未来堆栈增长的影响。
mmap(MAP_FIXED) 仍然可以为主堆栈创建障碍,但如果您使用MAP_FIXED,您将 100% 负责不破坏任何东西。 (Unlimited stack cannot grow beyond the initial 132KiB if MAP_FIXED involved?)。 MAP_FIXED 将替换现有的映射和保留,但其他任何东西都会将主线程的堆栈增长空间视为保留;。 (我认为这是真的;值得尝试使用 MAP_FIXED_NOREPLACE 或只是一个非 NULL 提示地址)
见
pthread_create 不会将MAP_GROWSDOWN 用于线程堆栈,其他任何人也不应该使用。一般不使用。 Linux pthreads 默认为线程堆栈分配完整大小。这会消耗虚拟地址空间,但(直到实际触及)不消耗物理页面。
Why is MAP_GROWSDOWN mapping does not grow? 上 cmets 的不一致结果(有些人发现它有效,有些人发现它在触摸返回值和下面的页面时仍然存在段错误)听起来像 https://bugs.centos.org/view.php?id=4767 - MAP_GROWSDOWN 甚至可能在使用标准主堆栈VM_GROWSDOWN 映射的方式。