x86分页如何工作?


91

这个问题的目的是填补有关该主题的大量免费信息的空白。

我相信一个好的答案将适合一个大的SO答案,或者至少可以包含几个答案。

主要目标是向完整的初学者提供足够的信息,以便他们可以自己阅读手册,并能够理解与分页相关的基本OS概念。

建议的准则:

  • 答案应该是初学者友好的:
    • 具体的,但可能简化的示例非常重要
    • 欢迎使用所示概念的应用程序
  • 引用有用的资源是好的
  • 欢迎对操作系统如何使用分页功能进行少量讨论
  • 欢迎使用PAE和PSE说明
  • 欢迎小小进取x86_64

相关问题以及为什么我认为它们不是伪造的:


1
这应该标记为“常见问题”并标记为“社区Wiki”。
Kerrek SB 2013年

@KerrekSB我真的不知道如何处理这类问题。答案应该是社区维基吗?我找不到faq标签。
Ciro Santilli郝海东冠状病六四事件法轮功

3
我想说的简短答案是,“请阅读《英特尔手册》第3卷第4章:分页”。它非常清晰,简洁且编写得很好,而且没有任何权威性。
Kerrek SB

4
@KerrekSB我同意该手册清晰且权威,但是对于我来说,这太苛刻了,我需要一些简单而具体的示例+原理来更好地理解。
Ciro Santilli郝海东冠状病六四事件法轮功

Answers:


144

此答案的版本具有很好的TOC和更多内容

我将更正所报告的任何错误。如果您要进行大的修改或添加缺少的方面,请根据自己的答案进行修改,以得到应有的代表。较小的编辑可以直接合并。

样例代码

最小示例:https : //github.com/cirosantilli/x86-bare-metal-examples/blob/5c672f73884a487414b3e21bd9e579c67cd77621/paging.S

像编程中的所有其他内容一样,真正理解这一点的唯一方法是玩最少的示例。

使之成为“难题”的原因是,最小的示例很大,因为您需要制作自己的小型操作系统。

英特尔手册

尽管没有示例就无法理解,但请尽早熟悉这些手册。

英特尔在《英特尔手册第3卷系统编程指南-325384-056US 2015年9月》中描述了分页》中描述了分页。

特别有趣的是图4-4“具有32位分页的CR3和分页结构条目的格式”,其中给出了关键的数据结构。

MMU

分页由CPU的内存管理单元(MMU)部分完成。像许多其他处理器(例如x87协处理器APIC)一样,它最初是由单独的芯片提供的,后来又集成到CPU中。但是这个术语仍然被使用。

普遍事实

逻辑地址是“常规”用户区域代码中使用的内存地址(例如rsiin中的内容mov eax, [rsi])。

首先分段将它们转换为线性地址,然后分页,然后将线性地址转换为物理地址。

(logical) ------------------> (linear) ------------> (physical)
             segmentation                 paging

在大多数情况下,我们可以将物理地址视为对实际RAM硬件存储单元进行索引,但这并不是100%正确,因为:

分页仅在保护模式下可用。在保护模式下使用分页是可选的。寻呼是当且仅当PG的位cr0寄存器。

分页与细分

分页和分段之间的主要区别在于:

  • 分页将RAM分成大小相等的块(称为页面)
  • 分段将内存分成任意大小的块

这是分页的主要优点,因为大小相等的块使事情更易于管理。

分页变得非常流行,以至于在64位模式下x86-64中不再支持分段功能,这是新软件的主要操作模式,它仅以兼容模式存在,它模拟IA32。

应用

分页用于在现代OS上实现进程虚拟地址空间。使用虚拟地址,操作系统可以通过以下方式在单个RAM中容纳两个或多个并发进程:

  • 这两个程序都不需要了解其他
  • 两个程序的内存可以根据需要增加和缩小
  • 程序之间的切换非常快
  • 一个程序永远无法访问另一个进程的内存

过去,分页是在分段之后进行的,并且由于在管理固定大小的页面内存块而不是可变长度段时更容易管理,因此在现代操作系统(例如Linux)中将其很大程度上替换为实现虚拟内存。

硬件实施

与保护模式下的分段(修改分段寄存器会触发来自GDT或LDT的加载)类似,分页硬件使用内存中的数据结构来完成其工作(页表,页目录等)。

这些数据结构的格式由硬件确定,但是操作系统要在OS上正确设置和管理这些数据结构,并告诉硬件在哪里找到它们(通过cr3)。

其他一些体系结构几乎将分页完全留在软件手中,因此TLB未命中运行OS提供的功能来遍历页表并将新映射插入到TLB中。这使页面表格式可以由OS选择,但是使硬件不太可能通过x86可以通过乱序执行其他指令来重叠页面遍历

示例:简化的单级寻呼方案

这是简化页面调度的示例在x86体系结构版本上以实现虚拟内存空间。

页表

操作系统可以为他们提供以下页表:

操作系统为进程1提供的页表:

RAM location        physical address   present
-----------------   -----------------  --------
PT1 + 0       * L   0x00001            1
PT1 + 1       * L   0x00000            1
PT1 + 2       * L   0x00003            1
PT1 + 3       * L                      0
...                                    ...
PT1 + 0xFFFFF * L   0x00005            1

操作系统为进程2提供的页表:

RAM location       physical address   present
-----------------  -----------------  --------
PT2 + 0       * L  0x0000A            1
PT2 + 1       * L  0x0000B            1
PT2 + 2       * L                     0
PT2 + 3       * L  0x00003            1
...                ...                ...
PT2 + 0xFFFFF * L  0x00004            1

哪里:

  • PT1PT2:表1和2在RAM上的初始位置。

    示例值:0x000000000x12345678,等。

    决定这些值的是操作系统。

  • L:页表条目的长度。

  • present:指示该页面存在于内存中。

页表位于RAM上。例如,它们可以位于:

--------------> 0xFFFFFFFF


--------------> PT1 + 0xFFFFF * L
Page Table 1
--------------> PT1


--------------> PT2 + 0xFFFFF * L
Page Table 2
--------------> PT2

--------------> 0x0

两个页表在RAM上的初始位置是任意的,并由OS控制。操作系统要确保它们不会重叠!

每个进程都不能直接触摸任何页表,尽管它可以向OS发出导致页表被修改的请求,例如,请求更大的堆栈或堆段。

页面是4KB(12位)的块,并且由于地址具有32位,因此仅20位(20 + 12 = 32,因此以十六进制表示为5个字符)即可识别每个页面。此值由硬件固定。

页表条目

页表是...页表项的表!

表条目的确切格式由硬件固定。

在此简化示例中,页表条目仅包含两个字段:

bits   function
-----  -----------------------------------------
20     physical address of the start of the page
1      present flag

因此,在此示例中,硬件设计师可以选择L = 21

大多数实际页表条目都有其他字段。

将内存对齐为21位是不切实际的,因为内存可通过字节而不是位来寻址。因此,在这种情况下,即使只需要21位,硬件设计人员也可能会选择L = 32提高访问速度,而只保留其余位供以后使用。Lx86上的实际值为32位。

单级方案中的地址转换

一旦由OS设置了页表,线性和物理地址之间的地址转换就由硬件完成。

当操作系统要激活进程1时,它将OS设置cr3PT1,即进程1的表的开始。

如果进程1要访问线性地址0x00000001,则分页硬件电路将自动为OS执行以下操作:

  • 将线性地址分为两部分:

    | page (20 bits) | offset (12 bits) |
    

    因此,在这种情况下,我们将拥有:

    • 页面= 0x00000
    • 偏移量= 0x001
  • 查看Page表1,因为cr3指向它。

  • 查找条目,0x00000因为那是页面的一部分。

    硬件知道该条目位于RAM地址PT1 + 0 * L = PT1

  • 因为存在,所以访问是有效的

  • 按页表,页码的位置0x000000x00001 * 4K = 0x00001000

  • 要找到最终的物理地址,我们只需要添加偏移量即可:

      00001 000
    + 00000 001
      -----------
      00001 001
    

    因为00001是在表上查找的页面的物理地址,并且001是偏移量。

    顾名思义,偏移量总是简单地添加到页面的物理地址中。

  • 然后,硬件将在该物理位置获取内存。

同样,对于过程1,以下翻译将发生:

linear     physical
---------  ---------
00000 002  00001 002
00000 003  00001 003
00000 FFF  00001 FFF
00001 000  00000 000
00001 001  00000 001
00001 FFF  00000 FFF
00002 000  00002 000
FFFFF 000  00005 000

例如,访问地址时00001000,页面的部分是00001硬件知道它的页表项位于RAM地址:PT1 + 1 * L1因为页面部分),而这正是它会寻找它。

当OS要切换到进程2时,所需要做的就是cr3指向第2页。就这么简单!

现在,流程2将会发生以下翻译:

linear     physical
---------  ---------
00000 002  00001 002
00000 003  00001 003
00000 FFF  00001 FFF
00001 000  00000 000
00001 001  00000 001
00001 FFF  00000 FFF
00003 000  00003 000
FFFFF 000  00004 000

相同的线性地址将转换为不同进程的不同物理地址,仅取决于内部值cr3

这样,每个程序都可以期望其数据以开头0和结尾FFFFFFFF,而不必担心确切的物理地址。

页面错误

如果进程1尝试访问不存在的页面内的地址怎么办?

硬件通过页面错误异常通知软件。

然后通常由操作系统来注册异常处理程序,以决定必须执行的操作。

访问不在表上的页面可能是编程错误:

int is[1];
is[2] = 1;

但是在某些情况下(例如在Linux中),在某些情况下是可以接受的:

  • 该程序希望增加其堆栈。

    它只是尝试访问给定范围内的某个字节,如果操作系统满意,它将页面添加到进程地址空间。

  • 该页面已交换到磁盘。

    操作系统将需要在后台进程后面做一些工作,以将页面返回到RAM。

    OS可以根据页表条目其余部分的内容发现情况,因为如果清除了当前标志,则页表条目的其他条目完全留给OS所需。

    例如,在Linux上,当present = 0时:

    • 如果页表项的所有字段均为0,则为无效地址。

    • 否则,该页面已交换到磁盘,并且这些字段的实际值编码页面在磁盘上的位置。

在任何情况下,操作系统都需要知道哪个地址生成了页面错误,才能解决该问题。这就是为什么cr2每当发生页面错误时,优秀的IA32开发人员都将的值设置为该地址的原因。然后,异常处理程序可以调查cr2以获取地址。

简化版

简化现实,使该示例更容易理解:

  • 所有实际的寻呼电路都使用多级寻呼来节省空间,但这显示了一种简单的单级方案。

  • 页表仅包含两个字段:20位地址和1位存在标志。

    实际页表总共包含12个字段,因此省略了其他功能。

示例:多级分页方案

单级分页方案的问题在于它将占用过多的RAM:每个进程4G / 4K = 1M 。如果每个条目的长度为4个字节,那么每个进程ps -A | wc -l将占用4M的内存,这对于台式机来说甚至太高了:说我现在正在运行244个进程,因此需要大约1GB的RAM!

出于这个原因,x86开发人员决定使用减少内存使用的多级方案。

该系统的缺点是访问时间稍长。

在用于不带PAE的32位处理器的简单3级分页方案中,将32个地址位划分如下:

| directory (10 bits) | table (10 bits) | offset (12 bits) |

每个进程都必须有一个且只有一个页面目录与之关联,因此它将至少包含2^10 = 1K页面目录条目,这比单级方案所需的最小1M更好。

页表仅在操作系统需要时分配。每个页表都有2^10 = 1K页目录条目

页面目录包含...页面目录项!页目录条目与页表条目相同,不同之处在于它们指向页表的RAM地址而不是表的物理地址。由于这些地址只有20位宽,因此页表必须位于4KB页的开头。

cr3 现在指向当前进程的页面目录在RAM上的位置,而不是页面表。

从单个级别的方案中,页表条目完全不变。

页表与单级方案有所不同,因为:

  • 每个进程最多可包含1K页表,每页目录条目一个。
  • 每个页表仅包含1K条目,而不是1M条目。

在前两个级别(而不是说12 | 8 | 12)使用10位的原因是每个页表项的长度为4个字节。然后,Page目录和Page Tables的2 ^ 10项将很好地适合4Kb页面。这意味着为此目的分配和取消分配页面更快,更简单。

多级方案中的地址转换

操作系统给进程1的页面目录:

RAM location     physical address   present
---------------  -----------------  --------
PD1 + 0     * L  0x10000            1
PD1 + 1     * L                     0
PD1 + 2     * L  0x80000            1
PD1 + 3     * L                     0
...                                 ...
PD1 + 0x3FF * L                     0

操作系统在PT1 = 0x100000000x10000* 4K)下为进程1提供的页表:

RAM location      physical address   present
---------------   -----------------  --------
PT1 + 0     * L   0x00001            1
PT1 + 1     * L                      0
PT1 + 2     * L   0x0000D            1
...                                  ...
PT1 + 0x3FF * L   0x00005            1

操作系统在PT2 = 0x800000000x80000* 4K)下为进程1提供的页表:

RAM location      physical address   present
---------------   -----------------  --------
PT2 + 0     * L   0x0000A            1
PT2 + 1     * L   0x0000C            1
PT2 + 2     * L                      0
...                                  ...
PT2 + 0x3FF * L   0x00003            1

哪里:

  • PD1:RAM上进程1的页面目录的初始位置。
  • PT1PT2:RAM中进程1的页表1和页表2的初始位置。

因此,在此示例中,页面目录和页面表可以存储在RAM中,如下所示:

----------------> 0xFFFFFFFF


----------------> PT2 + 0x3FF * L
Page Table 1
----------------> PT2

----------------> PD1 + 0x3FF * L
Page Directory 1
----------------> PD1


----------------> PT1 + 0x3FF * L
Page Table 2
----------------> PT1

----------------> 0x0

让我们0x00801004逐步翻译线性地址。

我们假设cr3 = PD1,即它指向刚刚描述的页面目录。

在二进制中,线性地址为:

0    0    8    0    1    0    0    4
0000 0000 1000 0000 0001 0000 0000 0100

分组为10 | 10 | 12

0000000010 0000000001 000000000100
0x2        0x1        0x4

这使:

  • 页面目录条目= 0x2
  • 页表条目= 0x1
  • 偏移量= 0x4

因此,硬件将查找页面目录的条目2。

页面目录表表示该页面表位于0x80000 * 4K = 0x80000000。这是该过程的第一个RAM访问。

由于页表项是0x1,因此硬件会查看页表的项1 0x80000000,它告诉它物理页位于address 0x0000C * 4K = 0x0000C000。这是该过程的第二次RAM访问。

最后,分页硬件将偏移量相加,最终地址为0x0000C004

翻译地址的其他示例包括:

linear    10 10 12 split   physical
--------  ---------------  ----------
00000001  000 000 001      00001001
00001001  000 001 001      page fault
003FF001  000 3FF 001      00005001
00400000  001 000 000      page fault
00800001  002 000 001      0000A001
00801008  002 001 008      0000C008
00802008  002 002 008      page fault
00B00001  003 000 000      page fault

如果不存在页面目录条目或页面表条目,则会发生页面错误。

如果操作系统要同时运行另一个进程,它将为第二个进程提供一个单独的页面目录,并将该目录链接到单独的页面表。

64位架构

对于当前的RAM大小,64位仍然是太多的地址,因此大多数体系结构将使用较少的位。

x86_64使用48位(256 TiB),而传统模式的PAE已经允许52位地址(4 PiB)。

这48位中的12位已经为偏移量保留,剩下36位。

如果采用2级方法,则最佳分割将是两个18位级。

但这意味着页面目录将具有 2^18 = 256K条目,这将占用太多RAM:接近32位体系结构的单级分页!

因此,64位体系结构甚至可以创建更多的页面级别,通常为3或4。

x86_64在9 | 9 | 9 | 12方案中使用4个级别,因此高层仅占用2^9高层条目。

PAE

物理地址扩展。

如果使用32位,则只能寻址4GB RAM。

这开始成为大型服务器的局限性,因此英特尔向奔腾Pro引入了PAE机制。

为了缓解该问题,英特尔增加了4条新的地址线,以便可以寻址64GB。

如果启用了PAE,则页表结构也会更改。更改它的确切方法取决于PSE是打开还是关闭。

PAE通过的PAE位打开和关闭cr4

即使总的可寻址内存为64GB,单个进程仍然最多只能使用4GB。但是,操作系统可以将不同的进程放在不同的4GB块上。

PSE

页面大小扩展。

允许页面的长度为4M(如果启用了PAE,则为2M),而不是4K。

PSE通过的PAE位打开和关闭cr4

PAE和PSE页表方案

如果PAE和PSE处于活动状态,则使用不同的寻呼级别方案:

  • 没有PAE和PSE: 10 | 10 | 12

  • 没有PAE和PSE :10 | 22

    由于22位地址为4Mb,因此22是4Mb页面内的偏移量。

  • PAE,无PSE: 2 | 9 | 9 | 12

    两次使用9而不是10的设计原因是,现在条目不再适合32位,这些位全部由20个地址位和12个有意义或保留的标志位填充。

    原因是20位已经不足以表示页表的地址:由于需要向处理器添加4条额外的导线,因此现在需要24位。

    因此,设计人员决定将条目大小增加到64位,并使它们适合单个页面表,有必要将条目数减少到2 ^ 9,而不是2 ^ 10。

    开头的2是一个新的页面级别,称为页面目录指针表(PDPT),因为它指向页面目录并填写32位线性地址。PDPT也是64位宽。

    cr3现在指向的PDPT必须位于第四个4GB内存上,并以32位倍数对齐以提高效率。这意味着现在cr3具有27个有效位,而不是20个:32倍数的2 ^ 5 * 2 ^ 27完成前4GB的2 ^ 32。

  • PAE和PSE: 2 | 9 | 21

    设计师决定保留9位宽的字段以使其适合单个页面。

    剩下23位。为PDPT保留2,以使与PAE情况保持一致,而没有PSE,则使21偏移,这意味着页面的宽度为2M而不是4M。

TLB

转换超前缓冲区(TLB)是用于分页地址的缓存。

由于它是高速缓存,因此它具有许多CPU高速缓存的设计问题,例如关联性级别。

本节将描述具有4个单地址条目的简化的完全关联TLB。请注意,像其他缓存一样,真实的TLB通常也不是完全关联的。

基本操作

线性地址和物理地址之间发生转换后,它将存储在TLB中。例如,一个4条目TLB在以下状态下启动:

  valid   linear   physical
  ------  -------  ---------
> 0       00000    00000
  0       00000    00000
  0       00000    00000
  0       00000    00000

>指示要更换当前条目。

在页面线性地址00003转换为物理地址之后00005,TLB变为:

  valid   linear   physical
  ------  -------  ---------
  1       00003    00005
> 0       00000    00000
  0       00000    00000
  0       00000    00000

和第二平移后0000700009它变成:

  valid   linear   physical
  ------  -------  ---------
  1       00003    00005
  1       00007    00009
> 0       00000    00000
  0       00000    00000

现在,如果00003需要再次转换,则硬件首先查找TLB,并通过单个RAM访问找出其地址00003 --> 00005

当然,00000它不在TLB上,因为没有有效的条目包含00000为密钥。

更换政策

填满TLB时,较旧的地址将被覆盖。就像CPU缓存一样,替换策略是一个潜在的复杂操作,但是一种简单合理的启发式方法是删除最近最少使用的条目(LRU)。

使用LRU,从状态开始:

  valid   linear   physical
  ------  -------  ---------
> 1       00003    00005
  1       00007    00009
  1       00009    00001
  1       0000B    00003

添加0000D -> 0000A将给出:

  valid   linear   physical
  ------  -------  ---------
  1       0000D    0000A
> 1       00007    00009
  1       00009    00001
  1       0000B    00003

凸轮

使用TLB可使翻译速度更快,因为初始翻译每个TLB级别需要一次访问,这意味着在简单的32位方案上为2,而在64位体系结构上为3或4。

TLB通常实现为称为内容可寻址存储器(CAM)的昂贵类型的RAM。CAM在硬件上实现关联映射,即给定键(线性地址)的结构检索值。

映射也可以在RAM地址上实现,但是CAM映射可能比RAM映射所需的条目少得多。

例如,其中的地图:

  • 键和值都具有20位(在简单的分页方案的情况下)
  • 每次最多需要存储4个值

可以存储在具有4个条目的TLB中:

linear   physical
-------  ---------
00000    00001
00001    00010
00010    00011
FFFFF    00000

但是,要使用RAM实现此功能,必须具有2 ^ 20个地址

linear   physical
-------  ---------
00000    00001
00001    00010
00010    00011
... (from 00011 to FFFFE)
FFFFF    00000

这比使用TLB还要昂贵。

无效的条目

cr3改变,所有的TLB项目失效,因为一个新的进程的新的页表将被使用,因此它是不可能的,任何旧的条目有任何意义。

x86还提供了invlpg明确使单个TLB条目无效的指令。其他体系结构甚至为无效的TLB条目提供了更多指令,例如使给定范围内的所有条目无效。

一些x86 CPU超出了x86规范的要求,并且在修改页表条目和使用它(尚未在TLB中缓存)之间提供了超出其所保证的一致性。。显然Windows 9x依靠它来确保正确性,但是现代AMD CPU不能提供连贯的页面浏览。英特尔CPU可以这样做,即使它们必须检测到错误推测。利用这一点可能是个坏主意,因为可能没有太多收获,而且存在引发细微的时序敏感问题的巨大风险,这些问题将很难调试。

Linux内核使用

Linux内核广泛使用x86的分页功能,以实现快速的进程切换和少量数据碎片。

在中v4.2,查看以下内容arch/x86/

  • include/asm/pgtable*
  • include/asm/page*
  • mm/pgtable*
  • mm/page*

似乎没有定义表示页面的结构,只有宏:include/asm/page_types.h特别有趣。摘抄:

#define _PAGE_BIT_PRESENT   0   /* is present */
#define _PAGE_BIT_RW        1   /* writeable */
#define _PAGE_BIT_USER      2   /* userspace addressable */
#define _PAGE_BIT_PWT       3   /* page write through */

arch/x86/include/uapi/asm/processor-flags.h定义CR0,尤其是PG位的位置:

#define X86_CR0_PG_BIT      31 /* Paging */

参考书目

自由:

  • rutgers-pxk-416一章“内存管理:讲义”

    对旧操作系统使用的内存组织技术进行了良好的历史回顾。

非免费:

  • bovet05章“内存寻址”

    合理介绍x86内存寻址。缺少一些简单的好例子。


很好的答案,但是我仍然不确定如何确定LRU。每次访问除MRU以外的页面时调用OS似乎很昂贵。或者,我可以看到硬件对LRU的页表进行了重新排序,这对于并发程序可能很危险。这些都是正确的吗?发生页面错误时,操作系统如何知道LRU的哪个页面?
Keynan'3

@Keynan我认为这是由硬件完成的,因此花费的时间不是问题。至于并发,我不知道如何管理。我认为每个处理器只有一个CR3和缓存,并且操作系统必须确保内存页面不重叠。
西罗Santilli郝海东冠状病六四事件法轮功

1
real TLBs are not usually fully associative……The TLB is usually implemented as … CAM这两个陈述不矛盾吗?
2016年

>>> x86_64在9 | |中使用4个级别。9 | 9 | 12方案应该是9 | 9 | 9 | 9 | 12?
monklof

@monklof我认为这是正确的:9 9 9 12已经允许512gb的RAM。5级方案是仅针对服务器的较新开发,这在我的网站的答案中有所提及,它是最新的。
西罗Santilli郝海东冠状病六四事件法轮功

22

这是一个简短的高级答案:

x86处理器以几种可能的模式之一运行(大致:真实,受保护的64位)。每种模式都可以使用几种可能的内存寻址模型之一(但并非每种模式都可以使用每种模型),即:实模式寻址,分段寻址和线性寻址。

在现代世界中,仅在保护或64位模式下的平面线性寻址是相关的,并且这两种模式基本相同,主要区别在于机器字的大小以及因此可寻址的内存量。

现在,内存寻址模式赋予了机器指令的内存操作数一定的含义(例如mov DWORD PTR [eax], 25,,它将32dword的值25的整数(aka )存储到其地址存储在eax32位寄存器中的内存中)。在平面线性寻址中,此数字eax允许在单个连续范围内运行,范围从零到最大值(在我们的示例中为2 32  − 1)。

然而,平面线性寻址既可以分页不分页。如果不进行分页,则该地址直接指向物理内存。通过分页,处理器的内存管理单元(或MMU)将所需的地址(现在称为虚拟地址)透明地馈送到查找机制(即所谓的页表)中,并获得一个新值,该值被解释为物理地址。现在,即使用户仅看到虚拟地址,原始操作也将在物理内存中的这个新的转换地址上进行。

分页的主要好处是页面表由操作系统管理。因此,操作系统可以任意修改和替换页表,例如在“切换任务”时。它可以保留一个完整的页表集合,每个“进程”一个。每当它决定某个特定进程将在给定的CPU上运行时,它将进程的页表加载到该CPU的MMU中(每个CPU都有自己的MMU)。页表集)。结果是,每个进程都会看到自己的虚拟地址空间,无论操作系统必须为其分配内存时,哪些物理页面都空闲,它们看起来都是相同的。它永远不知道任何其他进程的内存,因为它无法直接访问物理内存。

页表是存储在普通内存中的嵌套树状数据结构,由OS写入但由硬件直接读取,因此格式是固定的。通过设置一个特殊的CPU控制寄存器以指向顶层表,将它们“加载”到MMU中。CPU使用称为TLB的高速缓存来记住查询,因此,由于TLB丢失的原因以及通常的数据高速缓存原因,重复访问相同的几页要比分散的访问快得多。经常看到术语“ TLB条目”用于引用页表条目,即使它们没有缓存在TLB中也是如此。

并且如果您担心某个进程可能只是禁用分页或尝试修改页表:这是不允许的,因为x86实现了特权级别(称为“环”),并且用户代码以太低的特权级别执行,因此不允许它可以修改CPU的页表。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.