装载的方式
- 静态装入
静态装入:将程序运行所需的指令和数据全部装入内存中。
- 动态装入
很多情况下所需要的内存数量大于物理内存的数量,这可以通过添加内存的方式解决,但是内存较为昂贵,所以就希望通过不增加内存的方式来解决。又根据程序局部性原理:程序运行时,在某一时间段只是执行一小部分数据,我们可以将常用数据部分留到内存,不常用的数据可以存放到磁盘,等需要执行时再拷贝到内存中。这就是动态装入的原理。
动态装入有两种方式:
- 覆盖装入
- 页映射
覆盖装入
覆盖装入需要程序员手工对将程序进行模块划分,并通过编写辅助代码来管理(也称覆盖管理器,由于所占空间很小,所以常驻内存),某一时间将某一模块拷入内存,某一时间将某一模块进行替换。这些模块所占用的都是同一内存空间,只是谁被调用谁使用。
在程序员进行模块划分时,特别要注意各模块间的依赖关系。
如图组织关系:
模块main依赖于A 和 B,A依赖于C 和 D,同时B依赖于 E。这种组织结构可以看成树状结构。
值得注意的是:
- 这个树状结构任意模块到模块main(即树根)都叫做调用路径。而当某一模块被调用时整个调用路径模块必须都在内存中。比如调用C时A、main模块必须都在内存中。
- 禁止跨树间调用。比如:模块A不得调用模块B、模块E。假使有一模块F都被模块A和模B依赖,则可以将模块F并入模块main中。
覆盖载入是较为早期的动态装入方式,但在使用上较为复杂,且每个模块都需要经过覆盖管理器,如果某一模块不在内存还需要从磁盘中拷入,所以其运行速度相对较慢。
页映射
页映射是虚拟存储机制的一部分。
首先将内存和进程虚拟地址空间、磁盘中的数据和指令按“页”为单位划分。页大小有4096字节、8192字节、2MB、4MB等。比如:32KB按4096字节为一页,可划分为8页。
再将常用的数据和代码页装载到内存中,不常用的代码和数据保存到磁盘。
当我们程序执行需要某一页时,再将对应页从磁盘中装入到内存。
如上图所示,我们将VP0、VP1、VP6、VP7装入内存,而此时程序要访问VP2时,此时硬件会捕获到这个信息,就是所谓的页错误,然后操作系统接管进程,负责将VP2从磁盘中读出来并装入内存,然后在内存中的这个页与VP2建立映射关系。
在处理页错误时,我们可以通过多种算法处理。如:FIFO(先进先出算法)、LUR(最少使用算法)...。
在上述的页映射的动态装入的方式可看出,虚拟地址空间中的页可能被映射到内存中任意页。如VP2可以被装入到PP0~PP3的任意一个页中,显然,如果程序使用物理地址直接进行操作时,需要对每次装入的页进行重定位,这一功能通过硬件MMU来实现。
Linux内核装载ELF过程
ELF:Linux下可执行文件的格式。
ELF格式中的可执行文件:
- 可重定位文件。文件保存着代码和适当的数据,用来与其他目标文件创建一个可执行文件或者共享文件;
- 可执行文件。保存这用来执行的程序,该文件只出了exec(BA_OS)如何来创建程序进程映像;
- 共享目标文件。保存着代码和合适的数据,用来被下面的两个链接器链接(连接编辑器、动态链接器)。
ELF文件装载过程: