您的位置:首页 > 职场人生

动态共享对象的装载时重定位

2015-01-15 20:23 127 查看
最近读程序员的自我修养--链接 装载与库,其中有句话:动态链接模块被装载映射到虚拟空间后,指令部分是在多个进程之间共享的,由于装载时重定位的方法需要修改指令,所以没有办法做到同一份指令被多个进程共享,因为指令被重定位后对于每个进程来讲是不同的。一直没搞懂,花了不少时间查阅资料,了解原理。觉得自己一直理解的东西都太浅了。 以下是对这块内容的总结,作为记录。

其中的关键点在于掌握内存映射,重定位的实现以及共享对象的装载。搞清楚虚拟内存空间和物理内存空间。

共享对象在被装载时,是如何确定它在进程虚拟地址空间中的位置?

一种方法是固定装载,这种方法弊端明显:地址冲突;共享库升级困难;空间受限等等。(基本不用)

那么只能采用另一种思路,即共享对象能在任意地址装载。这种情况叫做装载时重定位。当程序被装载时,系统的动态链接器会将程序所需的所有动态链接库(例如最基本的libc.so)装载到进程的地址空间,且将程序中所有未决议的符号绑定到相应的动态链接库中,并进行重定位工作(术语叫装载时重定位-load time relocation,在windows中,又叫基址重置-rebasing,区别于静态链接的链接时重定位-link time relocation)。也即,动态链接是把可执行elf的形成过程从本来的程序链接前推迟到装载时。共享对象的最终装载地址在编译时是不确定的,而是在装载时,装载器根据当前地址空间的空闲情况,动态分配一块足够大小的虚拟地址空间给相应的共享对象。

装载时重定位的原理来自链接时重定位,但是对于共享对象,单纯的装载时重定位显然会引发问题,也就是文章开头说到的那句话。动态链接模块被装载映射到虚拟空间后,指令部分是在多个进程之间共享的,由于装载时重定位的方法需要修改指令,所以没有办法做到同一份指令被多个进程共享,因为指令被重定位后对于每个进程来讲是不同的。

在网上看到有不少人问了这个问题,答案看得我也是迷迷糊糊。折腾了一下午,发现其实是自己对基本的原理都没搞清楚才会看不懂这句话。关键在于共享对象也就是动态链接库在被装载到物理内存后,始终是只有一份的,不管有多少个进程使用它。但是对于每一个进程,共享对象会映射一次到虚拟地址空间,也就是每个进程空间都有一份共享对象的映射,此时,对于不同的进程,映射的地址(基址)是不一样的(大部分情况下)。紧接着,进行装载时重定位。装载时重定位由动态链接器完成,动态链接器会被一起映射到进程空间中。它根据共享对象在虚拟内存空间中的地址修改在物理内存中的共享对象中的指令,为什么会修改指令,原因在于绝对地址访问(如模块内的变量访问)是直接用mov指令完成的,也就是直接将地址打入寄存器,所以,此时的重定位会直接修改指令。进一步,共享对象中修改的指令是根据共享对象被映射到虚拟空间中的地址(基址)决定的,而每个进程对共享对象的映射不可能都是在相同地址。所以也就无法完成这一部分代码的共享。

那么如何解决这个问题,就要用到位置无关代码(PIC),也有叫地址无关代码的。基本思路是把指令中那些需要被修改的部分分离出来,跟数据部分放到一起,这样,剩下的指令就可以保持不变,而数据部分在每个进程中拥有一个副本。ELF针对各种可能的访问类型(模块内部指令调用、模块内部数据访问、模块间指令调用、模块间数据访问),实现了对应地址引用方式,从而实现了PIC。具体的细节不在这里赘述。

 

参考资料:http://www.360doc.com/content/14/0902/09/19184777_406438267.shtml
http://blog.chinaunix.net/uid-26983585-id-3364514.html http://blog.163.com/shi_shun/blog/static/2370784920122875910467/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息