现代操作系统普遍采用虚拟内存管理( Virtual Memory Management) 机制,这需
要MMU( Memory Management Unit,内存管理单元) 的支持。有些嵌入式处理器没有MMU,
则不能运行依赖于虚拟内存管理的操作系统。本节简要介绍MMU的作用和操作系统的虚拟内存
管理机制。
首先引入两个概念,虚拟地址和物理地址。如果处理器没有MMU,或者有MMU但没有启
用, CPU执行单元发出的内存地址将直接传到芯片引脚上,被内存芯片(以下称为物理内存,
以便与虚拟内存区分)接收,这称为物理地址( Physical Address,以下简称PA)
图 17.4. 物理地址
如果处理器启用了MMU, CPU执行单元发出的内存地址将被MMU截获,从CPU到MMU的地址
称为虚拟地址( Virtual Address,以下简称VA) ,而MMU将这个地址翻译成另一个地址发
到CPU芯片的外部地址引脚上,也就是将虚拟地址映射成物理地址。
图 17.5. 虚拟地址
注意,对于32位的CPU,从CPU执行单元这边看地址线是32条(图中只是示意性地画了4条地
址线),可寻址空间是4GB,但是通常嵌入式处理器的地址引脚不会有这么多条地址线,因为
引脚是芯片上十分有限而宝贵的资源,而且也不太可能用到4GB这么大的物理内存。事实上,
在启用MMU的情况下虚拟地址空间和物理地址空间是完全独立的,物理地址空间既可以小于也
可以大于虚拟地址空间,例如有些32位的服务器可以配置大于4GB的物理内存。我们说32位
的CPU,是指CPU寄存器是32位的,数据总线是32位的,虚拟地址空间是32位的,而物理地址
空间则不一定是32位的。物理地址的范围是多少,取决于处理器引脚上有多少条地址线,也取
决于这些地址线上实际连接了多大的内存芯片。
MMU将虚拟地址映射到物理地址是以页( Page) 为单位的,对于32位CPU通常一页为4KB。
例如, MMU可以通过一个映射项将虚拟地址的一页0xb7001000~0xb7001fff映射到物理地址的
一页0x2000~0x2fff,物理内存中的页称为物理页面或页帧( Page Frame) 。至于虚拟内存的哪
个页面映射到物理内存的哪个页帧,这是通过页表( Page Table) 来描述的,页表保存在物理
内存中, MMU会查找页表来确定一个虚拟地址应该映射到什么物理地址。总结一下这个过程:
1. 在操作系统初始化或者分配、释放内存时,会执行一些指令在物理内存中填写页表,然后
用指令设置MMU,告诉MMU页表在物理内存中的什么位置。
2. 设置好之后, CPU每次执行访问内存的指令都会自动引发MMU做查表和地址转换的操
作,地址转换操作完全由硬件完成,不需要用指令控制MMU去做。
我们在程序中使用的变量和函数都有各自的地址,程序被编译后,这些地址就成了指令中的地
址,指令中的地址被CPU解释执行,就成了CPU执行单元发出的内存地址,所以在启用MMU的
情况下,程序中使用的地址都是虚拟地址。一个操作系统中同时运行着很多进程,通常桌面上
的每个窗口都是一个进程, Shell是一个进程,在Shell下敲命令运行的程序又是一个新的进程,
此外还有很多系统服务和后台进程在默默无闻地工作着。由于有了虚拟内存管理机制,各进程
不必担心自己使用的地址范围会不会和别的进程冲突,比如两个进程都使用了虚拟地址0x0804
8000,操作系统可以设置MMU的映射项把它们映射到不同的物理地址,它们通过同样的虚拟地
址访问不同的物理页面,就不会冲突了。虚拟内存管理机制还会在后面进一步讨论。
MMU除了做地址转换之外,还提供内存保护机制。各种体系结构都有用户模式( User
Mode) 和特权模式( Privileged Mode) 之分,操作系统可以设定每个内存页面的访问权限,有
些页面不允许访问,有些页面只有在CPU处于特权模式时才允许访问,有些页面在用户模式和
特权模式都可以访问,允许访问的权限又分为可读、可写和可执行三种。这样设定好之后,
当CPU要访问一个VA时, MMU会检查CPU当前处于用户模式还是特权模式,访问内存的目的
是读数据、写数据还是取指令,如果和操作系统设定的页面权限相符,就允许访问,把它转换
成PA,否则不允许访问,产生一个异常( Exception) 。异常的处理过程和中断类似,只不过中
断是由外部设备产生的,而异常是由CPU内部产生的,中断产生的原因和CPU当前执行的指令
无关,而异常的产生就是由于CPU当前执行的指令出了问题,例如访问内存的指令被MMU检查
出权限错误,除法指令的除数为0等。
“中断”和“异常”这两个名词用得也比较混乱,不同的体系结构有不同的定义,有时候中断和异常
不加区分,有时候异常包括中断,有时候中断包括异常。在本书中按上述定义使用这两个名
词,中断的产生与指令的执行是异步( Asynchronous) 的,异常的产生与指令的执行是同步
( Synchronous) 的。
图 17.6. 处理器模式
通常操作系统把虚拟地址空间划分为用户空间和内核空间,例如x86平台的虚拟地址空间
是0x0000 0000~0xffff ffff,大致上前3GB( 0x0000 0000~0xbfff ffff)是用户空间,
后1GB( 0xc000 0000~0xffff ffff)是内核空间。用户程序在用户模式下执行,不能访问内核中
的数据,也不能跳转到内核代码中执行。这样可以保护内核,如果一个进程访问了非法地址,
顶多这一个进程崩溃,而不会影响到内核和其它进程。 CPU在产生中断或异常时会自动切换模
式,由用户模式切换到特权模式,因此跳转到内核代码中执行中断或异常服务程序就被允许
了。事实上,所有内核代码的执行都是从中断或异常服务程序开始的,整个内核就是由各种中
断处理和异常处理程序组成。
我们已经遇到过很多次的段错误是这样产生的:
1. 用户程序要访问的一个VA,经MMU检查无权访问。
2. MMU产生一个异常, CPU从用户模式切换到特权模式,跳转到内核代码中执行异常服务
程序。
3. 内核把这个异常解释为段错误,把引发异常的进程终止掉。
访问权限也是在页表中设置的,可以设定哪些页面属于用户空间,哪些页面属于内核空间,哪
些页面可读,哪些页面可写,哪些页面的数据可以当作指令执行等等。 MMU在做地址转换时顺
便检查访问权限。