问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

深入理解页面表:从虚拟内存到物理内存的映射之旅

创作时间:
作者:
@小白创作中心

深入理解页面表:从虚拟内存到物理内存的映射之旅

引用
CSDN
1.
https://m.blog.csdn.net/weixin_36829761/article/details/144925754

在计算机科学的世界里,页面表(Page Table)是一个至关重要的概念,它帮助我们理解如何将虚拟内存映射到物理内存。然而,这一概念常常让人感到困惑。通过对 xv6 操作系统的深入研究,我们可以将页面表视为一种特殊的哈希表,并逐步揭开它的神秘面纱。

🗺️ 什么是页面表?

页面表是一种专门的哈希表,用于将连续的虚拟内存映射到连续的物理内存。为了实现这一点,我们可以使用一个大型数组来存储页面表。多级页面表的引入则进一步优化了内存使用,允许我们进行懒惰的内存分配。

页面表的基本结构

在计算机中,虚拟内存和物理内存之间的映射是通过页面表来实现的。每个进程都有自己的页面表,这些页面表记录了虚拟地址与物理地址之间的关系。通过这种方式,操作系统可以有效地管理内存,确保每个进程都能获得所需的资源。

🛠️ 步骤一:将连续的虚拟内存映射到连续的物理内存

首先,我们需要简化问题,即将虚拟内存映射到物理内存。在编程中,我们可以使用哈希表来实现这一点。以下是一个简单的 C 语言示例,展示了如何通过哈希表实现虚拟地址到物理地址的映射:

uint64 virtual_memory_to_physical_memory(uint64 virtual_addr) {
  // 每个进程都有自己的哈希表,将相同的虚拟内存映射到不同的物理内存。
  HashMap map = current_process_map();

  return map[virtual_addr];
}

空间局部性与页面的概念

由于空间局部性,我们倾向于将连续的虚拟内存映射到连续的物理内存。为了实现这一点,我们将虚拟内存划分为较小的单元,称为页面(Page)。在一个页面内,所有的虚拟内存都映射到同一个物理内存页面,这样不仅满足了性能要求,还允许我们懒惰地分配物理内存。

假设页面大小为 4KB,那么虚拟地址的最后 12 位对于确定索引是无关紧要的。我们可以通过以下代码计算页面表的索引:

uint64 index = virtual_addr >> 12;

完整的映射函数

将虚拟地址转换为物理地址的完整函数如下:

uint64 virtual_addr_to_physical_addr(uint64 virtual_addr) {
  HashMap page_table = current_process_map();
  uint64 index = virtual_addr >> 12;
  uint64 offset = virtual_addr & 0xFFF;

  return page_table[index] + offset;
}

在这个函数中,我们首先从当前进程获取页面表,然后计算出虚拟地址的索引和偏移量,最后返回物理地址。

🏗️ 步骤二:在内存中表示页面表

接下来,我们需要考虑如何在内存中表示这个哈希表。由于 virtual_addr_to_physical_addr 函数将不同的虚拟地址映射到物理页面的起始地址,并且这些索引是连续的,因此我们可以使用数组来表示哈希表。每个数组项就是一个页面表项(Page Table Entry, PTE)。

在内存中,页面表的结构可以如下所示:

+---+
| Page Table Entry  |
+---+
| Page Table Entry  |
+---+
| Page Table Entry  |
+---+

🌐 步骤三:多级页面表

从上面的图示中,我们可以看到一个明显的问题:即使大多数虚拟内存可能未被使用,仍然需要分配一个庞大的页面表。例如,要将 64GB 的虚拟内存映射到 4KB 的页面,我们需要一个长度为 64

×

1024

×

1024

/

4

=

2

24

64 \times 1024 \times 1024 / 4 = 2^{24}

的数组。如果每个条目需要 8 字节,则页面表将占用 32MB 的内存。

为了减少内存使用,我们可以使用多级页面表。例如,采用二级页面表的设置,根页面表的条目可以指向二级页面表。使用 4KB 的页面来存储页面表,每个页面可以包含 2

12

2^{12}

个地址,指向其他页面表。每个二级页面表可以映射 512

×

4

KB

=

2

MB

512 \times 4 \text{KB} = 2 \text{MB}

的虚拟内存。因此,一个根页面表可以映射 2

×

512

=

1024

MB

2 \times 512 = 1024 \text{MB}

或 1GB 的虚拟内存。二级页面表的内存仅在必要时分配。

二级页面表的映射函数

二级页面表的完整映射函数如下:

uint64 virtual_addr_to_physical_addr(uint64 virtual_addr) {
  uint64 *root_page_table = current_process_map();
  uint64 root_index = (virtual_addr >> (9 + 12)) & 0x1FF;

  uint64 *page_table = root_page_table[root_index];
  uint64 page_table_index = (virtual_addr >> 12) & 0x1FF;

  uint64 offset = virtual_addr & 0xFFF;

  return page_table[page_table_index] + offset;
}

在这个函数中,我们首先获取根页面表,然后计算根索引和页面表索引,最后返回物理地址。通过这种方式,我们有效地减少了内存的占用。

🔍 其他方面

页面表还有许多其他方面,例如在 PTE 中存储页面权限,以及使用翻译后备缓冲区(Translation Lookaside Buffer, TLB)来缓存 PTE。这些主题超出了本文的范围,但在 xv6 教科书和 MIT 操作系统课程中都有详细讨论。

在计算机科学中,许多概念在最初看起来都很复杂。然而,通过将它们分解为更小的步骤,其底层思想变得清晰且易于管理。采取小步前进的策略,往往是理解复杂概念的金科玉律。

📖 参考文献

  1. xv6 Operating System Textbook
  2. MIT Operating Systems Course
  3. Computer Systems: A Programmer’s Perspective
  4. Operating System Concepts
  5. Modern Operating Systems

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号