访问未分配的页面
Accessing unallocated page
在虚拟内存的概念中,物理页面帧仅在虚拟空间中使用相应的页面时才分配。
我想知道这种分配何时发生。
我试图引用一些随机选择的地址,但大多数时候,它给了我一个分段错误。我想大多数页面都被标记为未使用,仅仅阅读页面不足以迫使操作系统为我分配物理页面框架。(我用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
- 访问动态分配列表中的元素
- 0xC0000005:访问冲突写入位置0xCDCDCDCD动态分配错误
- C++ 将元素分配给映射值时访问错误
- 将静态字符数组中的字符分配给动态分配的字符数组 - 访问冲突
- 创建一个类来访问和指定向量类型,并构建一个获取位置并为其分配区域的类
- 分配/访问2d阵列,使得2d子块是连续的
- 是否可以在堆中分配向量,但通过引用进行访问
- 即使在离开作用域后,如何访问在堆上分配的变量C++?
- C++中的内存分配(引发异常:读取访问冲突)
- 在尝试使用CUDA分配内存时,我遇到了访问冲突写入位置错误
- 如何在构造函数中访问类变量以分配它们,而无需在C++中使用此指针
- 分配一个(固定大小)矢量<矢量<double>>以便高效访问并添加它们?
- 为什么在 C++ 执行删除操作后仍可以访问释放的动态分配的内存
- 从私人矢量中公开访问和分配操作员
- 从函数中删除动态内存分配,但无法访问变量
- 如何访问使用 omp_target_alloc() 分配的设备内存
- 在动态分配矩阵上的访问违规读数位置
- 将指针作为参数分配给类变量,然后使用它抛出读取访问违规
- 隐藏的成员变量不应在仅允许const访问的基类中突变,以便保留分配运算符
- 好的设计?用于分配的智能指针与用于访问的原始指针相结合