学习程序员的自我修养,这是我刚刚工作的时候,带我的师傅,推荐我看的第一本书,从今天开始希望一周能够读完这本书,之前初略看过一遍。
1.什么是动态链接?
把程序的链接过程推迟到运行时的在进行,被称为动态链接。
动态链接的思想: 把程序按模块拆分成多个独立的模块,在程序运行时,才将他们链接成一个单独的可执行文件。
比如:可执行文件A 依赖lib.so,可执行文件B也依赖lib.so, 系统加载A,发现链接时,依赖lib.so,会加载lib.so。
system:: A -->lib.so。
如果系统执行 B
system:: A-->lib.so--B
由于lib.so已经被加载到内存,所以不需要再次加载,省空间。
动态链接相比静态链接的好处是什么?
1.静态链接,浪费空间,因为需要将所有的静态库连接成一个文件,每次要将所有的程序链接起来,然后发布户。
2.动态链接的好处:解决了静态链接两个困难,空间浪费和更新困难。
从本质上讲,普通可执行程序和动态链接库中,都包含指令和数据,这一点没有区别。
在使用动态链接库的情况下,程序本身分为程序主要模块和动态链接库。实际上它们都可以看作整个程序的一个模块。当我们提到程序的模块时,指动态链接库,其实可执行文件和动态链接库,都可以被称为模块。
2.动态链接程序运行时地址空间的分布
对于静态链接的可执行文件来讲,整个进程只有一个文件要映射,那就是可执行文件。
对于动态链接来讲,除了可执行文件,还有依赖的动态链接库,以及动态链接器,都需要被映射到虚拟地址空间。 在系统执行可执行文件时,动态链接器先进行链接,其链接工作完成后,交给可执行文件执行。
共享对象的装载位置:
共享对象的最终装载地址,在编译时是不确定的,可基本执行程序基本可以确定自己在进程虚拟地址空间的位置。
3.装载时重定位。
为了能够使共享对象在任意地址装载,在链接时不进行重定位,而是讲重定位推迟到装载后再完成。系统在装载完之后,会遍历模块中的重定位表,找到需要从定位的引用。
装载时重定位,对数据段是有效的,因为不同进程有这不同的数据段副本,但是代码段就不行,因为多个进程共享一份代码段,不能够去修改共用的代码段。 所以装载时从定位存在的缺点是指令部分无法在多个进程之间共享。
希望程序模块中共享指令部分在装载时,不需要因为装载地址而改变,所以基本的思想方法是,把指令中那些需要修改的部分分离出来,跟数据部分放在一起,这样指令部分就可以保持不变,而数据部分在每个进程中,拥有不一样的副本,这就叫做地址无关代码的技术。
共享模块中的地址分为两部分:
按模块区分模块内部引用和模块外部引用,
按引用方式区分,为指令引用和数据引用。
3.1 模块内部的调用和跳转
模块内部 调用函数和被调用函数之间相对位置是固定的,模块内部的跳转,都是基于相对地址的调用,或者基于寄存器的相对调用,这种指令是不需要重定位的。
3.2 模块内部的数据访问
模块的指令和数据段的相对位置是固定的,也是基于相对地址的调用。
3.3 模块间的数据访问
模块间的数据访问要复杂一些,因为模块间的数据访问目标地址,要等到装载时,才能决定。
ELF 的做法是在数据段里面,建立一个指向这些变量的指针数组,也被称为全局偏移量。
数据段中,建立一个指向这些变量的指针数组,也被称为全局偏移表(Gost)。
将非本模块的全局变量放在Gost中,用4个字节表示全局变量实际的地址。
(1) 编译时,模块生成Gost表,保存未定义在此模块全局变量。 Gost 变量与指令之间时相对偏移。
(2)链接器在链接时,为Gost中每一个变量赋上其实际地址。
(3)程序调用时,先通过内部偏移跳转到gost,然后再偏移访问到变量a的位置。
3.4 模块间的调用和跳转
同样时将外部函数放在Gost表中,不过储存的时函数的地址。
总结:
指令与跳转 | 数据 | |
模块内部 | 相对跳转与调用 | 相对跳转与调用 |
模块外部 | 间接跳转与调用 | 间接访问 |
fpic 时不包含任何可重定位表的。
4.共享模块的全局变量问题
当一个模块引用了一个定义在共享对象的全局变量时, 编译器有时无法确定,变量是定义在同一个模块其他目标文件,还是定义在另外一个共享对象中。
无法判断全局变量是否跨模块。
可执行文件引用动态库中变量,并且赋值。
可执行文件在运行时,不会进行代码重定位,所以在bss段中创建一个数据副本。
共享库中,也有一个副本, 一个变量存在多个位置,肯定是不行的。
ELF 共享库在编译时,把所有的定义在模块内部的全局变量,默认的当做定义在其他模块的全局变量。 链接器再链接时,发现如果全局变量在某个执行文件中有副本,会将GOT 中该变量的地址,指向该副本。 那么在实际执行中,只有一个副本。
如果变量在共享模块中被初始化,链接器,会将这个初始值,复制到程序主模块的副本上。
Q&&A:
如果在一个lib.so库中定义一个全局变量G,A进程和B进程都进行了装载,而进程A和进程B 使用是同一个副本吗?
每个进程都有独立的副本,无法访问。
如果是一个进程的两个线程呢?
两个线程访问同一个进程地址空间。
如何做到两个进程能够访问同一个副本?两个线程访问不同的副本?
进程间的通信,访问全局变量。 线程间的通讯。
5. 数据段的地址无关性
对于数据段来讲,每个进程都会有一个独立的副本,所以不用担心被进程所改变,所以可以在重定位时,来解决数据段中绝对地址的引用问题。
代码也可以使用重定位的方式,但是他将不能被多个进程装载。