19

众所周知,Windows 应用程序在 32 位系统上通常具有 2Gb 的私有地址空间。这个空间可以通过 /3Gb 开关扩展到 3Gb。

操作系统自己保留剩余的 4Gb。

我的问题是为什么?

在内核模式下运行的代码(即设备驱动程序代码)有自己的地址空间。为什么在一个独占的 4Gb 地址空间之上,操作系统还想为每个用户模式进程保留 2Gb 的空间?

我认为原因是用户模式和内核模式调用之间的转换。例如,调用NtWriteFile将需要内核调度例程的地址(因此系统在每个应用程序中保留 2Gb)。但是,使用SYSENTER,系统服务编号是否足以让内核模式代码知道正在调用哪个函数/服务?

如果您能向我解释为什么操作系统占用每个用户模式进程的 2Gb(或 1Gb)如此重要。

4

5 回答 5

23

两个不同的用户进程有不同的虚拟地址空间。由于虚拟↔物理地址映射不同,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 仍然超出范围.)

可能有什么样的错误?例如,假设您正在实现快速排序并有两个指针,ab指向数组的开头和结尾。如果用 选择中间作为主元(a+b)/2,只要两个地址都在 2GB 以下,它就可以工作,但如果它们都在上面,那么加法将遇到整数溢出,结果将超出数组。(正确的表达是a+(b-a)/2。)

顺便说一句,具有默认 3G/1G 用户/内核拆分的 32 位 Linux 历史上运行的程序的堆栈位于 2-3GB 范围内,因此任何此类编程错误都可能很快被清除。64 位 Linux 允许 32 位程序访问 0-4GB。

于 2009-07-12T01:33:40.480 回答
3

Windows(像任何操作系统一样)不仅仅是内核+驱动程序。

您的应用程序依赖于许多不仅仅存在于内核空间中的操作系统服务。有很多缓冲区、句柄和各种资源可以映射到您的进程自己的地址空间。每当您调用一个返回窗口句柄或画笔的 Win32 API 函数时,这些东西都必须分配到您的进程中的某个位置。因此,Windows 的一部分在内核中运行,是的,其他部分在它们自己的用户模式进程中运行,还有一些,您的应用程序需要直接访问的,被映射到您的地址空间。其中一部分是难以避免的,但一个重要的附加因素是性能。如果每个Win32 调用需要上下文切换,这将对性能造成重大影响。如果其中一些可以在用户模式下处理,因为它们所依赖的数据已经映射到您的地址空间,则可以避免上下文切换,并且可以节省相当多的 CPU 周期。

所以任何操作系统都需要留出一些地址空间。我相信 Linux 默认只为操作系统设置 1GB。

微软在 Windows 上选择 2GB 的原因曾经在 Raymond Chen 的博客上解释过。我没有链接,我不记得细节,但做出这个决定是因为 Windows NT 最初也是针对 Alpha 处理器的,而在 Alpha 上,做 50/50 确实有很好的理由分裂。;)

这与 Alpha 对 32 位和 64 位代码的支持有关。:)

于 2009-07-12T01:34:59.730 回答
2

在内核模式下运行的代码(即设备驱动程序代码)有自己的地址空间。

不,不是的。它必须与 x86 处理器上进程的用户模式部分共享该地址空间。这就是为什么内核必须总共保留足够的空间并限制地址空间的原因。

于 2009-07-12T01:21:05.723 回答
1

我相信最好的答案是操作系统设计者认为,当你不得不关心的时候,人们会使用 64 位 Windows。

但这里有一个更好的讨论

于 2009-07-12T00:57:44.933 回答
0

部分答案与微处理器架构的历史有关。这是我所知道的一些,其他人可以提供更多最新的细节。

Intel 8086 处理器具有用于内存的段偏移架构,提供 20 位内存地址,因此总可寻址物理内存为 1MB。

与那个时代的竞争处理器不同——比如 Zilog Z80——英特尔 8086 只有一个地址空间,它不仅必须容纳电子存储器,而且还必须容纳与键盘、串行端口、打印机端口和视频显示器等次要外围设备的所有输入/输出通信. (作为比较,Zilog Z80 有一个单独的输入/输出地址空间,带有专用的汇编操作码用于访问)

为不断扩大的外围扩展范围留出空间的需要导致最初决定将地址空间分割为 0-640K 的电子内存,以及从 640K 到“其他东西”(输入/输出、ROM、视频内存等) 1MB。

随着 x86 产品线的发展和演变,PC 也随之发展,类似的方案也被使用,以今天的 2G/2G 拆分 4G 地址空间结束。

于 2009-07-12T01:18:53.590 回答