St.*_*rio 3 c linux stack mmap linux-kernel
I'm trying to understand how stack works in Linux. I read AMD64 ABI sections about stack and process initialization and it is not clear how the stack should be mapped. Here is the relevant quote (3.4.1):
Stack State
This section describes the machine state that
exec(BA_OS) creates for new processes.
and
It is unspecified whether the data and stack segments are initially mapped with execute permissions or not. Applications which need to execute code on the stack or data segments should take proper precautions, e.g., by calling
mprotect().
So I can deduce from the quotes above that the stack is mapped (it is unspecified if PROT_EXEC is used to create the mapping). Also the mapping is created by exec.
The question is whether the "main thread"'s stack uses MAP_GROWSDOWN | MAP_STACK mapping or maybe even via sbrk?
Looking at pmap -x <pid> the stack is marked with [stack] as
00007ffc04c78000 132 12 12 rw--- [ stack ]
Run Code Online (Sandbox Code Playgroud)
Creating a mapping as
mmap(NULL, 4096,
PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE | MAP_STACK,
-1, 0);
Run Code Online (Sandbox Code Playgroud)
simply creates anonymous mapping as that is shown in pmap -x <pid> as
00007fb6e42fa000 4 0 0 rw--- [ anon ]
Run Code Online (Sandbox Code Playgroud)
我可以从上面的引用中推断出堆栈已映射
从字面上看,这意味着内存已分配。即存在从这些虚拟地址到物理页的逻辑映射。我们知道这一点是因为您可以使用pushorcall指令,而_start无需从用户空间进行系统调用来分配堆栈。
事实上,x86-64 System V ABI 指定 argc、argv 和 envp 在进程启动时位于堆栈上。
问题是“主线程”的堆栈是否使用
MAP_GROWSDOWN | MAP_STACK映射或者甚至可能使用viasbrk?
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也会使用该标志。请注意,在支持向上增长的堆栈的体系结构上,VM_GROWSUP使用 ,而不是VM_GROWSDOWN使用定义的内核进行编译CONFIG_STACK_GROWSUP。可以在此处找到在 Linux 内核中指定这些标志的代码行。
/proc/.../maps并且pmap不使用VM_GROWSDOWN- 它们依赖于地址比较。因此,他们可能无法准确确定主线程堆栈占用的虚拟地址空间的确切范围(请参阅示例)。另一方面,/proc/.../smaps查找VM_GROWSDOWN标志并将具有该标志的每个内存区域标记为gd。(虽然它似乎忽略了VM_GROWSUP。)
所有这些工具/文件都会忽略该MAP_STACK标志。事实上,整个 Linux 内核都会忽略这个标志(这可能就是程序加载器不设置它的原因。)用户空间只是为了面向未来而传递它,以防内核确实想要开始专门处理线程堆栈分配。
sbrk这里没有任何意义;堆栈与“中断”不连续,并且brk堆无论如何都会向上增长到堆栈。Linux 将堆栈放置在非常靠近虚拟地址空间顶部的位置。因此,当然不能使用(内核中的等效项)来分配主堆栈sbrk。
不,没有任何东西使用MAP_GROWSDOWN,甚至辅助线程堆栈也没有,因为它通常不能安全使用。
手册mmap(2)页上说MAP_GROWSDOWN“用于堆栈”是可笑的过时且具有误导性。请参阅如何在Linux上为clone()系统调用映射堆栈?。正如 Ulrich Drepper在 2008 年解释的那样,代码使用MAP_GROWSDOWN通常会被破坏,并建议从 Linux 和 glibc 标头中删除该标志mmap。(这显然没有发生,但 pthreads 在那之前就没有使用过它,如果有的话。)
MAP_GROWSDOWN设置VM_GROWSDOWN内核内部映射的标志。主线程还使用该标志来启用增长机制,因此线程堆栈可能能够以与主堆栈相同的方式增长:ulimit -s如果堆栈指针位于页面错误位置下方,则任意远(最多?)。(Linux 不需要“堆栈探针”来触及大型多页堆栈数组的每一页或alloca。)
(线程堆栈是预先完全分配的;只有正常的物理页延迟分配才能支持虚拟分配,以避免浪费线程堆栈的空间。)
MAP_GROWSDOWN映射也可以按照手册页描述的方式增长mmap:访问最低映射页面下方的“保护页面”也会触发增长,即使它位于红色区域底部下方。
但主线程的堆栈具有你无法获得的魔力mmap(MAP_GROWSDOWN)。 它保留了最多的增长空间,ulimit -s以防止随机选择mmap地址为堆栈增长造成障碍。这种魔力仅适用于内核程序加载器,它在 期间映射主线程的堆栈execve(),使其免受mmap(NULL, ...)随机阻塞未来堆栈增长的影响。
mmap(MAP_FIXED)仍然可能为主堆栈造成障碍,但如果您使用,MAP_FIXED您将 100% 负责不破坏任何东西。(如果涉及 MAP_FIXED,无限堆栈不能增长到超过初始 132KiB?)。 MAP_FIXED将替换现有的映射和保留,但其他任何内容都会将主线程的堆栈增长空间视为保留;。(我认为这是真的;值得尝试使用MAP_FIXED_NOREPLACE或只是非空提示地址)
看
pthread_create不用于MAP_GROWSDOWN线程堆栈,其他人也不应该使用。一般不使用。 默认情况下,Linux pthreads 为线程堆栈分配完整大小。这会消耗虚拟地址空间,但(直到实际触及)不会消耗物理页。
注释中的结果不一致Why is MAP_GROWSDOWN 映射不增长?(有些人发现它有效,有些人发现它在触摸返回值和下面的页面时仍然存在段错误)听起来像https://bugs.centos.org/view.php?id=4767 -MAP_GROWSDOWN甚至可能在外面有错误VM_GROWSDOWN使用标准主堆栈映射。
| 归档时间: |
|
| 查看次数: |
2892 次 |
| 最近记录: |