在 linux x86 分页中。
每个进程都有自己的页面目录。
页表遍历从 CR3 指向的页目录开始。
每个进程共享内核页面目录内容
假设三句话是正确的,假设某个进程进入内核模式并更新他的内核页面目录内容(地址映射,访问权限等......)
问题。由于内核地址空间是在进程之间全局共享的,所以这个更新必须与其他进程的页面目录同步,对吧?
如何管理?
在 linux x86 分页中。
每个进程都有自己的页面目录。
页表遍历从 CR3 指向的页目录开始。
每个进程共享内核页面目录内容
假设三句话是正确的,假设某个进程进入内核模式并更新他的内核页面目录内容(地址映射,访问权限等......)
问题。由于内核地址空间是在进程之间全局共享的,所以这个更新必须与其他进程的页面目录同步,对吧?
如何管理?
我不了解Linux,所以我会回答Windows。一些内核空间是“全局的”,这是在 PTE 中设置的一个标志,表示它被多个进程使用。该INVPCID
指令可以在寄存器操作数中配置,以在 TLB 无效中包含或排除这些条目。这些页表条目在进程之间共享,并且都出现在每个进程的页表中的相同位置。这样,只需要更新单个 PTE,并且不需要同步其他进程的其他 PTE,因为它们都在物理地址上共享单个 PTE。
http://www.cs.miami.edu/home/burt/journal/NT/memory.html
一些内核内存对所有进程都不可见,并且对每个进程都是私有的(不会改变它仍然是 ring 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 页面。内核不会被标记为空闲列表。由于环回的数量可变,可以填写和访问 PDE 或 PTE。在 64 位 Windows 上,当一个进程终止时,该进程的所有物理页面都被移动到空闲列表中,其中包括所有用户物理 PDPT 页面、PD 页面、PT 页面和 PML4/CR3 页面。内核不会被标记为空闲列表。由于环回的数量可变,可以填写和访问 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 组合中的任何一种。它在宏中使用,具有此前缀的任何虚拟地址将根据定义分别成为 PTE 或 PDE 的一部分。Windows 为页表层次结构选择偏移量 1100000000,但它可以是 2^9 组合中的任何一种。它在宏中使用,具有此前缀的任何虚拟地址将根据定义分别成为 PTE 或 PDE 的一部分。Windows 为页表层次结构选择偏移量 1100000000,但它可以是 2^9 组合中的任何一种。
KAISER 或 KPTI 旨在缓解熔毁,cr3
每个过程很可能有 2 秒。进入内核后,用户模式的受限 cr3 将包含单个内核 PML4E(足以访问执行交换的初步中断调度例程函数)将被包含所有内核 PML4E 的完整 cr3 替换。
至于 Windows 上的物理内存,请参见此处:https ://superuser.com/a/1549970/933117
问题。由于内核地址空间在进程之间是全局共享的,所以这个更新必须与其他进程的页面目录同步,对吧?
如何管理?
第一的; 了解分页通常是 2 级或更多级别的表。例如(对于 80x86),对于最早的“普通 32 位分页”,有页表和页目录;对于当前的长模式,有第 4 级页映射、页目录指针表、页目录和页表。CR3 指向最高级别的表,并且每个虚拟地址空间(“进程”)必须不同。对于第二高级别表,可以将单个第二高级别表放入所有最高级别表中,如果这样做,对第二高级别表的任何更改都会自动更改每个虚拟地址空间。
这意味着(对于 80x86),对于最旧的“普通 32 位分页”,您可以将相同的“内核页表”放入所有虚拟地址空间(所有页目录)中,并且当您从该页表中添加/删除页面时将自动影响所有虚拟地址空间;对于当前的长模式,您可以将相同的页目录指针表放在所有虚拟地址空间(所有页映射级别 4 表)中,并且当您添加/删除页目录、页表或页时,它将自动影响所有虚拟地址空间.
这意味着您只需要某种方法来更改第二高级别的页表(或者,某种方法来更改所有最高级别的页表条目)。有多种方法可以做到这一点。最简单的是预分配。例如,如果您说“内核空间将始终为 N MiB”,您可以在启动期间为“N MiB”预分配所有第二高级别的表,并且永远不要更改它们(例如,对于长模式,您可以说该内核空间将是 512 GiB,预先分配一个“内核页面目录指针表”,并在创建虚拟地址空间时将其放入每个页面映射级别 4,然后依赖所有其他更改(到页面目录、页表等)自动影响所有虚拟地址空间的内核空间)。
然而,这只是表的变化而已。还有 2 个其他问题。
第一个“其他问题”是 CPU 的翻译后备缓冲区 (TLB);当(虚拟地址到物理地址)转换/s改变时需要刷新。大多数操作系统使用“惰性 TLB 击落”的组合(其中 CPU 使用来自 TLB 的错误信息会导致页面错误和页面错误处理程序无效并返回,因此导致页面错误的软件可以在不知道的情况下继续进行新的/正确的转换发生任何事情)和“多 CPU TLB 击落”(您向其他 CPU 发送处理器间中断,并且该中断处理程序使 TLB 条目无效)。
第二个“其他问题”是确保 CPU 不会尝试同时更改相同的内容。这通常最终成为在更高层次上解决的问题。例如,如果您获取某个数据结构的锁(在更改该数据结构中的某些内容之前)并意识到您需要为该数据结构分配/释放页面(当您尝试进行更改时);那么修改分页表的代码不需要关心不同的 CPU 同时更改页表,因为它知道更高级别的某些东西(数据结构的锁)已经确保不会发生这种情况。