我们可以在不访问其中的值的情况下更改数据块的虚拟内存地址吗?

Can we change virtual memory address of a block of data without accessing the values in it?

那么,在像 C++ 这样的任何高级语言中,我们都可以使用指针和引用来检查变量的地址,但我们真的可以更改它吗?例如,让我们说 int A 的地址为 1000h,int B 的地址为 1004h,以下是内存中的表示形式:-

1000 小时 1004h
一个
100 104

我可以交换他们的地址吗? (看起来像这样)。

1004h 1000 小时
一个 B
100 104

这可能吗?

注意:请将1000h视为虚拟地址,将100视为实际地址,您可以使用任何您想要的编程语言。

如果我理解有误,请告诉我。

不,您不能更改对象的地址。每个对象在其整个生命周期中都具有相同的存储区域

一般不会。 运行ning 进程中的变量没有名称。编译源代码时,编译器会识别变量声明并计划它们在内存中的分配。然后为每个变量分配其地址(如果变量是静态的,则可以是常量,即在整个过程执行期间分配一次,或者如果变量是自动的,则为相对的,即在每个代码块的每个条目上创建并销毁退出时)。然后这些地址被内置到使用变量的指令代码中。

因此,程序生成后名称 AB 将不再存在。当你 运行 你的程序在内部使用例如地址 0x1004 来访问一个四字节的数据,但它不知道任何 int B.

尽管没有任何意义,但交换内存地址是不可能的。地址或多或少是存储单元的序数。您无法更改单元格的顺序,就像您无法将沿街的第三栋房子变成第一栋一样。当然你可以(理论上)交换建筑物,就像你可以交换记忆单元的内容一样,但单元的序号将保持不变,类似于建筑物(或其地块)的序号。

你不能改变&a,但是你可以通过玩弄虚拟内存来改变a的内容。但不能只是 int a;如果不复制实际的内存内容,您只能更改整个页面大小的块。

可以潜在地用虚拟内存技巧交换这两个数组,作为另一种方法std::swap_range(a, a+1024, b),这可能会或可能不会更快。

alignas(4096) int32_t a[1024];     // assuming 4k page size and
alignas(4096) int32_t b[1024];     // CHAR_BITS=8 so sizeof(int32_t) = 4

也许只有更大的阵列才会更快,因为复制是 O(N),而操作页面 tables 有很大的固定成本(系统调用,跨内核的 TLB 击落)但每页的成本很小触摸,就像实际操作的数据量的 8 / 4096。 (例如,在 x86-64 上,每 4096 字节数据有 8 个字节的页面-table-条目。)或更少 large/hugepages.


每个真实世界的系统上的页面大小都(远)大于 4 个字节,因此在您的示例中,这两个对象都在同一个虚拟页面中。 4 字节页面大小是完全不切实际的,页面 space 与实际数据一样多 space,并且需要比缓存更大的 TLB。 (每 48-2 = 46 位虚拟页号的 ~40 位物理地址,每 4 个字节的地址 - space 您要覆盖。具有访问权限、脏权限和 R/W/X 权限.)

常见的页面大小范围从 4kiB (x86-64) 到 16k 或 64k,4k 小得令人不安(太多的 TLB 条目需要覆盖现代的大型工作集软件经常使用)。一些系统支持使用 (在基数树中更高)作为一个连续的大页面的大页面/大页面,例如x86-64 的 2M / 1G large/hugepages.


理论上可以要求 OS 将您的虚拟地址 space 重新映射到物理内存中的相同数据上,例如交换两个完整虚拟页面的内容,只需更新它们的页面-table-条目 (PTE) 以交换物理地址。 (并使当前和所有其他核心上的 TLB 条目无效:TLB 击落。)


Linux AFAIK 没有 API 来请求两个虚拟页面的映射交换,但它确实有 mremap(2)。 (mremap 是 Linux 特定的。其他 OSes 可能有类似的东西。ISO C++ 不需要虚拟内存,因此没有任何可移植操作它的函数)。

通过三个 mremap(MREMAP_FIXED) 调用和一个临时虚拟页面(您未使用或您知道未分配),您可以执行 tmp=a / a=b / b=tmp 交换,其中 ab 是整个(范围)页面的内容。

#define _GNU_SOURCE
#include <sys/mman.h>

// swap contents of pa[0..size] with pa[0..size]
// effectively mmap(tmp, MAP_FIXED) then munmap(tmp, size)
// size must be a multiple of system page size, and pointers must be page-aligned
void swap_page_contents(void *pa, void *pb, void *tmp, size_t size)
{
    // need to force moving, otherwise kernel will leave it in place because we aren't growing.
    void *ret = mremap(pa, size, size, MREMAP_MAYMOVE|MREMAP_FIXED, tmp);
    assert(ret == tmp);  // t2 != MAP_FAILED
    ret = mremap(pb, size, size, MREMAP_MAYMOVE|MREMAP_FIXED, pa);
    assert(ret != MAP_FAILED);
    ret = mremap(tmp, size, size, MREMAP_MAYMOVE|MREMAP_FIXED, pb);
    assert(ret != MAP_FAILED);
}

您可以将 tmp 分配给 mmap(MAP_PRIVATE|MAP_ANONYMOUS)。惰性分配意味着永远不会分配物理页面来支持该映射,并且 Linux 会将它放在您的虚拟地址 space 中未使用的某个地方。这个交换最终取消了它的映射,所以也许我应该把它放在这个函数中。但是如果你可以 确定 你的进程自上次交换以来没有映射任何新内存,你可以重用相同的 tmp。它不需要映射,您只需要知道它没有用于任何用途 else.

如果您传递错误的参数(不是页面对齐或重叠),这可能会失败并返回 EINVAL。所以也许它 return 是一个错误而不是断言,尽管如果 b 没有对齐那么它将在已经将 a 移动到 tmp.

之后失败

这也不是原子的或线程安全的pa暂时未映射,暂时我们有papb指向 pb 的原始内容。 MREMAP_DONTUNMAP 并没有真正帮助;它只适用于 MAP_PRIVATE|MAP_ANONYMOUS 映射(例如 malloc 会分配,但当然如果你四舍五入到页面的开头并交换它,你可能会破坏 malloc 的簿记元数据。)此外,DONTUNMAP 使旧映射读取为零,尽管手册页说您可以使用 userfaultfd(2) 安装处理程序来做其他事情(例如协助垃圾收集)。

显然你可以传递 old_size=0 来让它为数据创建另一个虚拟映射,但前提是原始映射是 MAP_SHARED 映射。所以你不能这样做让内核为任意映射选择一个未使用的页面范围 tmp,只有共享(可能是文件支持的)映射。


Linux 也有 remap_file_pages(2),它可以在 tmpfs 文件支持的 mmap 中复制页面映射,尽管该系统调用已被弃用并且显然总是使用“较慢的内核仿真”而不是它以前做的任何事情。无论如何,我认为它仍然不能交换,只能在更大的映射中为文件的一部分创建第二个映射。