为什么 Linux 有 4 层 "page tables" 以及它是如何工作的

Why Linux has 4 layers of "page tables" and how it works exactly

我正在尝试理解 page layouts according to these notes,而 刚刚 开始学习操作系统,所以我有点不知所措 atm。具体来说是这样说的:

In linux there are 4 'levels' of page tables. A table consists of an array of entries of type pXX_t, wrapping a pXXval_t:

  1. Page Global Directory (PGD) - pgd_t/pgdval_t.
  2. Page Upper Directory (PUD) - pud_t/pudval_t.
  3. Page Middle Directory (PMD) - pmd_t/pmdval_t.
  4. Page Table Entry directory (PTE) - pte_t/pteval_t.

These types are simply typedef'd wrappers around fundamental architecture-dependent types, gathering all the x86-64 types together...

然后他们画了这个:

    6         5         4         3         2         1
4321098765432109876543210987654321098765432109876543210987654321
0000000000000000000000000000000000000000101100010111000000010000
[   RESERVED   ][  PGD  ][  PUD  ][  PMD  ][  PTE  ][  OFFSET  ]
                         |        |        |        |-PAGE_SHIFT
                         |        |        |-----------PMD_SHIFT
                         |        |--------------------PUD_SHIFT
                         |---------------------------PGDIR_SHIFT

它仍然在我的脑海中,尤其是所有的缩写等等。

能否用更温和的语言解释一下我们这里到底有什么?我想在 JS 操作系统中实现虚拟内存分页,以模拟某种真实的操作系统。我想知道正在使用什么结构以及它们在内存本身中的实际外观(或者如果通过汇编完成,则explain/teach更容易使用)。

所以现在我正在尝试弄清楚如何(在 JavaScript 中)使用 1 array 来管理多个进程的所有内存。没有作弊和使用其他 JavaScript 数组或对象或其他任何东西,只有 1 个数组,然后是整数。我一直在试图弄清楚如何在不使用数组的情况下在内存中创建一个数组(一片内存)哈哈。然后为了让它变得更复杂,我需要在这个 1 数组中制作页面,这些页面映射到每个进程允许的内存部分。我正在尝试详细了解如何执行此操作,这些注释是我所看到的最接近实际完成的注释,但它们有点太详细了 atm。想知道是否可以帮助稍微简化解释,并帮助描绘如何在单个数组中创建 pages/process 映射。解释这个 Linux 系统将有助于形象化这一点。

This is virtual memory.

具体来说,Linux 使用 x86-64 HW page-table 布局将一个进程的虚拟地址 space 映射到物理内存,具有页面粒度,当 运行 在那个 ISA 上。 有图表。

这是一个基数树,将地址分成 9 位块,具有 12 个页面偏移位。 (12 + 4*9 = 48 位虚拟地址,需要正确符号扩展为 64 位)。 32 位 x86 页面 tables 使用 2 级,每级 10 位(12 + 2*10 = 32 位虚拟地址)。

在 TLB 未命中时,硬件会遍历此 table 以到达 PTE(页面 Table 条目)或 "invalid" 条目,在这种情况下它会引发 #PF 异常。


根据您的其他问题判断,您应该在了解堆栈工作原理等基础知识后,将其放入 很多 的待办事项列表中。

只是模拟一个进程,而不是整个系统,所以只需要考虑它的私有地址space。在真实的OS下,一个进程的地址space是虚拟的,直接对应一个大的JS数组。所以每个进程都有一个 JS 数组,保存它的内存(并单独注册,或作为该数组的一部分)。

这基本上是将虚拟内存留给 JS 实现,因为您 运行 在 JS 上而不是在真实硬件上。

除非您正在为某些实际 硬件 ISA 编写完整的模拟器,否则这应该没问题。

CPU 使用专用硬件(TLB)来缓存翻译。在 JS 中,您需要通过映射函数间接访问 every 内存,这会使您的源代码非常痛苦。您可以通过使用 map/dictionary 数据结构而不是实际页面 tables.

来使用 TLB 的 JS 版本

IDK 如果你能用 try{}catch 做任何事情来对应页面错误。

也许还可以考虑使用 TypedArray 来获取相同底层缓冲区的不同 "views"? IDK 如何帮助模拟其中有漏洞的非连续虚拟内存。我认为在 JS 中你会希望你的 "processes" 保持它们的虚拟内存使用非常密集/连续,否则你可能会浪费大量内存(并且不会检测到对 "unmapped" 页面的访问)。