静态链接和动态链接原理简介
一个程序的产生,要经过编辑器editor的编辑,编译器compiler的的编译,链接器linker的链接,然后才能够成为可以load到内存的具有执行能力的程序,由于听说过linker and loader这本书,我一直以为在操作系统中存在一个专门将程序load到内存指定位置的系统程序,这个程序叫loader,后来才发现一个程序load到内存是通过linux的系统调用execve(2)来实现,而且对于一个程序中的标明的地址都是虚地址,真正的内存使用要经过地址转换的,但这需要cpu具有内存管理单元的支持,这就把程序员重内存分配中解脱出来。
简单来讲,程序的装入主要包含以下几个步骤:
- 读入可执行文件的头部信息以确定其文件格式及地址空间的大小;
- 以段的形式划分地址空间;
- 将可执行程序读入地址空间中的各个段,建立虚实地址间的映射关系;
- 将bbs段清零;
- 创建堆栈段;
- 建立程序参数、环境变量等程序运行过程中所需的信息;
- 启动运行。
个人觉得,静态链接的产生是由于C语言模块化的特点,为了将常用的一些功能容易归类的目标代码打包成静态链接库,这样方便管理代码,同时也方便使用,但是静态链接对内存没有节约,这在一些小系统中是很大的一个缺点。目前大部分都是采用的动态链接,动态载入的技术。
对于动态链接,动态载入:
在使用动态链接时,需要在程序映象中每个调用库函数的地方打一个桩(stub)。stub是一小段代码,用于定位已装入内存的相应的库;如果所需的库还不在内存中,stub将指出如何将该函数所在的库装入内存。
当执行到这样一个stub时,首先检查所需的函数是否已位于内存中。如果所需函数尚不在内存中,则首先需要将其装入。不论怎样,stub最终将被调用函数的地址替换掉。这样,在下次运行同一个代码段时,同样的库函数就能直接得以运行,从而省掉了动态链接的额外开销。由此,用到同一个库的所有进程在运行时使用的都是这个库的同一份拷贝。
动态链接的这一特性对于库的升级(比如错误的修正)是至关重要的。当一个库升级到一个新版本时,所有用到这个库的程序将自动使用新的版本。如果不使用动态链接技术,那么所有这些程序都需要被重新链接才能得以访问新版的库。为了避免程序意外使用到一些不兼容的新版的库,通常在程序和库中都包含各自的版本信息。内存中可能会同时存在着一个库的几个版本,但是每个程序可以通过版本信息来决定它到底应该使用哪一个。如果对库只做了微小的改动,库的版本号将保持不变;如果改动较大,则相应递增版本号。因此,如果新版库中含有与早期不兼容的改动,只有那些使用新版库进行编译的程序才会受到影响,而在新版库安装之前进行过链接的程序将继续使用以前的库。这样的系统被称作共享库系统。动态链接技术还是很复杂的,我们只需要知道,动态链接大概的原理,实现的大概流程,这些我们都是可以通过自己的探索,可以看到写端倪的,借用linux下一些工具,比如:objdump,readelf,gdb,ldd
一般使用objdump -dx readelf -h
静态链接库和动态链接库的创建
ar
命令类似于tar
命令,起一个打包的作用,但是把目标文件打包成静态库只能用ar
命令而不能用tar
命令。选项r
表示将后面的文件列表添加到文件包,如果文件包不存在就创建它,如果文件包中已有同名文件就替换成新的。s
是专用于生成静态库的,表示为静态库创建索引,这个索引被链接器使用
注:gcc常用选项
gcc -L你的路径 -lxxx(在库的目录中需要的库)
gcc -g 如果需要调试,不要忘了加上这个选项
参考文献:
程序的链接和装入及Linux下动态链接的实现