首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

剖析共享程序库(1)

剖析共享程序库(1)

共享程序库是现代 UNIX® 系统中有效利用空间和资源的基础。SUSE 系统中的 C 程序库大约有 1.3 MB。为 /usr/bin 中每一个程序(我有 2,569 个)制作副本将占去几个 G 的空间。
当然这个数字有一些夸张 —— 静态链接程序只合并它们使用的那部分程序库。尽管如此,        printf() 的所有副本所占用的空间数量也会让系统显得非常臃肿。      
共享程序库不仅可以节省磁盘空间,而且还可以节省内存。内核可以在内存中保持某个共享程序库的一个惟一副本,并在多个应用程序间共享这个副本。所以,我们不但可以在磁盘上只有         printf() 的一个副本,而且在内存中也只需要一个副本。这对性能有很大的影响。      
在本文中,我们将讨论共享程序库所使用的底层技术,以及在共享程序库版本号帮助下预防兼容性难题的方法,过去,本机共享程序库实现也曾遇到过这些难题。首先来看一下共享程序库的工作原理。
共享程序库的工作原理这个概念理解起来非常简单。拥有一个程序库;然后共享这个程序库。但是,当您的程序尝试调用         printf() 时,也就是说实际操作的时候,具体发生的事情却稍微有点复杂。      
这个过程在静态链接系统中比在动态链接系统中更简单。在静态链接系统中,生成的代码会持有对某个函数的引用。链接器使用加载该函数的真实地址去替换这个引用,以便生成的二进制代码在适当的位置会有正确的地址。然后,在运行代码时,只需要跳转到相应的地址即可。对管理员来说,这是一项简单的任务,因为它允许您对只在程序中的某个位置上实际引用的那些对象进行链接。
但是大部分共享程序库都是动态链接的。这具有一些更深层次的意义。其中一方面是,您不能事先预计某个函数在调用时的确切地址!(以及静态链接的共享程序库模式,比如 BSD/OS 中的,但是它们不在本文讨论范围之内。)
动态链接器可以为每个被链接的函数做相当多的工作,所以大部分链接器都是不积极的。只有在函数被调用时,它们才实际做一些工作。C 程序库中有一千多个外部可见的符号,有大约三千多个本地符号,因此这种方法可以节省非常多的时间。
实现此奇妙功能的是一个称为        过程链接表(Procedure Linkage Table)(PLT)的数据块,它是程序中的一个表,列出了程序所调用的每一个函数。当程序开始运行时,PLT 包含每个函数的代码,以便查询运行期链接器,从而获得已加载某个函数的地址。然后它会在表中填入这个条目并跳转到那个已加载函数。当每个函数被调用时,它的 PLT 中的条目就会被简化为一个到那个已加载函数的直接跳转。      
不过,重要的是,要注意到还有一个间接的额外层次 —— 可以通过跳转到某个表来解析每个函数调用。
兼容性不仅是为了关联这意味着您最终要链接的程序库最好与调用它的代码相兼容。使用静态链接的可执行文件,可以在某种程度上保证不会发生任何改变。如果使用动态链接,就得不到这样的保证。
当出现新版本的程序库时会怎样?特别是新版本改变了某个给定函数的调用次序时,又会怎样?
版本号可以解决这个问题 —— 共享的程序库将拥有一个版本号。当一个程序链接到某个程序库时,程序中会存储一个它计划支持的版本号。如果更改程序库,那么版本号就会不匹配,程序也就不会被链接到较新版本的程序库。
不过,动态链接的可能优势之一在于修正缺陷。如果可以修正程序库中的缺陷,而且不必重新编译上千个程序,就可以利用这一修正功能,这将是非常令人愉快的。有时,需要链接到某个较新的版本。
不幸的是,这会导致在某些情况下,您希望链接到较新的版本,而在另外一些情况下,您宁愿坚持使用较老的版本。不过,有一个解决方案 —— 使用两类版本号:
  • 主版本号表明程序库版本之间的潜在不兼容性。
  • 次要版本号表明只是修正了缺陷。
这样,在大部分情形下,加载具有相同主版本号和更高次要版本号的程序库是安全的;而加载主版本号更高的程序是不安全的行为。
为了让用户(和程序员)不必追踪程序库版本号和更新,系统提供了大量的符号链接。通常,其模式是:
libexample.so
将是一个指向
libexample.so.N
的链接,其中         N 是在系统中可以找到的最高的         版本号。      
对受支持的每一个主版本号而言,
libexample.so.N
将是一个指向
libexample.so.N.M
的链接,其中         M 是最高的        次要 版本号。      
这样,如果为链接器指定了         -lexample,那么它会去寻找         libexample.so,这是一个符号链接,指向某个指向最新版本的符号链接。另一方面,当加载某个现有程序时,它将尝试去加载         libexample.so.N,其中         N 是它先前链接的版本。各得其所!
返回列表