访问未分配的页面

Accessing unallocated page

在虚拟内存的概念中,只有在虚拟内存中使用相应的页面时才会分配物理页框space。

我想知道这样的分配是什么时候发生的。

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

OS 会处理这个问题。要实际查看它,您需要检测或将调试器应用于 OS 内核代码,但可以通过以下方式查看该概念:

int *p = new int[1000000];

这将分配大约 4MB(1000 页)的内存,但到目前为止 NONE 已被使用,因此其中 none 将是 "physically allocated"(但也许第一个实际上有,因为它可能用于存储分配的元数据)

p[2048] = 42; 

现在,OS 将分配一个 page-fault 8192 字节,一旦完成,值 42 就可以写入该页。

运行 GDB 不会显示这个。除了它比写入已经 "committed" 的物理页面慢得多这一事实之外,它是不明显的 - 您可以通过写入 100MB 数据中的每 4K 中的一个元素或相同的前 250000 个条目来尝试这一点100MB - 然后第二次写入。第二次都会更快,但由于 page-fault 第二次没有发生,第一种情况下的第二次会明显更快。

一个例子:

#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

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

理解这个需要拆分逻辑内存翻译和虚拟内存的概念。

一个内存管理单元提供了一个连续的逻辑地址space。在该地址 space 内,页面可能会或可能不会映射到物理页面框架。虽然地址 space 可能是连续的,但有效页面的范围通常是不连续的。

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

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

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

程序加载器进行页面映射的初始设置。这将设置页面 table,以便程序(由 LINKER 定义)在页面 table.

中具有有效地址

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

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

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