访问未分配的页面

时间:2016-01-16 15:17:43

标签: c++ operating-system gdb virtual-memory

在虚拟内存的概念中,仅当在虚拟空间中使用相应页面时才分配物理页面帧。

我想知道何时进行此类分配。

我试图引用随机选择的一些地址,但大多数时候,它给我一个分段错误。我猜大多数页面都被标记为不使用,只是阅读页面不足以强制操作系统为我分配一个物理页面框架。 (我用GDB试过了)

2 个答案:

答案 0 :(得分:0)

操作系统将处理此问题。要查看它的实际运行,您需要对OS内核代码进行检测或应用调试器,但可以通过以下方式看到该概念:

int *p = new int[1000000];

这将分配大约4MB(1000页)的内存,但到目前为止还没有使用它们,因此它们都不会被物理分配和#34; (但也许第一个实际有,因为它可能用于存储分配的元数据)

p[2048] = 42; 

现在,操作系统将分配8192个字节的页面错误,一旦完成,就可以将值42写入该页面。

运行GDB不会显示此信息。除了它写入已经"承诺"的速度要慢得多。物理页面,它是不明显的 - 你可以通过写入100MB数据中的每个4K中的一个元素,或相同100MB中的前250000个条目 - 然后再写第二次来尝试这一点。两者都会在第二时间更快,但由于第二次没有发生页面错误,第一次情况下第二次会明显加快。

一个例子:

#include <iostream>
#include <chrono>
#include <functional>
#include <memory>

void measure(const std::string& test, std::function<void()> function)
{
    auto start_time = std::chrono::high_resolution_clock::now();

    function();

    auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now() - start_time);
    std::cout<<test<<" "<<static_cast<double>(duration.count()) * 0.000001<<" ms"<<std::endl;
}

const int NWRITES = 1024*1024;
const int PAGESTEP = 1024;
const int NINTS = NWRITES*PAGESTEP; /* 1024M * sizeof(int) = 4GB */

int main()
{
    std::unique_ptr<int[]> p(new int [NINTS]);

    measure("Every int", [&p](){ for(int i = 0; i < NWRITES; i++) p[i] = i; });
    measure("Every 4KB", [&p](){ for(int i = 0; i < NWRITES; i++) p[i*PAGESTEP] = i; });    
    measure("Every int", [&p](){ for(int i = 0; i < NWRITES; i++) p[i] = i; });
measure("Every 4KB", [&p](){ for(int i = 0; i < NWRITES; i++) p[i*PAGESTEP] = i; });
}

给出这样的东西:

Every int 10.3651 ms
Every 4KB 1856.2 ms
Every int 2.4179 ms
Every 4KB 84.1603 ms

答案 1 :(得分:0)

您描述的访问冲突是逻辑页面无效的结果;不是他们没有被访问的事实。在虚拟内存中创建实际页面需要几个分配步骤。

要理解这一点,您需要分离逻辑内存转换和虚拟内存的概念。

存储器管理单元呈现连续的逻辑地址空间。在该地址空间内,页面可以或可以不映射到物理页面帧。虽然地址空间可能是连续的,但有效页面的范围通常不是连续的。

内存管理单元使用PAGE TABLE将逻辑地址转换为物理地址。

处理器通常使用多个页表或嵌套页表(一个表引用另一个表引用另一个表来标识页框)。在前一种情况下,页表的长度可能短于整个地址范围。对于后者也是如此,但此外,页表引用嵌套表(可能有空条目)。

此页表结构是使用该过程创建的。具有页表ENTRY是具有映射的先决条件。在具有嵌套页表的系统上,可以通过添加条目来调整页表的大小。表的大小通常受系统参数或过程配额的限制。 (忽略Unix克隆,具有持久shell的系统和系统表)在进程启动时,页表条目不引用任何内容。

程序加载器执行页面映射的初始设置。这将设置页表,以便程序(由LINKER定义)在页表中具有有效地址。

在大多数情况下访问实际页面框架之前,不会将其映射到页面。但是,您不会在应用程序中看到它。如果页表指示页面有效并且您引用没有物理帧的页面,则会触发页面错误。然后,操作系统将创建页面框架映射并重新启动您的应用程序。 (虚拟记忆)

您的应用程序可以在运行时映射其他页面。 New和Malloc为您做幕后工作,但您也可以直接进行。创建此类映射时,您正在更改页表,以使逻辑页有效。然后,您通常必须引用操作系统的页面以将逻辑页面映射到物理页面框架。

(假设您没有尝试编写或执行)简而言之,您正在描述由于没有有效的页表条目而导致的逻辑内存转换失败 对于您尝试访问的页面。