两个不同的用户进程有不同的虚拟地址空间。由于虚拟↔物理地址映射不同,TLB缓存在从一个用户进程切换到另一个用户进程时失效。这是非常昂贵的,因为如果没有已经缓存在 TLB 中的地址,任何内存访问都会导致故障和PTE的遍历。
系统调用涉及两个上下文切换:用户→内核,然后内核→用户。为了加快速度,通常会保留前 1GB 或 2GB 的虚拟地址空间供内核使用。因为虚拟地址空间不会在这些上下文切换之间发生变化,所以不需要 TLB 刷新。这是由每个 PTE 中的用户/主管位启用的,它确保内核内存只能在内核空间中访问;即使页表相同,用户空间也无权访问。
如果硬件支持两个独立的 TLB,其中一个专门供内核使用,那么这种优化将不再有用。但是,如果您有足够的空间来奉献,那么只制作一个更大的 TLB 可能更值得。
x86 上的 Linux 曾经支持一种称为“4G/4G 拆分”的模式。在这种模式下,用户空间可以完全访问整个 4GB 的虚拟地址空间,内核也拥有一个完整的 4GB 虚拟地址空间。如上所述,成本是每个系统调用都需要 TLB 刷新,以及在用户和内核内存之间复制数据的更复杂的例程。经测量,这会导致高达 30% 的性能损失。
自从最初提出并回答这个问题以来,时代已经发生了变化:64 位操作系统现在更加普遍。在 x86-64 上的当前操作系统中,用户程序允许从 0 到 2 47 -1 (0-128TB) 的虚拟地址,而内核永久驻留在从 2 47 ×(2 17 -1) 到 2 64 -1的虚拟地址中(或从 -2 47到 -1,如果您将地址视为有符号整数)。
如果在 64 位 Windows 上运行 32 位可执行文件会发生什么?您会认为从 0 到 2 32 (0-4GB) 的所有虚拟地址都很容易获得,但是为了避免暴露现有程序中的错误,32 位可执行文件仍然限制为 0-2GB,除非它们使用/LARGEADDRESSAWARE
. 对于那些,他们可以访问 0-4GB。(这不是一个新标志;同样适用于使用/3GB
交换机运行的 32 位 Windows 内核,它将默认的 2G/2G 用户/内核拆分更改为 3G/1G,当然 3-4GB 仍然超出范围.)
可能有什么样的错误?例如,假设您正在实现快速排序并有两个指针,a
并b
指向数组的开头和结尾。如果用 选择中间作为主元(a+b)/2
,只要两个地址都在 2GB 以下,它就可以工作,但如果它们都在上面,那么加法将遇到整数溢出,结果将超出数组。(正确的表达是a+(b-a)/2
。)
顺便说一句,具有默认 3G/1G 用户/内核拆分的 32 位 Linux 历史上运行的程序的堆栈位于 2-3GB 范围内,因此任何此类编程错误都可能很快被清除。64 位 Linux 允许 32 位程序访问 0-4GB。