访问未分配的页面

Accessing unallocated page

本文关键字:分配 访问      更新时间:2023-10-16

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

我想知道这种分配何时发生。

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

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

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

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

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

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

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

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

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

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

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

操作系统将处理此问题。若要查看它的实际效果,需要将调试器检测或应用于操作系统内核代码,但该概念可能由以下人员看到:

int *p = new int[1000000];

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

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