我对此有点模糊 - 但我认为操作系统需要能够跟踪进程线程正在使用(或保留)虚拟地址空间中的哪些页面。对于程序员通过VirtualAlloc(或您的OS等效)明确请求的内存,这很容易。但是,随着线程执行时堆栈的增长/收缩,堆栈会溢出不同数量的页面。很明显,应用程序员还没有请求使用这些页面 - 那么谁来处理对操作系统的请求? C运行时?我不认为操作系统可以自动执行此操作。我缺乏汇编知识来转储可执行文件并检查自己..
答案 0 :(得分:6)
Linux是开源的,你可以看一下它的功能。
这是来自 arch / x86 / mm / fault.c ,__do_page_fault
函数处理(等待)页面错误:
vma = find_vma(mm, address);
if (unlikely(!vma)) {
bad_area(regs, error_code, address);
return;
}
if (likely(vma->vm_start <= address))
goto good_area;
if (unlikely(!(vma->vm_flags & VM_GROWSDOWN))) {
bad_area(regs, error_code, address);
return;
}
if (error_code & PF_USER) {
/*
* Accessing the stack below %sp is always a bug.
* The large cushion allows instructions like enter
* and pusha to work. ("enter $65535, $31" pushes
* 32 pointers and then decrements %sp by 65535.)
*/
if (unlikely(address + 65536 + 32 * sizeof(unsigned long) < regs->sp)) {
bad_area(regs, error_code, address);
return;
}
}
if (unlikely(expand_stack(vma, address))) {
bad_area(regs, error_code, address);
return;
}
find_vma
找到第一个映射区域,该区域在错误地址之上结束。如果故障高于起始地址,则这是对已映射内存的正常访问,因此无需执行任何操作(转到good_area
)。
否则,如果它不是一个可以长大的区域,我们就会出错。我们剩下的是在一个可以长大的区域内进行访问。这是堆栈的常见情况。
接下来,代码检查故障与堆栈指针的关系。通常通过首先移动堆栈指针来分配局部变量,但这不是存储器访问,因此不会产生错误。堆栈将在第一次访问时扩展,因此应该在堆栈指针之上。评论Accessing the stack below %sp is always a bug.
与此相关。然而,一些指令临时访问堆栈指针下方,因此为此做了一些限制。一些ABI也利用所谓的红色区域,该红色区域是堆栈指针下方的固定大小区域,可自由使用。您可以看到它认为在大约64kB的堆栈指针内的任何访问都是有效的访问。如果一切正常,那么堆栈实际上已经扩展。
答案 1 :(得分:3)
有几种可能性 - 堆栈具有固定的,预先分配的大小,或者它没有。在第一种情况下,根本没有什么可做的。您的程序可能会因为过度使用堆栈而崩溃 - 堆栈溢出。
第二种情况有点复杂,但仍然可以解决。程序加载器可以创建/分配初始堆栈,并在未映射的堆栈下方保留一些虚拟内存空间。对该内存的任何访问都将导致内存访问错误,允许运行时捕获/处理该错误并为堆栈分配额外的内存空间。
答案 2 :(得分:1)
可执行映像被分成的内存段(通常包括代码段,数据段和堆栈)是不变的。
因此,当操作系统创建进程并将可执行映像加载到内存中时,每个部分的大小和基址都是众所周知的。
如果进程需要的内存比当前可用的内存多,那么确实有些可执行映像可能会“留在外面”。
在这种情况下,一旦需要执行缺少的部分,操作系统就会将其提取到内存中。
但作为一名程序员,你完全没有注意到这一点(正如你对代码中变量的物理地址一样无视)。