参考: 静态链接库和动态链接库 https://blog.csdn.net/lqy971966/article/details/105207532
共享对象在被装载时,如何确定它在进程虚拟地址空间中的位置? 早期是通过静态共享库(Static Shared Library)解决的。
静态共享库和静态库有明显的区别。 静态共享库的做法就是:将程序的各个模块统一交给操作系统来管理, 操作系统在某个特定的地址划分出一些地址块,为那些已知的模块预留足够的空间。
静态共享库的缺点: 地址冲突,升级问题多
假设函数 helloHani()相对于代码段的起始地址是0x100,当模块被装载到0x10000时,我们假设代码段位于模块的最开始,那么 helloHani 的地址就是:0x10100。 此时,系统遍历模块中的重定位表,把所有对helloHani的地址引用(其他模块调用它)都重定位至 0x10100。
静态链接的重定位叫做:链接时重定位 现在的叫做:装载时重定位 windows中,这种装载时重定位又叫做:基址重置
程序模块在编译时目标地址不确定而需要在装载时将模块重定位。 但是装载时重定位的方法不适合用来解决共享对象中的问题。
动态链接模块被装载映射到虚拟空间后,指令部分是在多个进程 之间共享的,由于装载时重定位的方法需要修改指令,所以没有办法 做到同一份指令被多个进程共享,因为指令被重定位后对于每个进程 来讲是不同的。
当然,动态链接库中的可修改数据部分对于不同的进程来说有多个副本, 所以它们可以采用装载时重定位的方法来解决。
linux 支持这种装载时重定位的方法:叫做 -shared 该选项指定生成动态连接库
PIC: Postion-independent Code 地址无关代码 基本思想: 把跟地址相关的部分放到数据段里面
装载时重定位是解决动态模块中有绝对地址引用的办法之一, 但是它有一个很大的缺点是:指令部分无法再多个进程之间共享, 这样就失去了动态链接库节省内存的一大优势。
我们希望程序模块中共享的指令部分在装载时不需要因为装载地址的改变而改变,所以基本想法就是把指令中那些需要被修改的部分分离出来,跟数据部分放在一起,这样指令部分就可以保持不变,而数据部分可以再每个进程中拥有一个副本。 这个方案就是:地址无关代码的技术。
代码例子 pic.c
static int a; extern int b; extern void ext(); void bac() { a - 1; //第二种 b - 1; //第四种 } void foo() { bac(); //第一种 ext(); //第三种 }GOT: Global Offset Table 全局偏移表
代码地址无关的思想是:把指令段里跟地址相关的部分数据放到数据段里面。
这里,其他模块的全局变量的地址是跟模块装载地址有关的。 ELF做法是在数据段里面建立一个指向这些变量的指针数组,也被称为Got全局偏移表。
优点: GOT放在数据段,所以它可以再模块装载时被修改,并且每个进程都可以有独立的副本,相互不受影响。
基本机制图:
前面四种情况没有包含:定义在模块内部的全局变量的情况,怎么解决?
大多数可以和内部静态变量一样处理,但是有一个特殊情况:
extern int global; int foo() { global = 1; }这里,当编译到 global的时候,无法判断是跨模块还是模块间调用 解决: 这里ELF编译时,默认把定义在模块内部的全局变量当做定义在其他模块的全局变量。
p的地址是一个绝对地址,a的地址会随着共享对象的装载地址改变而变。
方法一: 可以采用装载时重定位的方法来解决。 通过重定位表来解决它。