程序加载读书笔记

加载概念和方法

 

1.加载的概念

加载是将一个程序放到主存(RAM)里使其能运行的过程。链接加载器(本文中涉及的)和单纯的加载器没有什么太大的区别,主要和最明显的区别是在于前者的输出放在内存而不是在文件中。

 

2、带有虚拟内存加载步骤:

从目标文件读入足够的头部信息,找出需要多少地址空间。

分配地址空间,如果目标代码的格式具有独立的段,那么就将地址空间按独立的段划分。

将程序读入地址空间的段中。

bss段空间填充为0,如果虚拟内存系统不自动这么做的话。

如果体系结构需要的话,创建一个堆栈段(stack segment)。

设置程序参数及环境变量和其他运行时需要的信息。

程序开始运行。

 

3、不带虚拟内存

需要使用普通IO进行读取文件。

在支持共享只读代码段的系统上,系统检查是否在内存中已经加载了该代码段的一个拷贝,而不是生成另外一份拷贝。

4.带有重定位的基本加载

       加载时重定位要比链接时重定位简单的多,因为整个程序作为一个单元进行重定位。例如,如果一个程序被链接为从位置0开始,但是实际上被加载到位置15000,那么需要所有程序中的空间都要被修正为“加上15000”。在将程序读入主存后,加载器根据目标文件中的重定位项,并将重定位项指向的内存位置进行修改。

 

5.位置无关代码

       Position Independent CodePIC位置无关代码,是将相同程序加载到普通地址的常用解决方案。

思路是:将数据和普通代码中不会因为加载地址不同而变化的代码分离出来。这种方法中的代码(PIC)可以在所有进程中共享,只有数据页为各进程私有。

       分支和跳转代码是位置相关的,或者与某一个运行时设置的基址寄存器相关。所以需要对他们进行非运行时的重定位。在现代体系结构中,生成PIC可执行代码并不困难。。问题在于数据的寻址,代码无法获取任何的直接数据地址。由于代码是可重定位的,而数据不是位置无关的。普通的解决方案是在数据页中建立一个数据地址的表格,并在一个寄存器中保存这个表的地址,这样代码可以使用相对于寄存器中地址的被索引地址来获取数据。这样存在两个问题:

    对每一个数据引用需要进行一次额外的重定位。

    如何获取保存到寄存器中的初始地址。

 

ELF可执行程序中的代码页组跟在数据页组后面,不论程序被加载到地址空间的什么位置,代码到数据的偏移量是不变的。所以如果代码可以将自己的地址加载到一个寄存器中,那么数据位于相对于代码的的位置也确定下来。并且代码可以通过相对于某一固定偏移量的基址寻址方式引用它数据段中的数据。链接器将可执行文件中寻址的所有全局变量的指针保存在它创建的全局偏移量表(GOTglobal Offset Table),每一个共享库拥有自己的GOT。鉴于链接器创建了GOT对于每一个ELF可执行程序的数据只有一个地址,不论有多少例程引用了它。

 

      GOT寄存器被加载之后,程序数据段中的静态数据与GOT直接的距离在链接时被固定了,所以代码就可以将GOT寄存器作为一个基址寄存器来引用局部静态数据。全局数据的地址只有在程序被加载后才被确定。,所以为了引用全局数据,代码必须从GOT中加载数据的指针,然后引用这个指针。这个多余的内存引用使得程序稍微慢了一些,尽管大多数程序员为了方面的使用动态链接库愿意付出这个代价。对速度要求较高的代码可以使用静态共享库或者根本不使用共享库。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值