Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3013973
  • 博文数量: 167
  • 博客积分: 613
  • 博客等级: 中士
  • 技术积分: 5473
  • 用 户 组: 普通用户
  • 注册时间: 2011-09-13 21:35
个人简介

人, 既无虎狼之爪牙,亦无狮象之力量,却能擒狼缚虎,驯狮猎象,无他,唯智慧耳。

文章分类
文章存档

2015年(19)

2014年(70)

2013年(54)

2012年(14)

2011年(10)

分类: LINUX

2012-02-13 20:11:40

大概四个月后,因为写DLL的关系,自己又翻起了《深入理解操作系统》这本神书,再来复习其中的《链接》一章,虽说现在理解的还只是一点皮毛,但真的是越品越有味道,不愧是大牛的神作。

重复的部分就不再提了,这次来着重总结下自己新的理解。

通过链接我们实现了分离编译:将一个项目划分成不同的功能模块,针对每一个模块具体实现,单独编译;一旦需要针对某些模块更新升级,只需要修改后单独编译,重新链接即可。应该认识到,一般说来,汇编与编译的工作量要远远大于链接。

那链接到底做了什么工作呢?

要理解链接的作用,必须首先了解目标文件的格式。我们常说目标文件,指的是由ASC2源码文件汇编而成的机器码文件。目标文件又分为三类:可重定位目标文件(.o),可执行目标文件(.exe),共享目标文件。这里的后缀名是比较常见的后缀名,.exe多用于win32系统下。
我们通过编译器没有链接之前得到的是可重定位目标文件,它由二进制机器码组成,但是由于编译器和汇编器不知道程序整体的组成,大小,所以不能确定对程序的存储器分配,因而各个部分的起始位置均为从0开始的相对偏移,具体的存储器位置需要由链接来完成,因而称之为可重定位目标文件。
可执行目标文件可以直接载入存储器并且执行。
共享目标文件也是一种特殊的可重定位目标文件。在其他可执行目标文件执行时被动态地加载到存储器并链接。

各个系统之间目标文件格式不同,常见的有ELF和PE格式。这里需要注意的是,目标文件的构成是按照字节块来构成的,代码指令统一放到.text节,已经初始化的全局变量统一放到.data节,等等。

链接的第一个任务是符号解析。就是将程序中出现的每一个符号,比如函数名,变量名等对象,都与自身模块中.symtab节中的符号条目定义对应起来,使得每一个符号都可以找到定义实现。
链接的第二个任务是重定位。链接器已经知道了程序所用的所有目标文件,因此对各个目标文件中的同类节进行合并形成新的统一目标文件的同类节,比如将obj1.text和obj2.text处理为obj.text节,等等。然后将运行时存储器地址赋给新的聚合节和定义的每个符号。完成这步之后,针对所有调用函数或者全局变量的指令,修改其符号映射地址,使得它们都指向正确的地址。

写程序时要注意两种不易察觉的错误。一是不要使用同名全局变量。因为链接符号解析时的定义必须是唯一的,因而如果出现多重定义的全局变量,会导致符号之间的混淆和冲突。二是在命令行下链接添加库名时,注意引用者在前,被引用者在后。

关于共享库

如果每一个可执行目标文件都包含了执行所需要的所有代码和数据,势必造成存储器的巨大浪费,因为很多标准库是所有的程序都要使用的,这样就会在存储器中出现大量重复的代码和数据。为了解决这个问题,人们引入了共享库。我们现在将一个可重定位目标文件部分链接,某些常用的API我们不再直接链接进可执行目标文件。执行该目标文件时,首先加载器将可执行目标文件加载进存储器,而后检查动态链接标记,发现确实有DLL链接,因而将控制权交给链接器,链接器获取存储器中DLL的实际位置,而后映射进进程的私有地址空间,最后链接器将修改代码指令中所有有关DLL部分的调用指令,使得它们正确的指向进程地址空间中的共享库地址,通过地址映射正确地找到DLL实际的存储位置。

如此一来,就不需要重复地加载相同的代码,节约了存储器空间。而且借助于DLL,还可以实现高性能WEB服务器,对于页面的动态请求进行函数的动态加载。
如Linux运行时存储器映像:



阅读(2482) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~