分享

学习 Linux,101: 管理共享库

 danydany_ok 2010-12-31

关于本系列

本系列文章帮助您学习 Linux 系统管理任务相关知识。 您可以使用本系列文章中的资料准备 Linux Professional Institute Certification 级别 1 (LPIC-1)考试

请参见我们的 学习 Linux,101:LPIC-1 路线图,查看本系列中各文章的介绍和链接。该路线图目前仍然在更新中,目前反映的最新内容是 LPIC-1 考试的最新目标(2009 年 4 月)。在我们完成文章后,我们会将其添加到路线图中。但是,与此同时,您可以在我们的 LPI 认证考试准备教程 中找到类似资料的早期版本,这些内容支持 2009 年 4 月以前发布的 LPIC-1 目标。

概述

在本文中,学习如何找到并加载 Linux 可执行程序所需的共享库。 您将学到:

  • 确定程序需要哪些共享库
  • 了解系统如何找到共享库
  • 加载共享库

本文将帮助您准备 Linux Professional Institute's Junior Level Administration (LPIC-1) 考试 101 中主题 102 下的目标 102.3。该目标的权值为 1。

先决条件

为了更有效利用这部分内容,您需要具有基本的 Linux 知识, 并需要准备一个 Linux 系统,用于练习本文介绍的命令。 有时候不同版本的程序输出格式不同,因此您所得到的结果未必总是与此处的相同。 特别要指出,这里的很多例子来自 64 位系统。 我们引入了很多来自 32 位系统的例子,来说明两者的显著差异 。


回页首

静态与动态链接

联系 Ian

Ian 是我们的一位最受欢迎、最高产的作者。查看 Ian 的个人信息,与 Ian、其他作者和 My developerWorks 的其他读者联系。

Linux 系统当中有两类可执行程序:

  • 静态链接 可执行程序包含了其所需的全部库函数;所有库函数都连接到程序中。 这类程序是完整的,其运行不需要外部库的支持。 静态链接程序的优点之一是其安装之前不需要做环境准备工作 。
  • 动态链接 可执行程序要小得多;这类程序运行时需要外部 共享 函数库的支持,因此好像并不完整。除了程序体小之外,动态链接允许程序包指定必须的库,而不必将库装入程序包内。动态链接技术还允许多个运行中的程序共享一个库,这样就不会出现同一代码的多份拷贝共占内存的情况了。由于这些原因,当前多数程序采用动态链接技术。

在许多 Linux 系统中有个很有趣的例子, ln 命令(/bin/ln),用于在文件之间生成链接, 不论是 链接还是 (或者 符号 )链接。 该命令使用共享库。 共享库经常会引入库通用名与库特定层级之间的符号链接, 所以如果因为某些原因链接没有出现或已断开, 那么 ln 命令本身就无法起作用,会发生循环错误。 为避免此类问题,一些 Linux 系统包含了 ln sln(/sbin/sln)之间的静态链接版本。 清单 1 说明了采用动态链接的 ln 与采用静态链接的 sln之间大小的显著差别。 该例子来自 Fedora 12 64-bit 系统。


清单 1. sln 与 ln 的大小
				
[ian@echidna ~]$ ls -l /sbin/sln /bin/ln
-rwxr-xr-x. 1 root root 47384 2010-01-12 09:35 /bin/ln
-rwxr-xr-x. 1 root root 603680 2010-01-04 09:07 /sbin/sln


回页首

需要哪些库?

尽管当前的 LPI 考试不涉及此类问题, 但您还是应当明确当今很多 Linux 系统所运行的硬件同时支持 32 位及 64 位程序。 很多库同时编译为 32 位及 64 位版本。 64 位版本通常存放在文件系统的 /lib64 目录树中,而 32 位版本则位于 /lib 目录树中。 您可能会在 64 位 Linux 系统中同时发现 /lib/libc-2.11.1.so 以及 /lib64/libc-2.11.1.so 。 这两个库允许 32 位及 64 位 C 程序在 64 位 Linux 系统当中运行。

关于 ldd 命令

除了明确静态链接程序比较大之外,如何确定程序是否为静态链接? 如果是静态链接,如何知道它需要哪些库?ldd 命令能够回答这些问题。 如果您正在运行 Debian 或 Ubuntu之类的程序, 可能找不到 sln 命令,因此您可能会检查 /sbin/ldconfig 能否执行。 清单 2 展示了 ldd 命令关于 ln 以及 sln 执行情况, 还有 ldconfig 可执行情况的输出。该例子来自 Fedora 12 64-bit 系统(echidna)。 为了进行对比,还列出了来自旧版本 Fedora 8 32-bit 系统(pinguino)关于 /bin/ln 的输出 。


清单 2. ldd 关于 sln 以及 ln 的输出
				
[ian@echidna ~]$ #Fedora 12 64-bit
[ian@echidna ~]$ ldd /sbin/sln /sbin/ldconfig /bin/ln
/sbin/sln:
not a dynamic executable
/sbin/ldconfig:
not a dynamic executable
/bin/ln:
linux-vdso.so.1 => (0x00007fff644af000)
libc.so.6 => /lib64/libc.so.6 (0x00000037eb800000)
/lib64/ld-linux-x86-64.so.2 (0x00000037eb400000)

[ian@pinguino ~]$ # Fedora 8 32-bit
[ian@pinguino ~]$ ldd /bin/ln
linux-gate.so.1 => (0x00110000)
libc.so.6 => /lib/libc.so.6 (0x00a57000)
/lib/ld-linux.so.2 (0x00a38000)

因为 ldd 与动态链接有关, 这告诉我们 sln 与 ldconfig 都是静态链接的, 它们 “不是一个动态可执行文件”, 同时告诉我们 ln 命令所需共享库的名字(linux-vdso.so.1、libc.so.6 和 /lib64/ld-linux-x86-64.so.2)。 注意, .so 指明这些是 共享库 或者动态库。 这一输出结果提供了您想了解的三类不同信息。

linux-vdso.so.1
是 Linux Virtual Dynamic Shared Object ,这一点我们稍后讨论。 您还可以在 Fedora 8 中发现 linux-gate.so.1。
libc.so.6
具有指向 /lib64/libc.so.6. 的指针。
/lib64/ld-linux-x86-64.so.2
指向其他库的绝对路径。

在清单 3 中,通过 ls -l 命令, 发现最后的两个库依次与库的特定版本之间具有符号链接。 该例子来自 Fedora 12 64-bit系统 。


清单 3. 库符号链接
				
[ian@echidna ~]$ ls -l /lib64/libc.so.6 /lib64/ld-linux-x86-64.so.2
lrwxrwxrwx. 1 root root 12 2010-01-14 14:24 /lib64/ld-linux-x86-64.so.2 -> ld-2.11.1.so
lrwxrwxrwx. 1 root root 14 2010-01-14 14:24 /lib64/libc.so.6 -> libc-2.11.1.so

Linux Virtual Dynamic Shared Objects

在早期的 x86 处理器中,用户程序与管理服务之间的通信通过软中断实现。 随着处理器速度的提高,这已成为一个严重的瓶颈。 自 Pentium? II 处理器开始,Intel? 引入了 Fast System Call 装置来提高系统调用速度, 即采用 SYSENTER 和 SYSEXIT 指令,而不是中断。

您所看到的 linux-vdso.so.1 是个虚拟库,或者说是 Virtual Dynamic Shared Object ,它只存在于程序的地址空间当中。 在旧版本系统中该库为 linux-gate.so.1。 该虚拟库为用户程序以处理器可支持的最快的方式 (对于特定处理器,采用中断方式;对于大多数最新的处理器,采用快速系统调用方式) 访问系统函数提供了必要的逻辑 。



动态加载

通过前面的内容,您会很奇怪的发现 /lib/ld-linux.so.2 以及它的 64 位版本,/lib64/ld-linux-x86-64.so.2,看上去都像是共享库, 都在其各自的权限内可执行。它们是负责实现动态加载的代码。 它们读取可执行程序的头信息,这些信息采用 Executable and Linking Format 或者( ELF )格式。 它们通过这些消息,来确定哪些库是必须的,以及哪些库需要加载。 然后执行动态链接,把可执行程序当中所有的地址指针与需要加载的库联系起来, 这样程序就可以运行了。

有关 ld-linux.so 的帮助也描述了 ld.so, 它为早期的 a.out 二进制格式提供类似的功能。 清单 4 举例说明, 如何利用 ld-linux.so 的 --list 选项来显示与清单 2 当中ln命令 与 ldd 命令显示结果相同的内容。


清单 4. 利用 ld-linux.so 来展示库需求
				
[ian@echidna ~]$ /lib64/ld-linux-x86-64.so.2 --list /bin/ln
linux-vdso.so.1 => (0x00007fffc9fff000)
libc.so.6 => /lib64/libc.so.6 (0x00000037eb800000)
/lib64/ld-linux-x86-64.so.2 (0x00000037eb400000)

[ian@pinguino ~]$ /lib/ld-linux.so.2 --list /bin/ln
linux-gate.so.1 => (0x00110000)
libc.so.6 => /lib/libc.so.6 (0x00a57000)
/lib/ld-linux.so.2 (0x00a38000)

注意,两个清单中的十六进制地址有可能不同。 在两次运行 ldd 命令时,该地址也可能不相同。


动态库配置

动态加载器怎样找到可执行程序?对于 Linux 当中的很多问题, 都在 /etc 当中有相应的配置文件。 事实上,有两个配置文件,/etc/ld/so/conf 以及 /etc/ld.so.cache。 清单 5 展示了 Fedora 12 64 位系统当中 /etc/ld.so.conf 的内容。 注意,/etc/ld.so.conf 文件指明所有来自 ld.so.conf.d 子目录的 .conf 文件都应当被包含。 旧版系统中可能包含 /etc/ld/so/conf 的所有条目, 而不包含 /etc/ld.so.conf.d 目录中的条目。 您的系统当中 /etc/ld.so.conf 或者 /etc/ld.so.conf.d 目录的实际内容可能与此处有所差别。


清单 5. /etc/ld.so.conf 的内容
				
[ian@echidna ~]$ cat /etc/ld.so.conf
include ld.so.conf.d/*.conf
[ian@echidna ~]$ ls /etc/ld.so.conf.d/*.conf
/etc/ld.so.conf.d/kernel-2.6.31.12-174.2.19.fc12.x86_64.conf
/etc/ld.so.conf.d/kernel-2.6.31.12-174.2.22.fc12.x86_64.conf
/etc/ld.so.conf.d/kernel-2.6.31.12-174.2.3.fc12.x86_64.conf
/etc/ld.so.conf.d/mysql-x86_64.conf
/etc/ld.so.conf.d/qt-x86_64.conf
/etc/ld.so.conf.d/tix-x86_64.conf
/etc/ld.so.conf.d/xulrunner-64.conf

程序需要快速加载,因此可以使用 ldconfig 命令来处理 ld.so.conf 文件、 所有 ld.so.conf.d 包含的文件、所有受信目录当中的库、 /lib 和 /usr/lib,以及命令行当中所支持的其他内容 。 ldconfig 命令在 /etc/ld.so.cache 中为最近使用过的共享库生成必须的链接和 cache 。 动态加载器利用来自 ld.so.cache 的缓存文件来定位需要动态加载及链接的文件。 如果改变了 ld.so.conf(或在 ld.so.conf.d 中增加新文件), 必须运行 ldconfig 命令(以 root 用户身份)来重构 ld.so.cache 文件。

通常,可在不加参数的情况下, 使用 ldconfig 命令来重构 ld.so.cache 文件。 可以利用一些参数来改变这一使用习惯。 一般情况下,可使用 man ldconfig 来获得更多信息。 清单 6 举例说明利用参数 -p 来展示 ld.so.cache 的内容。


清单 6. 使用 ldconfig 来展示 ld.so.cache
				
[ian@lyrebird ian]$ /sbin/ldconfig -p | less
1602 libs found in cache `/etc/ld.so.cache'
libzip.so.1 (libc6,x86-64) => /usr/lib64/libzip.so.1
libz.so.1 (libc6,x86-64) => /lib64/libz.so.1
libz.so (libc6,x86-64) => /usr/lib64/libz.so
libx86.so.1 (libc6,x86-64) => /usr/lib64/libx86.so.1
libx11globalcomm.so.1 (libc6,x86-64) => /usr/lib64/libx11globalcomm.so.1
libxul.so (libc6,x86-64) => /usr/lib64/xulrunner-1.9.1/libxul.so
libxtables.so.2 (libc6,x86-64) => /usr/lib64/libxtables.so.2
libxslt.so.1 (libc6,x86-64) => /usr/lib64/libxslt.so.1
libxslt.so (libc6,x86-64) => /usr/lib64/libxslt.so
libxpcom.so (libc6,x86-64) => /usr/lib64/xulrunner-1.9.1/libxpcom.so
libxml2.so.2 (libc6,x86-64) => /usr/lib64/libxml2.so.2
libxml2.so (libc6,x86-64) => /usr/lib64/libxml2.so
...
libABRTdUtils.so.0 (libc6,x86-64) => /usr/lib64/libABRTdUtils.so.0
libABRTUtils.so.0 (libc6,x86-64) => /usr/lib64/libABRTUtils.so.0
ld-linux.so.2 (ELF) => /lib/ld-linux.so.2
ld-linux-x86-64.so.2 (libc6,x86-64) => /lib64/ld-linux-x86-64.so.2



加载指定的库

如果您正在运行需要特定旧版共享库支持的程序, 或者您正在开发新的共享库或现有共享库的新版本, 您可能希望覆盖加载器的默认搜索路径。 使用安装在 /opt 树当中特定于产品共享库的脚本文件可能也需要这一功能。

就如同可通过设置变量 PATH 来为可执行程序指定搜索路径一样, 可以将变量 LD_LIBRARY_PATH 设置为用冒号分割的, 为加载 ld.so.cache 当中所指定的共享库需要搜索的目录清单。 例如,可使用命令:

export LD_LIBRARY_PATH=/usr/lib/oldstuff:/opt/IBM/AgentController/lib

参见下面的 参考资料 来获得细节信息以及相关文章的链接。


参考资料

学习

获得产品和技术

讨论

关于作者

Ian shields developerWorks 投稿作者

Ian Shields 参与 developerWorks Linux 专区的许多 Linux 项目。他是 IBM 北卡罗来纳州 Research Triangle Park 的一名高级程序员。他于 1973 年作为一名系统工程师加入 IBM 位于澳大利亚堪培拉的子公司。此后,在加拿大蒙特利尔和北卡罗来纳州 RTP 从事通信系统和普适计算。他拥有多项专利。他毕业于 Australian National University,本科学位是纯粹数学和哲学。他拥有北卡罗来纳州州立大学的计算机硕士和博士学位。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多