在远古时期,内存极小,程序也不大,程序直接装载到内存运行。但是随着程序越来越复杂,理所应当的出现了这个问题,
如果程序所需内存比可用内存大,程序该如何运行?
为了解决这个问题,当时提出了一种覆盖装入的技术,就是需要程序员自己把程序分割成若干个不相干的程序块,块与块之间分别装载运行。举个很简单的例子,程序里面有两个主要函数A和B,这两个函数之间没有相互调用关系,当需要A函数运行的时候,就把A函数对应的程序块装载到内存里,等到执行完毕,再装载别的块。
这样的操作不需要把程序全部装入,暂时解决了上面提到的问题。为什么说是暂时解决呢?注意看上面那一段的黑体字部分,这种方法需要程序员自己去理清程序之间的关系,分割程序。程序逻辑相对简单还比较ok,但是当程序更加的复杂,甚至程序代码逻辑调用,根本分不出独立的程序块时,该如何解决?
其实上面那种方法就是现代虚拟内存技术的雏形。
要理解虚拟内存,我们先来慢慢理清几个概念,
接下来,我们将理清这3个概念以及它们之间的关系:虚拟地址空间,物理内存空间,虚拟内存。
试问,当你在程序中输出一个变量的地址时,这个地址的含义是什么?
试问,你输出一个全局变量的地址,干掉程序,再启动程序,为什么这个地址没有改变?
试问,大家都知道,堆是向上增长,栈是向下增长,那栈和堆之间肯定有一大块地址,这块地址是不是浪费了?
嗯,好好思考一下~
不懂虚拟内存,就相当于没学过操作系统。
每个进程都会有一个独立的虚拟地址空间,注意,是独立的,相互不能访问的,地址空间里包含了程序运行所需要的全部东西。进程运行时,所需要的所有数据,所有代码,包括内核系统调用,运行库之类的东西,全部取自虚拟地址空间。看到这你可能有疑问了,系统内核只有一个,确定是每个进程独立拥有内核吗?是的,在虚拟空间,每个进程都是独立拥有的,进程所需要的所有东西,都会在虚拟地址空间有独立的地址。
虚拟地址空间是操作系统为每一个程序虚拟化出来的内存空间,虚拟地址空间的大小一般等于内存地址大小,程序装载执行的时候操作系统生成,程序终止的时候消失。程序执行的时候,所有的操作,都是在虚拟地址空间上完成的,物理地址空间对于程序是不可知的状态。程序跟物理地址是完全隔离的。你在程序中输出的变量地址,就是这个变量在虚拟地址空间中的地址。
使用虚拟地址空间的好处:
1.虚拟地址空间是连续的,方便程序执行。
2.各个程序都有自己的一份虚拟地址空间,能隔离各个进程,一个进程中,函数溢出,可能会影响本进程的函数堆栈,但是绝对不会影响到别的进程。
3.节省内存空间。
前面还有很多没有交代清楚,比如:
每个进程都有内存那么大的地址空间,内存哪够装啊?
虚拟地址空间中有大块的地方是没有用到的,明明就浪费了空间,为什么说节省内存空间?
现在讲一下,虚拟地址空间和物理内存的映射。
在编程中,基本的内存单位应该是字节了,也就是8bit。但是在内存管理中,这个显然太小了,现代操作系统使用页作为内存管理的基本单位,一页大小一般为4K。现在想象一下,整个虚拟地址空间被划分成很多个页,整个物理内存也被划分成很多个页。大家都知道,程序是一条语句一条语句执行的,同一小时间段内,可能程序只会用到几个页的数据,别的页处于闲置状态。事实上,别扯什么虚拟地址空间什么的,代码,数据只有在内存中才能被用到。操作系统做了虚拟地址空间和物理内存的映射,会把即将要用到的虚拟空间页放入内存,不怎么用到的页移除,这个过程进程是感知不到的。进程只跟虚拟地址空间打交道,它用的时候,操作系统准备好了,所以在这个过程对它是无感知的。
地址映射的过程大概就是这样:
程序正在执行,程序的虚拟地址空间一共有10个页,程序执行的时候,操作系统把其中123页映射到物理内存,程序用到了使用虚拟地址空间页面1的时候,使用的是虚拟地址空间的地址,操作系统就把地址映射对应的物理内存页上。再次强调,这个过程中,程序用到的地址都是虚拟地址,只不过操作系统给转换成了物理地址。
等程序把页面1用完了,现在想用页面5,操作系统就会把地址映射到物理内存页对应的5上,但是这个时候,发现内存中没有页面5,这是时候就会触发缺页中断,把一个不怎么用的页淘汰掉(淘汰算法不再赘述),把页面5加载到物理内存中。淘汰掉淘汰到哪了?数据不就丢了?没丢,只是做了个映像,放到了虚拟内存中(也就是磁盘),加载是从哪加载的?从虚拟内存中加载。
这个虚拟内存是个啥?
不多说,大概可以理解为,内存页的备份。
从上面的过程可以看出来,虚拟地址空间中的空间,只有用到,才会真实存在(加载到物理内存,或者扔到虚拟内存),如果没用到的话,就什么都没有。就好像一个程序,只用到了10个页,内存中加载了3个,虚拟内存中放了7个,虚拟地址空间很大,但是只有用了才会切切事实存在,比如现在申请了个地址,是第11个页上的,那么这个内存中就扔了一个页出去,把这个页撸进去,这时候,内存中3页,虚拟内存中8页。
emmmm,为了描述的方便,我页面是按连续的,要注意实际上的情况,比如虚拟地址空间4个G,也就是2^20 个页,只要访问到哪个页面,哪个页面就会被操作系统弄到内存里,对于程序来说,它就是拥有了所有空间。
好了,还有好多东西没有写,比如装载的时候动态库静态库,虚拟地址空间中内存分布模型,操作系统如何实现页面置换的,页表段表,多级映射,elf文件模型,等等
以后可能会慢慢补充,这次大概就写到这里,有什么问题欢迎找我交流