可执行文件装载与进程、动态链接

   


我们知道,可执行文件只有装载到内存以后才能被CPU执行。要了解装载的过程,就必须先了解进程的虚拟地址空间的概念。

一,进程虚拟地址空间的概念

  1,进程和程序的区别

  。。程序是静态的概念,是未装入内存的;进程是动态的概念,有时候也叫它“Runtime”,是已经装入内存并且跑了起来的。

  2,进程虚拟地址空间

  。。每个程序被运行起来以后,它将拥有自己独立的虚拟地址空间,其大小由计算机的硬件平台决定,具体说是由CPU的位数决定的,如32位决定了虚拟地址空间的大小为:0到2^32-1,即4GB虚拟空间的大小。64位即为0到2^64-1,即17 179 869 184GB。  

  以32位机为例,4GB的虚拟地址空间其中有1GB被操作系统占用,剩下的3GB中,又有一些其他用途的空间不能被使用,所以进程可以使用的也就只有不到3GB。

二,装载的方式

  程序执行所需要的指令和数据都必须在内存中才能够正常执行,但是很多情况下,程序所需要的内存数量总是大于物理内存的数量。这个时候就需要添加内存,然而内存是昂贵的。因此人们想出了动态装入这么个办法。

    动态装载,就是将程序常用的部分驻留在内存中,而将一些不太常用的数据存放在磁盘里面。

  动态装载有两种经典的方式:一是覆盖装入;二是页映射。

  1》覆盖装入

  因为覆盖装入目前已经渐渐被淘汰了,我们在此只简单的描述一下,如果一个程序,主模块为main,main需要调用A、B这两个子模块,三个模块的大小分别为 1024、512、256(字节),若将其全部装入内存,则需要1792个字节。其中A和B无依赖关系(即两者无相互调用),但是如果采用覆盖装入的方法,则如图

  

  当main调用A模块时,装入A模块,不装入B模块;当调用B模块时,装入B模块,覆盖掉A模块。因此可以节省256的空间。但是装入覆盖操作很花费时间,若是有很多模块,相互依赖关系一多,这种时间上的浪费是很巨大的。

  如下,A和B无相互调用。C和D无相互调用,E和F无相互调用。当然,A、C、D与B、E、F无调用。但是main要调用E就必须有B。就跟树的结构一样,main是根结点。A、B是main的子结点,C、D是A的子结点,E、F是B的子结点。树间不可相互调用。

  当main已经调用了C,接着需要调用E,则需要把C和B从磁盘读入内存,并且需要覆盖掉A和C,这样一定很慢。总之,覆盖装入是一种典型的以时间换空间的方法。

  2》页映射

  页映射的思想是将内存和所以磁盘中的数据和指令按照“页(Page)”为单位划分成若干页,以后所有的装载和操作的单位就是页。

  假设有一32位的机器,16KB的内存,每个页大小为4K字节,则共有4个页。假设一个程序总共有32KB的指令和数据。很明显,一次性不能全部装入到内存,因此得采用动态装入的方法。

 

        程序                               内存

左侧是将程序被分出来的8个页,右侧是内存被分的4个页。假设程序开始时入口地址在P0,这时操作系统发现P0并不在内存中,,于是将F0分配给P0,并且将P0的内容装入F0;运行一段时间后,程序需要用到P5,于是操作系统就将P5装进F1;接着,当用到P3、P6时,操作系统就将它们分别装入F2、F3。到现在,内存的页已经全部被用到了,这时如果需要装入P4,就必须覆盖掉一个页,则就涉及到算法的问题了,比如按照FIFO,将F0页上的P0覆盖掉,。又或者按照LUR,将用的最少次数的页覆盖掉。

  我们知道,装载的过程其实就是将一个静态的程序转成一个动态的进程的过程,我们反过来学习一下进程是如何建立的,就可以明白装载究竟都干了些什么了。

  进程的建立:

      创建一个进程,然后装载相应的可执行文件并且执行。

      以上过程需要三步:

    1,创建一个独立的虚拟地址空间。

    此处的创建虚拟地址空间并不是真正的空间,而是创建映射函数所需要的相应的数据结构,比如页表,它存着虚拟空间和物理空间的联系。

    2,读取可执行文件头并且建立虚拟空间与可执行文件的映射关系。

    当程序发生页错误时,操作系统 将从物理内存中分配一个物理页,然后将该”缺页“从磁盘中读取到内存中,再设置缺页的虚拟页和物理页的映射关系,这样程序才得以正常运行。设置缺页的虚拟页和物理页的映射关系(数据结构)是装载的核心。

    3,将CPU的指令寄存器设置成可执行文件的入口地址,启动运行。

    操作系统通过设置CPU的指令寄存器将控制权转交给了进程,由此进程开始执行。(此过程中,操作系统可看做是执行了一条跳转指令,直接调到可执行文件的入口地址。也就是ELF头文件的入口地址中去。)


  关于动态链接,动态链接是为了解决静态链接出现的空间浪费和更新困难而被提出的,简单来说,就是不对那些组成程序的目标文件进行链接,等到程序要运行时才进行链接。

  假设有三个目标文件,pro1.o,pro2.o和lib.o。当要运行pro1时,系统首先要加载pro1.o,当系统发现pro1.o中用到了lib.o时,系统会接着加载lib.o,假设还有其他的也会依次加载完所有的目标文件,加载完毕之后,开始链接,链接与静态链接的过程差不多,符号解析,重定位等。完成这些步骤之后,系统开始把控制权交给pro1.o的程序入口处,程序开始运行。这时如果要运行pro2,系统将不会再重新加载lib.o,因为内存中已经存在了一份lib.o的副本。系统要做的只是把这两个链接起来。

  动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序。

  

原文地址:https://www.cnblogs.com/junlinfeizixiao/p/6099145.html