如何将不同进程的虚拟地址映射到不同的物理地址

dir*_*bit 14 kernel linux-kernel virtual-address-space

我参加了一个关于操作系统设计和概念的课程,现在我正在努力学习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)
Run Code Online (Sandbox Code Playgroud)

nos*_*nos 23

提供虚拟内存的CPU允许您在CPU将其视为物理内存地址时设置内存地址的映射,通常由称为MMU的硬件单元完成.

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

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

就程序而言,所有这一切都是透明的,它只是在CPU上执行指令,并且由于CPU已经设置为虚拟内存模式(分页模式),因此MMU会在每次访问内存之前对其进行转换.物理总线到内存.

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

  • 顺便说一下,感谢戈曼的书.确实非常好. (2认同)

wal*_*lyk 6

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

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

当然,安全增强型Linux(SELinux)的一些功能进一步使这变得复杂,后者将不同程序部分加载到内存中的地址随机化,包括共享库映射.

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

  • @Pushdown如上所述,操作系统为每个进程保留一个单独的映射,并在进程间切换时交换进出这些映射. (2认同)

nin*_*alj 5

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

现在,不同的CPU是不同的.x86使用一个名为CR3的特殊寄存器直接访问页表,该寄存器指向正在使用的页表.MIPS处理器对页表没有任何了解,因此操作系统必须直接与TLB一起工作.

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


dir*_*bit 2

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

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