动态链接(Dynamic Linking)是相对于静态链接(Static Linking)而言的。程序设计中,为了能做到代码和模块的重用,程序设计者常常将常用的功能函数做成库,当程序需要实现某种功能时,就直接调用库文件中的函数,从而实现了代码的重用。早期的程序设计中,可重用的函数模块以编译好的二进制代码形式放于静态库文件中,在MS的操作系统中是Lib为后缀的文件。程序编写时,如果用户程序调用到了静态库文件中的函数,则在程序编译时,编译器会自动将相关函数的二进制代码从静态库文件中复制到用户目标程序,与目标程序一起编译成可执行文件。这样做的确在编码阶段实现了代码的重用,减轻了程序设计者的负担,但并未在执行期实现重用。如一个程序a.exe使用了静态库中的 f() 函数,那么当a.exe有多个实例运行时,内存中实际上存在了多份f()的拷贝,造成了内存的浪费。
随着技术的进步,出现了新的链接方式,即动态链接,从根本上解决了静态链接方式带来的问题。动态链接的处理方式与静态链接很相似,同样是将可重用代码放在一个单独的库文件中(在MS的操作系统中是以dll为后缀的文件,Linux下也有动态链接库,被称为Shared Object的so文件),所不同的是编译器在编译调用了动态链接库的程序时并不将库文件中的函数执行体复制到可执行文件中,而是只在可执行文件中保留一个函数调用的标记。当程序运行时,才由操作系统将动态链接库文件一并加载入内存,并映射到程序的地址空间中,这样就保证了程序能够正常调用到库文件中的函数。同时操作系统保证当程序有多个实例运行时,动态链接库也只有一份拷贝在内存中,也就是说动态链接库是在运行期共享的。
使用动态链接方式带来了几大好处:首先是动态链接库和用户程序可以分开编写,这里的分开即可以指时间和空间的分开,也可以指开发语言的分开,这样就降低了程序的耦合度;其次由于动态链接独特的编译方式和运行方式,使得目标程序本身体积比静态链接时小,同时运行期又是共享动态链库,所以节省了磁盘存储空间和运行内存空间;最后一个是增加了程序的灵活性,可以实现诸如插件机制等功能。用过winamp的人都知道,它的很多功能都是以插件的形式提供的,这些插件就是一些动态链接库,主程序事先规定好了调用接口,只要是按照规定的调用接口写的插件,都能被winamp调用。
WIndow 95、98、NT系列等系统都提供了动态链接库的功能,并且这些操作系统的系统调用大多都是通过动态链接库实现的,最常见的NT系列OS中的KENEL32.dll,USER32.dll,GDI32.dll等动态链接库文件就包含了大量的系统调用。在windows家族中,NT内核的操作系统在动态链接库机制上较之前的95、98系统要更安全。95、98系统在程序调用动态链接库时,将动态链接库加载到2G-3G之间的被称为进程共享空间的虚拟地址空间,并且所有进程关于这1G的虚拟地址空间的页表都是相同的,也就是说对于所有的进程,这片共享区的页表都指向同一组物理页,这样一来,加载入内存的的动态链接库对所有正在运行的进程都是可见的。如果一个动态链接库被其中一个进程更改,或其自身崩溃,将影响到所有调用它的进程,如果该动态链接库是系统的动态链接库,那么将导致系统的崩溃。在Windows NT系统中,动态链接库被映射到进程的用户地址空间中,并用Copy On Write机制保证动态链接库的共享安全,Copy On Write可以理解为写时拷贝。一般情况下,多个运行的进程还是按原来的模式共享同一个动态链接库,直到有进程需要向动态链接库的某个页面写数据时,系统将该页做一个拷贝,并将新复制页面的属性置为可读可写,最后修改进程的页表使之指向新拷贝的物理页。这样无论该进程怎么修改此页的数据,也不会影响到其他调用了此动态链接库的进程了。