机制:地址转换

机制:地址转换


对于一个进程来说,它只能看到自己的地址空间。在进程的角度,对内存的每次访问,都使用虚拟地址来访问内存;当然,这也是为了编程简单,在编程时,不必考虑真实的内存,程序运行在虚拟内存系统之上,在进程眼里,它使用的内存是地址空间。(实际的内存情况,交给虚拟内存系统将虚拟地址映射到物理地址)

然而,在内存的角度,要读取内存,必须使用物理内存所以,需要一种地址转换机制,在每次使用虚拟地址访问时,将这个虚拟地址转换为物理地址,从而对内存进行访问。

如何实现虚拟内存系统(虚拟化内存)

重定位:重定位就是把程序的逻辑地址空间变换成内存中的实际物理地址空间的过程。(其实就是地址转换的另一个名字

如图,是一个进程的地址空间(左),以及将将这个地址空间装入物理内存(右)的情况(不考虑分段的情况,先分析简单的情况):
地址空间装入内存后,称为重定位的进程(Relocated Process)

机制:地址转换

如图所示,整个个地址空间(虚拟地址为0KB~16KB)都被装入物理内存(物理地址32KB~48KB)。

现在问题来了,进程访问内存通过虚拟地址,虚拟地址1KB对应的物理地址是多少?
整个地址空间装入之后,虚拟地址和物理地址的值不相等,因此需要对地址进行转换(重定位),注意,地址空间装入内存后,虽然重定位的进程的物理地址并不等于在地址空间中的虚拟地址,但是,装入内存前后,相同的数据相对于地址空间首部和相对于重定位进程的首部,偏移量是一样的

有了上面的对应关系,就可以解决上面的问题。地址空间首部的虚拟地址是0KB,虚拟地址1KB相对于地址空间首部的偏移量为1KB-0KB=1KB。因此,同样的数据在重定位进程中,相对于重定位进程首部(32KB)的偏移量也是1KB,所以虚拟地址1KB对应的物理地址为32KB+1KB=33KB。

基于软件的重定位(静态重定位)

在早期,通过一个加载程序(loader)完成静态重定位,loader将程序加载到内存,使它成为一个进程。在加载的过程中,将虚拟地址重写到物理内存中期望的偏移量。(也就是说,在加载过程中,将虚拟地址改为物理地址,这也是基于偏移量不变实现的)。

举个例子,程序中有一条指令,movl 1000, %eax,这条指令是读取虚拟地址为1000的内存,将这个值放入%eax寄存器。假设进程/程序的地址空间从0开始,则偏移量为1000;当整个地址空间被加载到从3000开始的物理地址中时,加载程序loader重写这条指令movl 4000,%eax

静态重定位有许多的问题(相比于动态重定位)。虽然使用静态重定位可以实现虚拟内存的功能,但是没有满足安全效率

所以,需要通过动态重定位,它满足虚拟内存的三个目标:对程序/进程透明;安全;高效率。

基于硬件的重定位(动态重定位)

动态重定位,需要依赖硬件实现地址转换(重定位)。对于每个CPU来说,都需要两个寄存器:基址(base)寄存器界限(bound)寄存器。CPU中负责地址转换的部分称为MMU(Memory Management Unit)。

假设程序的地址空间是从0开始(这样,虚拟地址就是偏移量),当程序运行时,由操作系统决定这个地址空间的加载位置,并将这个位置的地址放入基址寄存器。进程每次对内存进行访问时,会把偏移量(如果地址空间从0开始,则偏移量的值等于虚拟地址)与基址寄存器的值相加,这样就得到了物理地址。

采用硬件支持的重定位,不仅效率更高,而且通过界限寄存器,保证内存访问的安全。界限寄存器保存地址空间的长度,只有偏移量小于界限寄存器的虚拟地址才可以被转换为物理地址,保证访问不会越界界限寄存器的另一种使用方式:记录地址空间装入内存后的边界值,即先将偏移量和基址寄存器相加,再与界限寄存器比较。
举个例子:
地址空间(从0开始)大小:128 KB,这个地址空间被装入物理地址为256KB开始的内存单元。装入后,基址寄存器的值为256KB,界限寄存器的值为128KB(地址空间的大小)。如果要访问虚拟地址为200KB的内存单元,由于偏移量200KB-0KB超过了界限寄存器的值,所以越界了,这时,会抛出一个异常;如果要访问虚拟地址为100KB的内存单元,因为偏移量100KB-0KB小于界限寄存器的值,所以没有越界,则物理地址=偏移量+基址寄存器=100KB+256KB=356KB

(动态重定位)硬件支持:总结

硬件要求 解释
特权模式 需要,以防用户模式的进程执行特权操作
基址/界限寄存器 每个CPU都需要一对寄存器来支持地址转换和界限检查
能够转换虚拟地址并检查是否越界 电路来完成地址转换和检查界限
修改基址/界限寄存器的特群指令 在进程运行之前,操作系统必须能狗设置这些值
注册异常处理程序的特权指令 操作系统必须告诉硬件,如果发生异常,执行哪些代码
能够触发异常 如果进程试图使用特权指令或越界的内存

硬件应该提供特权指令,用来修改基址寄存器和界限寄存器,允许操作系统在进程切换的时候改变他们。这些指令都是特权指令,只有在内核模式才能修改。(如果允许修改MMU,可能会越过自己的地址空间)

用户程序尝试非法访问内存时,CPU必须能够产生异常,并执行操作系统“预先安排”的异常处理程序。

(动态重定位)操作系统的支持

一个简单的虚拟内存系统,除了有硬件支持外,也离不开操作系统的支持。

操作系统的要求 解释
内存管理 需要为新进程分配内存
从终止的进程收回内存
一般通过空闲列表(free list)来管理内存
基址/界限管理 在上下文切换时正确设置和保存基址/界限寄存器
异常处理 当异常发生时执行的代码

内部碎片

再来看这张图:
机制:地址转换

如图,将整个地址空间装入内存后虚拟地址为4KB~16KB的部分不会被使用,但是在物理内存中,物理地址为36KB~46KB(虚拟地址为4KB~16KB)的空间并不空闲,这一部分就是内部碎片在地址空间中,内部碎片并没有分配给进程/程序使用;但是,当整个地址空间装入内存时,这部分不会使用的内部碎片也占用了物理内存,因此造成了浪费。

避免内部碎片的方式就是对代码分段,在下一篇博客介绍。

受限制的直接执行协议(动态重定位)

操作系统@启动(内核模式) 硬件 用户空间(用户模式)
初始化陷阱表 记住以下地址:
系统调用处理程序
时钟处理程序
非法内存处理程序
非常指令处理程序
开启中断时钟
开启时钟,在x ms后中断
初始化进程表
初始化空闲列表
为了启动进程A:
在进程表中分配条目
为进程分配内存
设置基址/界限寄存器
从陷阱返回(进入A)
恢复A的寄存器
转向用户模式
进程A执行
获取指令(需要读内存)
转换虚拟地址并取指令
执行指令
如果显式加载/保存:
确保地址不越界
转换虚拟地址并执行
加载/保存
时钟中断
转向内核模式
跳到中断处理程序
处理陷阱:
上下文切换(包括MMU的寄存器)
从陷阱返回(进入B)
恢复B的寄存器
转向用户模式
进程B执行
对内存非法的访问
触发访问越界
转向内核模式
跳到陷阱处理程序
处理异常:
决定终止进程B
收回B的内存
移除进程B的PCB