os 程序的装入和链接

用户程序要在系统中运行,必须先将它装入内存,然后再将其转变为一个可以执行的程序,通常都要经过以下几个步骤:

  • (1)编译,由编译程序( compiler )对用户源程序进行编译,形成若干个目标模块( Object Module );
  • (2)链接,由链接程序( Linker )将编译后形成的一组目标模块以及它们所需要的库函数链接在一起,形成一个完整的装入模块( Load Module );
  • (3)装入,由装入程序( Loader )将装入模块装入内存。

下图示出了这样的三步过程。本节将扼要阐述程序(含数据)的链接和装入过程。
os 程序的装入和链接_第1张图片

程序的装入

为了阐述上的方便,我们先介绍一个无需进行链接的单个目标模块的装入过程。该目标模块也就是装入模块。在将一个装入模块装入内存时,可以有如下三种装入方式:

1.绝对装入方式( Absolute Loading Mode )

当计算机系统很小,且仅能运行单道程序时,完全有可能知道程序将驻留在内存的什么位置。此时可以采用绝对装入方式。用户程序经编译后,将产生绝对地址(即物理地址)的目标代码。例如,事先已知用户程序(进程)驻留在从 R 处开始的位置,则编译程序所产生的目标模块(即装入模块),便可从 R 处开始向上扩展。绝对装入程序便可按照装入模块中的地址,将程序和数据装入内存。装入模块被装入内存后,由于程序中的相对地址(即逻辑地址)与实际内存地址完全相同,故不需对程序和数据的地址进行修改。
程序中所使用的绝对地址既可在编译或汇编时给出,也可由程序员直接赋予。但由程序员直接给出绝对地址时,不仅要求程序员熟悉内存的使用情况,而且一旦程序或数据被修改后,可能要改变程序中的所有地址。因此,通常是宁可在程序中采用符号地址,然后在编译或汇编时,再将这些符号地址转换为绝对地址

2.可重定位装入方式( Relocation Loading Mode )

绝对装入方式只能将目标模块装入到内存中事先指定的位置,这只适用于单道程序环境。而在多道程序环境下,编译程序不可能预知经编译后所得到的目标模块应放在内存的何处,对于用户程序编译所形成的若干个目标模块,它们的起始地址通常都是从0开始的,程序中的其它地址也都是相对于起始地址计算的。此时,不可能再用绝对装入方式,而应采用可重定位装入方式,它可以根据内存的具体情况将装入模块装入到内存的适当位置
值得注意的是,在采用可重定位装入程序将装入模块装入内存后,会使装入模块中的所有逻辑地址与实际装入内存后的物理地址不同,下图示出了这一情况。例如,在用户程序的1000号单元处有一条指令 LOAD 1,2500,该指令的功能是将2500单元中的整数365取至寄存器1。但若将该用户程序装入到内存的10000~15000号单元而不进行地址变换,则在执行11000号单元中的指令时,它将仍从2500号单元中把数据取至寄存器1,而导致数据错误。由图可见,正确的方法应该是,将取数指令中的地址2500修改成12500,即把指令中的逻辑地址2500与本程序在内存中的起始地址10000相加,才得到正确的物理地址12500除了数据地址应修改外,指令地址也须做同样的修改,即将指令的逻辑地址1000与起始地址10000相加,得到绝对地址11000。通常,把在装入时对目标程序中指令和数据地址的修改过程称为重定位。又因为地址变换通常是在进程装入时一次完成的,以后不再改变,故称为静态重定位
os 程序的装入和链接_第2张图片

3.动态运行时的装入方式(Dynamic Run-time Loading)

可重定位装入方式可将装入模块装入到内存中任何允许的位置,故可用于多道程序环境。但该方式并不允许程序运行时在内存中移动位置。因为,程序在内存中的移动,意味着它的物理位置发生了变化,这时必须对程序和数据的地址(绝对地址)进行修改后方能运行。然而,实际情况是,在运行过程中它在内存中的位置可能经常要改变,例如,在具有对换功能的系统中,一个进程可能被多次换出,又多次被换入,每次换入后的位置通常是不同的。在这种情况下,就应采用动态运行时装入的方式。
动态运行时的装入程序在把装入模块装入内存后,并不立即把装入模块中的逻辑地址转换为物理地址,而是把这种地址转换推迟到程序真正要执行时才进行。因此,装入内存后的所有地址都仍是逻辑地址。为使地址转换不影响指令的执行速度,这种方式需要一个重定位寄存器的支持。


程序的链接

源程序经过编译后,可得到一组目标模块。链接程序的功能是将这组目标模块以及它们所需要的库函数装配成一个完整的装入模块。在对目标模块进行链接时,根据进行链接的时间不同,可把链接分成如下三种。

1.静态链接( Static Linking )方式

在程序运行之前,先将各目标模块及它们所需的库函数链接成一个完整的装配模块,以后不再拆开。我们把这种事先进行链接的方式称为静态链接方式。我们通过一个例子来说明在实现静态链接时应解决的一些问题。在图( a )中示出了经过编译后所得到的三个目标模块 A 、 B 、 C ,它们的长度分别为 L 、 M 和 N 。在模块 A 中有一条语句 CALL B ,用于调用模块 B 。在模块 B 中有一条语句 CALL C ,用于调用模块 C 。 B 和 C 都属于外部调用符号,在将这几个目标模块装配成一个装入模块时,须解决以下两个问题:

  • (1)对相对地址进行修改。在由编译程序所产生的所有目标模块中,使用的都是相对地址,其起始地址都为0,每个模块中的地址都是相对于起始地址计算的。在链接成一个装入模块后,原模块 B 和 C 在装入模块的起始地址不再是0,而分别是 L 和 L + M ,所以此时须修改模块 B 和 C 中的相对地址,即把原 B 中的所有相对地址都加上 L ,把原 C 中所有相对地址都加上 L + M。
  • (2)变换外部调用符号。将每个模块中所用的外部调用符号也都变换为相对地址,如把 B 的起始地址变换为 L ,把 C 的起始地址变换为 L + M ,如图( b )所示。这种先进行链接所形成的一个完整的装入模块,又称为可执行文件通常都不再把它拆开,要运行时可直接将它装入内存。把这种事先进行链接而以后不再拆开的链接方式称为静态链接方式。

os 程序的装入和链接_第3张图片

2.装入时动态链接( Load-time Dynamic Linking )

这是指将用户源程序编译后所得到的一组目标模块,在装入内存时,采用边装入边链接的链接方式。即在装入一个目标模块时,若发生一个外部模块调用事件,将引起装入程序去找出相应的外部目标模块,并将它装入内存,还要按照上图所示的方式修改目标模块中的相对地址。装入时动态链接方式有以下优点:

  • (1)便于修改和更新。对于经静态链接装配在一起的装入模块,如果要修改或更新其中的某个目标模块,则要求重新打开装入模块。这不仅是低效的,而且有时是不可能的。
    若采用动态链接方式,由于各目标模块是分开存放的,所以要修改或更新各目标模块是件非常容易的事
  • (2)便于实现对目标模块的共享。在采用静态链接方式时,每个应用模块都必须含有其目标模块的拷贝,无法实现对目标模块的共享。但采用装入时动态链接方式时, OS 就很容易将一个目标模块链接到几个应用模块上,实现多个应用程序对该模块的共享。

3.运行时动态链接( Run-time Dynamic Linking )

在许多情况下,应用程序在运行时,每次要运行的模块可能是不相同的。但由于事先无法知道本次要运行哪些模块,故只能是将所有可能要运行到的模块全部都装入内存,并在装入时全部链接在一起。显然这是低效的,因为往往会有部分目标模块根本就不运行。比较典型的例子是作为错误处理用的目标模块,如果程序在整个运行过程中都不出现错误,则显然就不会用到该模块。
近几年流行起来的运行时动态链接方式,是对上述装入时链接方式的一种改进。这种链接方式是,将对某些模块的链接推迟到程序执行才进行。亦即,在执行过程中,当发现一个被调用模块尚未装入内存时,立即由 OS 去找到该模块,并将之装入内存,将其链接到调用者模块上。凡在执行过程中未被用到的目标模块,都不会被调入内存和被链接到装入模块上,这样不仅能加快程序的装入过程,而且可节省大量的内存空间。

你可能感兴趣的:(#,操作系统,os,储存器)