当堆栈增长时,谁有责任从操作系统请求页面?

时间:2014-12-09 18:06:32

标签: c assembly operating-system

我对此有点模糊 - 但我认为操作系统需要能够跟踪进程线程正在使用(或保留)虚拟地址空间中的哪些页面。对于程序员通过VirtualAlloc(或您的OS等效)明确请求的内存,这很容易。但是,随着线程执行时堆栈的增长/收缩,堆栈会溢出不同数量的页面。很明显,应用程序员还没有请求使用这些页面 - 那么谁来处理对操作系统的请求? C运行时?我不认为操作系统可以自动执行此操作。我缺乏汇编知识来转储可执行文件并检查自己..

3 个答案:

答案 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)

可执行映像被分成的内存段(通常包括代码段,数据段和堆栈)是不变的。

因此,当操作系统创建进程并将可执行映像加载到内存中时,每个部分的大小和基址都是众所周知的。

如果进程需要的内存比当前可用的内存多,那么确实有些可执行映像可能会“留在外面”。

在这种情况下,一旦需要执行缺少的部分,操作系统就会将其提取到内存中。

但作为一名程序员,你完全没有注意到这一点(正如你对代码中变量的物理地址一样无视)。