系统相关
首页 > 系统相关> > linux-使用“ push”或“ sub” x86指令时如何分配堆栈内存?

linux-使用“ push”或“ sub” x86指令时如何分配堆栈内存?

作者:互联网

我浏览了一段时间,试图例如在执行操作时了解如何将内存分配给堆栈:

push rax

或者移动堆栈指针为子例程的局部变量分配空间:

sub rsp, X    ;Move stack pointer down by X bytes 

我了解的是堆栈段在虚拟内存空间中是匿名的,即不是文件支持的.

我还了解的是,内核不会真正将匿名虚拟内存段映射到物理内存,直到程序实际对该内存段执行某些操作(即写入数据)为止.因此,尝试在写入该段之前先读取该段可能会导致错误.

在第一个示例中,如果需要,内核将在物理内存中分配一个帧页.
在第二个示例中,我假设内核不会在程序实际将数据写入堆栈堆栈段中的地址之前将任何物理内存分配给堆栈段.

我在正确的轨道上吗?

解决方法:

是的,您在这里的方向正确. sub rsp,X有点像“惰性”分配:在#PF页面错误异常之后,内核仅通过触摸新RSP上方的内存来执行任何操作,而不仅仅是修改寄存器.但是您仍然可以考虑“分配”内存,即可以安全使用.

So, trying to read that segment before writing to it may cause an error.

不,读取不会导致错误.无论是在BSS,堆栈还是mmap(MAP_ANONYMOUS)中,从未写入的匿名页面都将写时复制映射到一个/物理零页面.

有趣的事实:在微基准测试中,请确保您触摸了输入数组的内存的每一页,否则,实际上您会反复遍历同一物理4k或2M页的零,即使您仍然遇到TLB丢失,也会获得L1D缓存命中(以及软页面错误)! gcc会将malloc memset(0)优化为calloc,但是std :: vector实际上将写入所有内存,无论您是否愿意.全局阵列上的memset尚未优化,因此可以正常工作. (或者非零初始化的数组将在数据段中作为文件支持.)

注意,我忽略了映射和有线之间的区别.即访问是否将触发软/次页错误以更新页表,还是仅仅是TLB未命中,而硬件页表遍历将找到一个映射(到零页).

但是RSP之下的堆栈内存可能根本不会被映射,因此在不先移动RSP的情况下对其进行触摸可能是无效的页面错误,而不是用于解决写时复制的“次要”页面错误.

堆栈内存有一个有趣的变化:堆栈大小限制约为8MB(ulimit -s),但是在Linux中,进程第一个线程的初始堆栈很特殊.例如,我在hello-world(动态链接)可执行文件的_start中设置了一个断点,并为此查看了/ proc /< PID> / smaps:

7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0                          [stack]
Size:                132 kB
Rss:                   8 kB
Pss:                   8 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:         8 kB
Referenced:            8 kB
Anonymous:             8 kB
...

仅8kiB的堆栈已被引用,并由物理页支持.这是预料之中的,因为动态链接器不会使用很多堆栈.

甚至只有132kiB的堆栈被映射到进程的虚拟地址空间.但是特殊的魔术使mmap(NULL,…)阻止了在堆栈可能增长到的虚拟地址空间的8MiB中随机选择页面.

在当前堆栈映射之下但在堆栈限制causes the kernel to grow the stack mapping以内的内存(在页面错误处理程序中).

(但是only if rsp is adjusted first仅比rsp低128个字节,因此ulimit -s unlimited不会使rsp以下1GB的触摸内存增长到此处的堆栈but it will if you decrement rsp to there and then touch memory.)

这仅适用于初始/主线程的堆栈. pthreads仅使用mmap(MAP_ANONYMOUS | MAP_STACK)映射无法增长的8MiB块.
 (MAP_STACK当前为空操作.)因此,分配后线程堆栈无法增长(除非在其下方有空间,否则使用MAP_FIXED进行手动操作除外),并且不受ulimit -s unlimited的影响.

对于mmap(MAP_GROWSDOWN),这种防止其他事情在堆栈增长区域中选择地址的魔力不存在,因此do not use it to allocate new thread stacks.(否则,您可能最终会耗尽新堆栈下面的虚拟地址空间,从而无法使用该地址)增长).只需分配完整的8MiB.另请参见Where are the stacks for the other threads located in a process virtual address space?.

MAP_GROWSDOWN确实具有按需增长功能described in the mmap(2) man page,但是没有增长限制(除了接近现有映射之外),因此(根据手册页)它基于Windows使用的保护页面,而不是主线程的堆栈.

在MAP_GROWSDOWN区域底部下方触摸多个页面的内存可能会导致段错误(与Linux的主线程堆栈不同).面向Linux的编译器不会生成堆栈“探针”来确保在分配大量内存(例如本地数组或alloca)后按顺序触摸每个4k页面,因此这是MAP_GROWSDOWN对于堆栈而言不安全的另一个原因.

编译器确实会在Windows上发出堆栈探针.

(MAP_GROWSDOWN甚至可能根本无法工作,请参见@BeeOnRope’s comment.将其用于任何用途都不是非常安全的,因为如果映射关系变得越来越接近,则可能会发生堆栈冲突安全漏洞.因此,请勿将MAP_GROWSDOWN用于任何其他用途.我我将不去描述Windows使用的保护页机制,因为很有趣的是,Linux的主线程堆栈设计并不是唯一的可能.)

标签:linux,memory,x86-64,memory-management,red-zone
来源: https://codeday.me/bug/20191011/1894087.html