17

我上了一门关于操作系统设计和概念的课程,现在我正在尝试彻底研究 Linux 内核。我有一个我无法摆脱的问题。在现代操作系统中,每个进程都有自己的虚拟地址空间(VAS)(例如,32 位系统中的 0 到 2^32-1)。这提供了许多优点。但在实施中,我在某些方面感到困惑。让我通过一个例子来解释它:

假设我们有两个进程 p1, p2; p1 和 p2 有自己的 VAS。一个地址0x023f4a54映射到不同的物理地址(PA),怎么可能呢?这种翻译是怎么做到的。我的意思是我知道翻译机制,但我无法理解当涉及不同进程的地址空间时,相同的地址被映射到不同的物理地址。

0x023f4a54 in p1's VAS => PA 0x12321321
0x023f4a54 in p2's VAS => PA 0x23af2341 # (random addresses)
4

6 回答 6

26

提供虚拟内存的 CPU 允许您设置 CPU 看到的内存地址到物理内存地址的映射,通常这是由称为 MMU 的硬件单元完成的。

操作系统内核可以对 MMU 进行编程,通常不会细化到单个地址,而是以页面为单位(通常为 4096 字节)。这意味着可以对 MMU 进行编程以将例如虚拟地址 0x1000-0x2000 转换为物理地址 0x20000-0x21000。

操作系统为每个进程保留一组这些映射,在调度进程运行之前,它会将该映射加载到 MMU 中,然后再将控制权切换回进程。这为不同的进程启用了不同的映射,并且没有什么能阻止这些映射将相同的虚拟地址映射到不同的物理地址。

就程序而言,这一切都是透明的,它只是在 CPU 上执行指令,并且由于 CPU 已设置为虚拟内存模式(分页模式),因此每次内存访问都由 MMU 翻译,然后才进入到内存的物理总线。

实际的实现细节很复杂,但这里有一些参考资料可能会提供更多的见解;

于 2010-08-08T19:08:57.143 回答
6

您的问题将虚拟地址与使用地址作为识别方式混淆了,因此理解的第一步是分离概念。

一个工作示例是 C 运行时库函数sprintf()。当正确地声明和调用时,它会作为共享对象模块与它所需的所有子功能一起被合并到程序中。的地址sprintf因程序而异,因为库是在可用的空闲地址中加载的。对于一个简单的hello world程序,sprintf 可能会加载到地址 0x101000。对于计算税收的复杂程序,它可能会在 0x763f8000 加载(因为主程序包含的所有令人讨厌的逻辑都在它引用的库之前)。从系统的角度来看,共享库仅在一个位置加载到内存中,但每个进程看到内存的地址窗口(地址范围)对于该可执行文件是唯一的。

当然,安全增强型 Linux (SELinux)的一些特性使情况变得更加复杂,这些特性随机化不同程序部分加载到内存中的地址,包括共享库映射。

---澄清---正如有人正确指出的那样,每个进程的虚拟地址映射是特定于每个进程的,与其文件描述符集、套接字连接、进程父子进程等没有什么不同。也就是说,p1可能映射地址 0x1000 到物理 0x710000,而 p2 将地址 0x1000 映射到页面错误,并且 p3 映射到物理 0x9f32a000 处的某个共享库。虚拟地址映射受到操作系统的仔细监督,可以说是为了提供交换和分页等功能,但也提供共享代码和数据以及进程间共享数据等功能。

于 2010-08-08T19:05:24.663 回答
5

有两种重要的数据结构处理分页:页表和 TLB。操作系统为每个进程维护不同的页表。TLB 只是页表的缓存。

现在,不同的 CPU 是不同的。x86 使用一个名为 CR3 的特殊寄存器直接访问页表,该寄存器指向正在使用的页表。MIPS 处理器对页表一无所知,因此操作系统必须直接使用 TLB。

一些 CPU(例如:MIPS)在 TLB 中保留一个标识符以将不同的进程分开,因此操作系统可以在进行上下文切换时更改控制寄存器(除非它需要重用标识符)。其他 CPU 在每次上下文切换中都需要完整的 TLB 刷新。因此,基本上,操作系统需要更改一些控制寄存器,并且可能需要清除 TLB(进行 TLB 刷新)以允许来自不同进程的虚拟地址映射到它们应该映射的任何物理地址。

于 2010-08-08T20:04:09.633 回答
2

感谢所有的答案。我不知道的实际一点是,不同进程的相同虚拟地址如何不会与彼此的物理通信对象发生冲突。我在下面的链接中找到了答案,每个进程都有自己的页表。

http://tldp.org/LDP/tlk/mm/memory.html

于 2010-08-09T14:10:18.477 回答
1

这种映射(虚拟地址到物理地址)由操作系统和 MMU 处理(参见@nos 的答案);这种抽象的意义在于 p1 “认为”它正在访问0x023f4a54,而实际上它正在访问0x12321321.

如果您回到您的课程,了解程序如何在机器代码级别上工作,那么 p1 将期望一些变量/函数/任何东西在0x023f4a54每次加载时都在同一个地方(例如)。操作系统将物理地址映射到虚拟地址提供了这种抽象。实际上,它并不总是被加载到相同的物理地址,但是只要它在相同的虚拟地址中,您的程序就不会在意。

于 2010-08-08T19:11:50.640 回答
0

我认为重要的是要记住每个进程都有自己的一组页表。当我认为整个系统只有一个页表时,我也很难理解这一点。

当特定进程引用其页表并尝试访问尚未映射到页框的页面时,操作系统会为该特定进程分配不同的物理内存并将其映射到虚拟地址。

于 2018-06-28T18:45:48.060 回答