04-共享库概述
41.3 共享库概述
将程序与静态库链接起来时(或没有使用静态库),得到的可执行文件会包含所有被链接进程序的目标文件的副本。这样当几个不同的可执行程序使用了同样的目标模块时,每个可执行程序会拥有自己的目标模块的副本。这种代码的冗余存在几个缺点。
- 存储同一个目标模块的多个副本会浪费磁盘空间,并且所浪费的空间是比较大的。
- 如果几个使用了同一模块的程序在同一时刻运行,那么每个程序会独立地在虚拟内存中保存一份目标模块的副本,从而提高系统中虚拟内存的整体使用量。
- 如果需要修改一个静态库中的一个目标模块(可能是因为安全性或需要修正bug),那么所有使用那个模块的可执行文件都必须要重新进行链接以合并这个变更。这个缺点还会导致系统管理员需要弄清楚哪些应用程序链接了这个库。
共享库就是设计用来解决这些缺点的。共享库的关键思想是目标模块的单个副本由所有需要这些模块的程序共享。目标模块不会被复制到链接过的可执行文件中,相反,当第一个需要共享库中的模块的程序启动时,库的单个副本就会在运行时被加载进内存。当后面使用同一共享库的其他程序启动时,它们会使用已经被加载进内存的库的副本。使用共享库意味着可执行程序需要的磁盘空间和虚拟内存(在运行的时候)更少了。
虽然共享库的代码是由多个进程共享的,但其中的变量却不是的。每个使用库的进程会拥有自己的在库中定义的全局和静态变量的副本。
共享库还具备下列优势。
- 由于整个程序的大小变得更小了,因此在一些情况下,程序可以完全被加载进内存中,从而能够更快地启动程序。这一点只有在大型共享库正在被其他程序使用的情况下才成立。第一个加载共享库的程序实际上在启动时会花费更长的时间,因为必须要先找到共享库并将其加载到内存中。
- 由于目标模块没有被复制进可执行文件中,而是在共享库中集中维护的,因此在修改目标模块时(需遵循41.8节中介绍的限制)无需重新链接程序就能够看到变更,甚至在运行着的程序正在使用共享库的现有版本的时候也能够进行这样的变更。
这项新增功能的主要开销如下所述。
- 在概念上以及创建共享库和构建使用共享库的程序的实践上,共享库比静态库更复杂。
- 共享库在编译时必须要使用位置独立的代码(在41.4.2节中予以介绍),这在大多数架构上都会带来性能开销,因为它需要使用额外的一个寄存器([Hubicka, 2003])。
- 在运行时必须要执行符号重定位。在符号重定位期间,需要将对共享库中每个符号(变量或函数)的引用修改成符号在虚拟内存中的实际运行时位置。由于存在这个重定位的过程,与静态链接程序相比,一个使用共享库的程序或多或少需要花费一些时间来执行这个过程。
共享库的另一种用法是作为Java NativeInterface (JNI)中的一个构建块,它允许Java代码通过调用共享库中的C函数直接访问底层操作系统的特性,更多信息可参考[Liang, 1999]和[Rochkind, 2004]。