【问题标题】:linux kernel page table updatelinux内核页表更新
【发布时间】:2014-10-27 08:52:13
【问题描述】:

在 linux x86 分页中。

  1. 每个进程都有自己的页面目录。

  2. 页表遍历从 CR3 指向的页目录开始。

  3. 每个进程共享内核页面目录内容

假设三句话是正确的,假设某个进程进入内核 模式并更新他的内核页面目录内容(地址映射,访问 权利等...)

问题。由于内核地址空间在进程之间全局共享, 此更新必须与其他进程的页面目录同步, 对吧?

如何管理?

【问题讨论】:

    标签: linux-kernel x86 kernel paging page-tables


    【解决方案1】:

    我不了解 Linux,所以我会回答 Windows。一些内核空间是“全局的”,这是在 PTE 中设置的一个标志,表示它被多个进程使用。 INVPCID 指令可以在寄存器操作数中配置,以在 TLB 无效中包含或排除这些条目。这些页表条目在进程之间共享,并且都出现在每个进程的页表中的相同位置。这样,只需要更新单个 PTE,不需要同步其他进程的其他 PTE,因为它们都在物理地址上共享单个 PTE。

    http://www.cs.miami.edu/home/burt/journal/NT/memory.html

    一些内核内存对所有进程不可见,并且对每个进程都是私有的(不会改变它仍然是环 0 的事实)。这在 32 位 Windows 系统上将是 0xC0000000–0xC0200000,其中包含所有用户空间 PTE 和 PDE,其中 0xC0000000 是 PTE_BASE,它允许等式

    #define MiGetPteAddress (x)    ((PMMPTE)(((((ULONG)(x)) >> 12) << 2) + (ULONG_PTR)MmPteBase))
    #define MiAddressToPte(x) MiGetPteAddress(x)
    

    优雅地将cr2 中的错误虚拟地址转换为 PTE 的地址。这是每个进程私有的,因为每个进程都具有相同的基本 PTE 分配基地址;如果它对所有进程可见,它将很快占用虚拟内存,因为每组页面都必须按顺序分配。它不需要对所有进程可见,因为一个进程对另一个进程的页表条目不感兴趣。页面错误总是在当前进程的上下文中处理,0xC0000000–0xC0200000 在每个进程上下文中意味着不同的东西。

    用于分配内核 PTE(用于内核地址)的内核空间 0xC0200000–0xC0400000 将是全局的,并由所有进程共享,但其中表示 0xC0000000–0xC0200000 的部分除外,根据我的计算,它将是 0xC0300000–0xC0300800,这是 PDE 的用户模式端,即 PDE_BASE = 0xC0300000–0xC0300FFF。

    然而,将用户 PDE 和内核 PDE 部分分开是不可能的,这样前者是私有的,后者是全局的(即使 0xC0300000–0xC0300800 私有(指向不同的物理地址)和 0xC0300000–0xC0300FFF 指向同一个每个进程的物理地址),因为整个 PDE 区域(0xC0300000–0xC0300FFF)将位于同一个物理帧上并构成cr3 指向的单个帧,而cr3 对于每个进程是不同的,这意味着整个 PDE 区域(所有 PDE)必须为每个进程私有(每个进程复制和安装)。如果内核页表页面(包含内核页表的页面)被调出并进入新的物理位置,则所有 PDE 都必须同步,因为所有进程在不同的 cr3 物理地址上都有副本,而不是相同的物理 PDE .我不确定它是如何(有效地)完成 ATM 的,因此最好限制不允许内核页表被分页并将它们放在非分页池中;这样,内核 PDE 将在所有 CR3 页面上保持不变。在 64 位上,可以强加内核 PDPT 不能被分页的限制。在 32 位 Windows 上,进程以物理 CR3 页面启动,其 PDE 位于偏移量 1100000000(base 2)*4 字节指向自身,它是硬写入的,可能通过短暂关闭 cr0 中的分页(因为写入不会在没有需要编写的递归条目的情况下成功,从而产生悖论)。请注意,PD 条目本身是涵盖范围 0xC0000000–0xC0400000 的页表,即它指向 1023 个页表和 1 个页目录(本身)(2^10 个条目),因此允许 PTE 通过其虚拟地址进行修改. CR3 页面之所以在 0xC0300000 是因为该地址具有相同的页目录和页表索引 1100000000 和 1100000000,因此它会自循环两次,因此产生 CR3 页面,您可以通过地址修改 PDE(还有其他像这样的特殊地址,例如 0xE0380000)。设置完成后,会进行适当的内核映射。在 64 位 Windows 上,与使用指向自身的单个 PML4 表页面设置进程类似,并且由于环回数量可变,因此可以填充和访问任何 PML4E、PDPTE、PDE 或 PTE。在 64 位 Windows 上,当一个进程终止时,该进程的所有物理页面都被移动到空闲列表中,其中包括所有用户物理 PDPT 页面、PD 页面、PT 页面和 PML4/CR3 页面。内核不会被标记为空闲列表。

    通常,如果您知道 PML4 中的哪个条目是物理 PML4 页面的递归条目,则可以计算出服务(用于转换)特定虚拟地址范围的 PTE 结构的虚拟地址和该范围内的特定虚拟地址。您将 PML4 中的偏移量(32 位为 10 位;64 位为 9 位)附加到其自身的条目中,附加到您要查找其服务 PTE 虚拟地址的虚拟地址的开头(这就是添加 0xC0000000在前面的 32 位方程中)并删除最后 12 位,然后通过将其乘以 8(或 4)将现在位于虚拟地址末尾的 PT 中的偏移量弥补为 12 位(因此右移 12和左移 3(或 2 对于 32 位条目))。 1 环回带走 1 层间接,你得到 PTE 的虚拟地址。 2个环回将为您留下用于转换该特定虚拟地址的PDE的虚拟地址,依此类推。 32 位窗口上的 PTE_BASE 是偏移量 110000000 左移以形成 32 位,而 PDE_BASE 是偏移量 110000000110000000 左移以形成 32 位。它在宏中使用,具有此前缀的任何虚拟地址将根据定义分别成为 PTE 或 PDE 的一部分。 Windows 为页表层次结构选择偏移量 1100000000,但它可以是 2^9 组合中的任何一种。

    KAISER 或 KPTI,旨在缓解崩溃,很可能每个流程有 2 个cr3s。在捕获到内核时,用户模式的受限 cr3 将包含单个内核 PML4E(足以访问执行交换的初步中断调度例程函数)将被包含所有内核 PML4E 的完整 cr3 替换。

    windows上的物理内存见这里:https://superuser.com/a/1549970/933117

    【讨论】:

      【解决方案2】:

      问题。由于内核地址空间在进程之间是全局共享的,所以这个更新必须与其他进程的页面目录同步,对吧?

      如何管理?

      首先;了解分页通常是 2 级或更多级别的表。例如(对于 80x86),对于最早的“普通 32 位分页”,有页表和页目录;对于当前的长模式,有第 4 级页映射、页目录指针表、页目录和页表。 CR3 指向最高级别的表,每个虚拟地址空间(“进程”)必须不同。对于第二高级别表,可以将单个第二高级别表放入所有最高级别表中,如果这样做,对第二高级别表的任何更改都会自动更改每个虚拟地址空间。

      这意味着(对于 80x86),对于最旧的“普通 32 位分页”,您可以将相同的“内核页表”放入所有虚拟地址空间(所有页面目录),并且当您从中添加/删除页面时页表它会自动影响所有的虚拟地址空间;对于当前的长模式,您可以将相同的页目录指针表放在所有虚拟地址空间(所有页映射级别 4 表)中,并且当您添加/删除页目录、页表或页时,它将自动影响所有虚拟地址空间.

      这意味着您只需要某种方法来更改第二高级别的页表(或者,某种方法来更改所有最高级别的页表条目)。有多种方法可以做到这一点。最简单的是预分配。例如,如果您说“内核空间将始终为 N MiB”,您可以在启动期间为“N MiB”预分配所有第二高级别的表,并且永远不要更改它们(例如,对于长模式,您可以说该内核空间将为 512 GiB,预先分配一个“内核页面目录指针表”,并在创建虚拟地址空间时将其放入每个页面映射级别 4,然后依赖所有其他更改(到页面目录,页表等)自动影响所有虚拟地址空间的内核空间)。我相信这是 Linux 使用的方法(部分原因是 Linux 在启动时使用了愚蠢的“将所有 RAM 映射到内核空间”安全灾难)。

      然而,这只是表的变化而已。还有两个问题。

      第一个“其他问题”是 CPU 的转换后备缓冲区 (TLB);当(虚拟地址到物理地址)转换/s改变时需要刷新。大多数操作系统使用“惰性 TLB 击落”的组合(其中 CPU 使用来自 TLB 的错误信息会导致页面错误和页面错误处理程序无效并返回,因此导致页面错误的软件可以在不知道的情况下继续进行新的/正确的转换发生任何事情)和“多 CPU TLB 击落”(您向其他 CPU 发送处理器间中断,并且该中断处理程序使 TLB 条目无效)。

      第二个“其他问题”是确保 CPU 不会尝试同时更改相同的内容。这通常最终成为在更高层次上解决的问题。例如,如果您获取某个数据结构的锁(在更改该数据结构中的某些内容之前)并意识到您需要为该数据结构分配/释放页面(当您尝试进行更改时);那么修改分页表的代码就不需要关心不同的 CPU 同时更改分页表,因为它知道更高级别的某些东西(数据结构的锁)已经确保不会发生这种情况。

      【讨论】:

        【解决方案3】:

        当内核更改页表条目时,这些更新必须以原子方式进行:

        在 64 位内核中,这可以使用 64 位内存操作方便地完成,而 i386 需要使用 CMPXCHG8。

        (Source)

        【讨论】:

        • 这不能回答问题。他问的是如何在所有进程页表中反映更改,因为它们都有内核映射。
        猜你喜欢
        • 2013-05-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-03-02
        • 2011-09-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多