历史
在早期的计算机,程序是直接运行在物理内存上的,也就是说,程序所访问的地址都是物理内存。但是我们往往需要同时运行多个程序,所以就有一个很明显的问题如何把计算机上有限的物理内存分配给多个程序使用?
举个例子
假如我们计算机有128M内存,现在:
- 程序A需要10M内存
- 程序B需要100M内存
- 程序C需要20M内存
如果我们需要同时运行A和B,那么比较直接的做法是这样的,0-10M分给A,10-110M分给B,如下:
2016-06-04-1
这样分配,问题会有很多,大致有以下三个:
-
地址空间不隔离: 恶意程序可以很容易改写其他程序的内存数据,如A可以通过类似
*(p + 1024 * 1024 * 10) = xxx
来访问到B里的内存,这是很危险的。 - 内存使用率低: 比如我们在A、B同时运行的时候,突然要运行C。这个时候内存已经不够,我们可以采取的一个办法是把某个程序的数据暂时写到磁盘里,等到需要的时候再读回来。这个时候问题来了,由于地址空间是连续的,我们取出A也没法把C放入内存运行,只能取出B,可以看到这是一个巨大的IO操作,会导致效率十分低下。
- 程序运行的地址不确定:因为程序每次需要装入运行时我们都需要一块足够大的空闲区域,这个区域是不确定的,就需要程序重定位。
怎么办?还是那个熟悉的配方,熟悉的味道:增加中间层。
一切计算机的问题都可以通过增加中间层来解决。
思想:我们把程序的地址视为一种虚拟地址,然后通过映射的方式将这个虚拟地址转换成实际的物理地址。只要我们妥善的处理映射的方式,就能达到空间隔离的效果。
分段管理
这个时候分段的管理方法就出现了,先上图:
2016-06-04-2
分段管理:把一段程序所需要的内存大小的虚拟空间映射到某个地址空间。
零散论点:分段的虚拟空间大小不一定相等,这是一个和分页不同的特点。
如上图,我们A需要10M的大小,那么我们的虚拟空间就是从0x00000000开始到0x00A00000结束。所以:
- 如果A访问的地址空间超过了0x00A00000,那么硬件就会判断这是个非法访问,就会拒绝这个请求,从而实现了隔离。
- 对于程序来说,我们只在0x00000000到0x00A00000来编写程序、放置变量,所以程序不需要再重定位。
分段解决了但是分段还是没有解决我们第二个问题-内存使用效率问题。人们很自然的就想到了更小粒度的内存分割和映射方法,于是,分页出现了。
分页管理
分页的基本方法是把地址空间人为的分成固定大小的页,一般都是4KB。
首先我们要知道三种页:
- 磁盘页:磁盘中的页。
- 虚拟页:虚拟空间的页。
- 物理页:物理内存中的页。
程序会放在虚拟页中,最后进行映射。
2016-06-04-3
我们把程序中常用的数据和代码页装到内存中(虚拟页中0、1、7),把不常用的放在磁盘页(虚拟页中2、3),暂未用到的不映射(虚拟页中的4、5、6)。
页错误
如果进程需要使用虚拟页的2、3,但是这时它们在磁盘页中,硬件会捕获这个消息,这就是所谓的页错误。
这时操作系统会用自己的办法(用空再写,就是TLB那块)把磁盘中表示虚拟页2、3的数据读出来并且装入内存。
以页为单位来存取和交换这些数据非常方便,硬件本身就支持这种以页为单位的操作方式。
MMU
映射需要硬件来实现,几乎所有的硬件都采用一个叫MMU的部件来进行映射,如图所示。
2016-06-04-4
写在结尾
如果您有所收获,希望点个赞支持。
也欢迎收录我的博客专栏:biggergao的博客,谢谢。
Done
作者 @biggergao
2016年06月04日
网友评论