程序装载
程序装载难处
要想装载器解析ELF或者PE格式的可执行文件,把对应的指令和数据加载到内存。装载器需要满足两个要求。
第一,可执行程序加载后占用的内存空间应该是连续的。
第二,我们需要同时加载很多个程序,并且不能让程序自己规定在内存中加载的位置。
如何解决难处?
我们可以在内存中,找到一段连续的内存空间,然后分配给装载的程序,把这段连续的内存空间地址和整个程序指令里指定的内存地址做一个映射。
我们把指令里用到的内存地址叫做虚拟内存地址,实际在内存硬件里面的空间地址叫做物理内存地址。
我们维护一个虚拟内存到物理内存的映射表,这样实际程序指令执行的时候,会通过虚拟内存地址找到对应的物理内存地址,然后执行。因为是连续的内存地址空间,我们只需要维护映射关系的起始位置和对应的空间大小即可。
内存分段
这种找出一段连续的物理内存和虚拟内存地址进行映射的方法我们叫分段。
分段解决了程序本身不需要关心具体的物理内存地址的问题,但是它会产生内存碎片。
针对内存碎片我们的解决办法叫内存交换。
我们可以把上图的Python程序写到硬盘上,然后再从硬盘读回来到内存里面。不过读回来的时候,不在原来的位置,而是紧跟着512MB的内存后面。这样就把两个空余的128MB的内存碎片合并,解决了内存碎片的问题。
但是硬盘的访问速度要比内存慢的多,所以内存交换的时候整个机器会显得卡顿。
内存分页
问题出在内存碎片和内存交换的空间太大,那么少出现一些碎片,而且在交换的时候让需要交换的大小小一点,就可以解决这个问题。在计算机管理内存里面,就叫做内存分页。
和分段这样分配一整段连续的空间给到程序相比,分页是把整个物理内存空间切成一段段固定尺寸的大小,而对应的程序所需要占用的虚拟内存空间,也会同样切成一段段固定尺寸的大小。这样一个连续并且尺寸固定的内存空间,我们叫做页。这样通过内存交换释放一些内存的页出来,一次性写入磁盘的只有少数的几个页,不会花很多时间,降低机器的性能。
更进一步,分页使我们在加载程序的时候,不需要一次性都把程序加载到物理内存中。而是只在程序运行中,需要用到对应的虚拟内存页里面的指令和数据时,再加载到物理内存中去。
当要读取特定的页时,发现数据并没有加载到物理内存,会触发CPU的缺页错误。操作系统回捕捉到这个错误然后将对应的页从存放在磁盘上的虚拟内存里取出来,加载到物理内存。