动态链接
动态链接的基本思想是把程序按照模块拆分成各个相对独立的部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接那样把所有的程序模块都链接成一个单独的可执行文件。动态链接涉及运行时的链接及多个文件的装载,必需要有操作系统的支持,因为动态链接的情况下,进程的虚拟地址空间的分布会比静态链接情况下更为复杂,还有一些存储管理、内存共享、进程线程等机制在动态链接下也会有一些微妙的变化。在Linux系统中,ELF动态链接文件被称为“动态共享对象(DSO,Dynamic Shared Objects),简称共享对象,它们一般都是以“.so”为扩展名的一些文件,而在Windows系统中,动态链接文件被称为动态链接库(Dynamical Linking Library),即平时见到的以“.dll”为扩展名的文件。
动态链接库的特点与优势
把函数库推迟到程序运行时加载的好处有几个:
- 可以实现进程之间的资源共享。就是说,某个程序的在运行中要调用某个动态链接库函数的时候,操作系统首先会查看所有正在运行的程序,看在内存里是否已有此库函数的拷贝。如果有,则让其共享那一拷贝(共享代码段,数据码各自独立一份);只有没有才链接载入。这种模式虽然会带来一些”动态链接“额外的开销,但却大大地节省了系统的内存资源,通过一定的优化,与静态链接相比,性能损失大约在5%以下。
- 程序升级变得简单。用户只需要升级动态链接库,而无需像静态链接那样重新编译其他原有的代码就可以完成整个程序的升级。
- 可以使链接载入由程序员在程序代码中控制,如dlopen、dlsym、dlclose等等。
ELF文件格式说明
ELF文件类型
ELF文件主要分为三种类型:
- 可重定位文件(Relocatable File)包含适合于与其他目标文件链接来创建可执行文件或者共享目标文件的代码和数据。
- 可执行文件(Executable File) 包含适合于执行的一个程序,此文件规定了exec() 如何创建一个程序的进程映像。
- 共享目标文件(Shared Object File) 包含可在两种上下文中链接的代码和数据。首先链接编辑器可以将它和其它可重定位文件和共享目标文件一起处理,生成另外一个目标文件。其次,动态链接器(Dynamic Linker)可能将它与某个可执行文件以及其它共享目标一起组合,创建进程映像。
ELF文件的数据表示
ELF文件头结构及相关常数被定义在"/usr/include/elf.h"里。ELF目标文件中的所有数据结构都遵从自然大小和对齐规则。如果必要,数据结构可以包含显式的补齐,例如为了确保4字节对象按4字节边界对齐。数据对齐同样适用于文件内部。下面为ELF中常用的数据格式:
| 名称 | 大小 | 对齐 | 描述 |
| Elf32_Addr | 4 | 4 | 无符号程序地址 |
| Elf32_Half | 2 | 2 | 无符号短整型 |
| Elf32_Off | 4 | 4 | 无符号偏移地址 |
| Elf32_Sword | 4 | 4 | 有符号整型 |
| Elf32_Word | 4 | 4 | 有符号整型 |
ELF除了32位版还有64位版本,数据类型的名称和大小也相应地变化(Elf64_Addr...)。
可执行文件的装载
对于32位平台下的4G的虚拟空间,在默认情况下,Linux操作系统将进程的虚拟地址空间划分为两部分,其中操作系统本身用去了一部分:从地址0xC0000000到0xFFFFFFFF,共1GB。剩下的从0x00000000地址开始到0xBFFFFFFF共3GB的空间都是进程使用的。而对于Windows操作系统来说,它的进程虚拟地址空间划分是操作系统占用2GB。
覆盖装入(Overlay)和页映射(Paging)是两种很类型的动态装载方法,它们所采用的思想都差不多,原则上都是利用了程序的局部性原理。动态装入的思想是程序用到哪个模块,就将哪个模块装入内存,如果不用就暂时不装入,存放在磁盘中。
覆盖载入在没有发明虚拟存储之前使用比较广泛,现在已经几乎被淘汰了。覆盖载入的方法是把挖掘内存潜力的任务交给了程序员,即是程序员在编写代码的同时要自己手动对程序模块进行内存和磁盘的切换,使不需要的代码切换出磁盘,腾出空间给需要执行的代码块。
与覆盖载入的原理相似,页映射也不是一下子就把程序的所有数据和指令都载入内存,而是将内存和所有磁盘中的数据和指令按照“页(Page)”为单位划分分成若干个页,以后所有的装载和操作的单位就是页。以目前的情况,硬件规定的页大小有4096字节、8192字节、2MB、4MB等。其实这个是属于操作系统存储管理的一部分,内存页的切换会使用先进先出、最少使用等等算法。
可执行文件的装载
可执行文件的装载和进程的建立大概经历3个步骤:
- 首先是创建虚拟地址空间。一个虚拟空间是由一组页映射函数将虚拟空间的各个页映射至相应的物理空间,那么创建一个虚拟空间实际上并不是创建空间而是创建映射函数所需要的相应的数据结构。在i386的Linux下,创建虚拟地址空间实际上只是分配一个页目录(Page Directory)就可以了,甚至不设置页映射关系,这些映射关系等到后面程序发生页错误的时候再进行设置。
- 读取可执行文件头,并且建立虚拟空间与可执行文件的映射关系。上面一步的页映射关系函数是虚拟空间到物理内存的映射关系,这一步所做的是虚拟空间与可执行文件的映射关系。当程序执行发生页错误时,操作系统将从物理内存中分配一个物理页,然后将该“缺页”从磁盘中读取到内存中,再设置缺页的虚拟页和物理页的映射关系,这样程序才得以正常运行。当操作系统捕获到缺页错误时,它应知道程序当前所需要的页在可执行文件中的哪个位置。
- 将CPU指令寄存器设置成可执行文件入口,启动运行。这一步涉及了内核堆栈和用户堆栈的切换、CPU运行权限的切换等等。跳转到入口指令其实就是在ELF文件头中e_entry记录的可执行文件入口地址。
静态链接
链接有哪些过程?
假如有2个源文件如下,这两个源文件生成的目标文件链接成可执行文件后,涉及到空间和地址的分配、符号解析和重地位等等过程。
/* a.c */
extern int shared;
int main()
{
int a = 100;
swap( &a, &shared);
}
/* b.c */
int shared = 1;
void swap( int* a, int* b)
{
*a ^= *b ^= *a ^= *b;
}
在a.c中引用到了b.c中的变量"shared"和函数"swap"。使用gcc编译成目标文件a.o和b.o之后,接着就需要将两个目标文件链接成一个可执行文件。
gcc -c a.c b.c
一般来说,链接过程分为两步:
ELF文件结构简述
现在PC平台流行的可执行文件格式主要是Windows下的PE(Portable Executable)和Linux的ELF(Executable Linkable Format),它们都是COFF(Common File Format)格式的变种。目标文件就是源代码编译后但未进行链接的那些中间文件(Windows的.obj和Linux下的.o),它跟可执行文件的内容与结构很相似,所以一般跟可执行文件格式一起采用一种格式存储。
Linux的.o/Windows的.obj、/bin/bash或Windows的exe、Linux的.so/Windows的.dll分别是什么文件?
- Linux的.o和Windows的.obj称为可重定位文件(Relocatable File),这类文件包含了代码和数据,可以被用来链接成可执行文件或共享目标文件,静态链接库也可以归为这一类。
- /bin/bash和Windows的.exe是可以直接执行的程序,它的代表就是ELF可执行文件,它们一般是没有扩展名的。
- Linux的.so和Windows的.dll称为共享目标文件,这种文件也包含了代码和数据,可以在以下两种情况下使用。一种是链接器可以使用这种文件跟其他的可重位文件和共享目标文件链接、产生新的目标文件。第二种是动态链接器可以将几个这种共享目标文件与可执行文件结合,作为进程映像的一部分来执行。
ELF目标文件的格式是怎样?

- 最近都没有喝咖啡 工作力不从心 - -
- 那是因为中国没有不同政见者 谁敢跟共党博弈 RT @vonbo: 国内连“轮流”都算不上。。。RT @monica0331: 我记得以前政治书上说,美国的选举就是利益集团的博弈,只不过是利益集团轮流做皇帝云云。。
- RT @lianyue: 中国新闻周刊:【香港政府承诺永久照顾在菲遇害六家庭】 http://is.gd/eC4B7——这样政府负担太重了,三公消费无法保障,幸好一国两制,大陆制度的先进性得以保存。
- 在CSS和HTML中死来活去…
- 今天总算有点胃口了 身体乃革命的本钱啊
- 一个看似简单的flash游戏,但发现太多的地方需要用到事务来保证一致性
- @yangjuven 你不如开个情书分享会,教教大家写一下情书,怎样? 我觉得会有很多人去听的
- 想多写点东西 但最近没有什么收获可以总结
Categories
- C/C++ (7)
- Compiler (1)
- Hadoop (4)
- It's My Life (4)
- JavaScript (8)
- Linux (3)
- MySQL (5)
- Operating System (8)
- Python (3)
- Reading (6)
- TCP/IP (2)
- Tools (6)
- Web (8)
Blogroll
Recent Posts
Recent Comments
- 成人用品 on 写在毕业一年后
- Stephen on 香港游记
- xiaket on 香港游记
- Stephen on Crontab
- dhf on Crontab
- dhf on Crontab
- runcoderen on 实模式切换到保护模式
- workgang on 浅析JavaScript的原型链
- jianpx on Crontab
- 橡树小屋 on 浅谈JavaScript的闭包和作用域链
Archives
- August 2010 (2)
- July 2010 (1)
- June 2010 (14)
- May 2010 (1)
- April 2010 (4)
- March 2010 (16)
- February 2010 (9)
- January 2010 (10)
- December 2009 (8)