程序的装入和链接
目录
参考资料:
用户程序要在系统中运行,必须先将它装入内存,然后再将其转变为一个可以执行的程序,通常都要经过以下几个步骤:
- 编译,由编译程序(Compiler)对用户源程序进行编译,形成若干个目标模块(ObjectModule)
- 链接,由链接程序(Linker)将编译后形成的一组目标模块以及它们所需要的库函数链接在一起,形成一个完整的装入模块(Load Module)
- 装入,由装入程序(Loader)将装入模块装入内存
1 程序的装入
将一个装入模块装入内存时,可以有如下三种装入方式:
1.1 绝对装入方式(Absolute Loading Mode)
- 无需进行逻辑地址和物理地址的转换
当计算机系统很小,且仅能运行单道程序时,完全有可能知道程序将驻留在内存的什么位置。此时可以采用绝对装入方式。用户程序经编译后,将产生绝对地址(即物理地址)的目标代码。例如,事先已知用户程序驻留在从 R 处开始的位置,则编译程序所产生的目标模块,便可从 R 处开始向上扩展。绝对装入程序便可按照装入模块中的地址,将程序和数据装入内存。装入模块被装入内存后,由于程序中的相对地址(即逻辑地址)与实际内存地址完全相同,故不需对程序和数据的地址进行修改。
1.2 可重定位装入方式(Relocation Loading Mode)
- 重定向:需要把逻辑地址转换为物理地址
- 装入时重定向,装入后不再修改
- 该方式不允许程序运行时在内存中移动位置
采用可重定位装入程序将装入模块装入内存后,会使装入模块中的所有逻辑地址与实际装入内存后的物理地址不同。
例如,在用户程序的 1000 号单元处有一条指令 LOAD(1,2500),该指令的功能是将 2500 单元中的整数 365 取至寄存器 1。但若将该用户程序装入到内存的 10000~15000 号单元而不进行地址变换,则在执行 11000 号单元中的指令时,它将仍从 2500 号单元中把数据取至寄存器 1,而导致数据错误。
由此可见,正确的方法应该是,将取数指中的地址 2500 修改成 12500,即把指令中的逻辑地址 2500 与本程序在内存中的起始地址 10000 相加才得到正确的物理地址 12500。除了数据地址应修改外,指令地址也须做同样的修改,即将指令的逻辑地址 1000 与起始地址 10000 相加,得到绝对地址 11000。
通常,把在装入时对目标程序中指令和数据地址的修改过程称为重定位。又因为地址变换通常是在进程装入时一次完成的,以后不再改变,故称为静态重定位。
1.3 动态运行时的装入方式(Dynamic Run-time Loading)
如果程序在内存中的移动,意味着它的物理位置发生了变化,这时必须对程序和数据的地址(绝对地址)进行修改后方能运行。实际情况下,程序在运行过程中它在内存中的位置可能经常要改变,例如,在具有对换功能的系统中,一个进程可能被多次换出,又多次被换入,每次换入后的位置通常是不同的。在这种情况下,就应采用动态运行时装入的方式。
动态运行时的装入程序在把装入模块装入内存后,并不立即把装入模块中的逻辑地址转换为物理地址,而是把这种地址转换推迟到程序真正要执行时才进行。因此,装入内存后的所有地址都仍是逻辑地址。为使地址转换不影响指令的执行速度,这种方式需要一个重定位寄存器的支持。
2 程序的链接
源程序经过编译后,可得到一组目标模块。链接程序的功能是将这组目标模块以及它们所需要的库函数装配成一个完整的装入模块。在对目标模块进行链接时,根据进行链按的时间不同,可把链接分成如下三种:
2.1 静态链接(Static Linking)方式
事先进行链接:把所有程序用到的资源一次性加载到内存
在程序运行之前,先将各目标模块及它们所需的库函数链接成一个完整的装配模块,以后不再拆开。我们把这种事先进行链接的方式称为静态链接方式。
例如,在下图中示出了经过编译后所得到的三个目标模块 A、B、C,它们的长度分别为 L、M 和 N。在模块 A 中有一条语句 CALL B,用于调用模块 B。在模块 B 中有一条语句 CALL C,用于调用模块 C。B 和 C 都属于外部调用符号,在将这几个目标模块装配成一个装入模块时,须解决以下两个问题:
对相对地址进行修改。在由编译程序所产生的所有目标模块中,使用的都是相对地址,其起始地址都为 0,每个模块中的地址都是相对于起始地址计算的。在链接成一个装入模块后,原模块 B 和 C 在装入模块的起始地址不再是 0,而分别是 L 和 L+M,所以此时须修改模块 B 和 C 中的相对地址,即把原 B 中的所有相对地址都加上 L,把原 C 中所有相对地址都加上 L+M。
变换外部调用符号。将每个模块中所用的外部调用符号也都变换为相对地址,如把 B 的起始地址变换为 L,把 C 的起始地址变换为 L+M。这种先进行链接所形成的一个完整的装入模块,又称为可执行文件。通常都不再把它拆开,要运行时可直接将它装入内存。把这种事先进行链接而以后不再拆开的链接方式称为静态链接方式。
2.2 装入时动态链接(Load-time Dynamic Linking)
装入时动态链接
这是指将用户源程序编译后所得到的一组目标模块,在装入内存时,采用边装入边链接的链接方式。即在装入一个目标模块时,若发生一个外部模块调用事件,将引起装入程序去找出相应的外部目标模块,并将它装入内存。装入时动态链接方式有以下优点:
- 便于修改和更新。对于经静态链接装配在一起的装入模块,如果要修改或更新其中的某个目标模块,则要求重新打开装入模块。这不仅是低效的,而且有时是不可能的。若采用动态链接方式,由于各目标模块是分开存放的,所以要修改或更新各目标模块是件非常容易的事。
- 便于实现对目标模块的共享。在采用静态链接方式时,每个应用模块都必须含有其目标模块的拷贝,无法实现对目标模块的共享。但采用装入时动态链接方式时,OS 就很容易将一个目标模块链接到几个应用模块上,实现多个应用程序对该模块的共享。
2.3 运行时动态链接(Run-time Dynamic Linking)
运行时动态链接
在许多情况下,应用程序在运行时,每次要运行的模块可能是不相同的。但由于事先无法知道本次要运行哪些模块,故只能是将所有可能要运行到的模块全部都装入内存,并在装入时全部链接在一起。显然这是低效的,因为往往会有部分目标模块根本就不运行。比较典型的例子是作为错误处理用的目标模块,如果程序在整个运行过程中都不出现错误则显然就不会用到该模块。
近几年流行起来的运行时动态链接方式,是对装入时链接方式的一种改进。这种链接方式是,将对某些模块的链接推迟到程序执行时才进行。亦即,在执行过程中,当发现一个被调用模块尚未装入内存时,立即由 OS 去找到该模块,并将之装入内存,将其链接到调用者模块上。凡在执行过程中未被用到的目标模块,都不会被调入内存和被链接到装入模块上,这样不仅能加快程序的装入过程,而且可节省大量的内存空间。
程序的装入和链接,从静态发展到动态,由事先准备延迟到程序用到才准备,可节省大量内存空间,唯一的问题是,程序在运行时才去调用资源,会占用额外的操作时间