Linux:动态链接
文章目录
动态链接共享库
静态库的缺点
研究的静态库解决了许多关于如何让大量相关函数对应用程序可用的问题。然而,静态库仍然有一些明显的缺点。静态库和所有的软件一样,需要定期维护和更新。如果应用程序员想要使用一个库的最新版本,他们必须以某种方式了解到该库的更新情况,然后显式地将他们的程序与更新了的库重新链接。
什么是静态链接?有什么用?
另一个问题是几乎每个C程序都使用标准 I/O 函数,比如 printf scanf。在运行时,这些函数的代码会被复制到每个运行进程的文本段中。在一个运行上百个进程的典型系统上,这将是对稀缺的内存系统资源的极大浪费。(内存的一个有趣属性就是不论系统的内存有多大,它总是一种稀缺资源。磁盘空间和厨房的垃圾桶同样有这种属性。)
共享库
共享库(shared library)是致力于解决静态库缺陷的一个现代创新产物。共享库是一个目标模块,在运行或加载时,可以加载到任意的内存地址,并和一个在内存中的程序链接起来。这个过程称为动态链接(dynamic linking),是由一个叫做动态链接器(dynamic linker)的程序来执行的。共享库也称为共享目标(shared object),在 Linux 系统中通常用.so后缀来表示。微软的操作系统大量地使用了共享库,它们称为 DLL(动态链接库)。
共享库是以两种不同的方式来“共享”
共享库是以两种不同的方式来“共享”的。
第一种:共享这个so文件中的代码和数据
首先,在任何给定的文件系统中,对于一个库只有一个.so 文件。所有引用该库的可执行目标文件共享这个so文件中的代码和数据,而不是像静态库的内容那样被复制和嵌入到引用它们的可执行的文件中。
第二种:共享库的.text 节的副本可以被不同的正在运行的进程共享
其次,在内存中,一个共享库的.text 节的一个副本可以被不同的正在运行的进程共享。
动态链接过程
图7-16 概括了图7-7 中示例程序的动态链接过程。
为了构造图 7-6 中示例向量例程的共享库 libvector.so,我们调用编译器驱动程序,给编译器和链接器如下特殊指令:
-fpic选项指示编译器生成与位置无关的代码
-shared选项指示链接器创建一个共享的目标文件。
一旦创建了这个库,随后就要将它链接到程序中:
这样就创建了一个可执行目标文件 prog21,而此文件的形式使得它在运行时可以和libvector.so链接。
基本的思路是当创建可执行文件时,静态执行一些链接,然后在程序加载时,动态完成链接过程。认识到这一点是很重要的:
此时,没有任何 lbvector.so的代码和数据节真的被复制到可执行文件 prog21 中。反之,链接器复制了一些重定位和符号表信息,它们使得运行时可以解析对 libvector.so 中代码和数据的引用。当加载器加载和运行可执行文件 prog21 时,加载部分链接的可执行文件 prog21。接着,它注意到 prog21 包含一个.interp 节,这一节包含动态链接器的路径名,动态链接器本身就是一个共享目标(如在 Linux 系统上的 ld-linux.so)加载器不会像它通常所做地那样将控制传递给应用,而是加载和运行这个动态链接器。
然后,动态链接器通过执行下面的重定位完成链接任务:
- 重定位 libc.so的文本和数据到某个内存段。
- 重定位 libvector.so 的文本和数据到另一个内存段。
- 重定位 prog21 中所有对由 libc.so和 ibvector.so定义的符号的引用。
最后,动态链接器将控制传递给应用程序。从这个时刻开始,共享库的位置就固定了,并且在程序执行的过程中都不会改变。
动态链接的用武之地和使用场景
到目前为止,我们已经讨论了在应用程序被加载后执行前时,动态链接器加载和链接共享库的情景。然而,应用程序还可能在它运行时要求动态链接器加载和链接某个共享库,而无需在编译时将那些库链接到应用中。
动态链接是一项强大有用的技术。下面是一些现实世界中的例子:
分发软件
微软 Windows 应用的开发者常常利用共享库来分发软件更新。他们生成一个共享库的新版本,然后用户可以下载,并用它替代当前的版本。下一次他们运行应用程序时,应用将自动链接和加载新的共享库。
构建高性能 Web 服务器
许多 Web 服务器生成动态内容,比如个性化的 Web 页面、账户余额和广告标语。早期的 Web 服务器通过使用 fork 和 execve 创建一个子进程,并在该子进程的上下文中运行 CGI程序来生成动态内容。
然而,现代高性能的 Web 服务器可以使用基于动态链接的更有效和完善的方法来生成动态内容。其思路是将每个生成动态内容的函数打包在共享库中。当一个来自 Web 浏览器的请求到达时,服务器动态地加载和链接适当的函数,然后直接调用它,而不是使用 fork 和execve 在子进程的上下文中运行函数。函数会一直缓存在服务器的地址空间中,所以只要一个简单的函数调用的开销就可以处理随后的请求了。这对一个繁忙的网站来说是有很大影响的。更进一步地说,在运行时无需停止服务器,就可以更新已存在的函数,以及添加新的函数。
动态链接的API/接口介绍
dlopen:让程序在运行时加载和链接共享库
Linux系统为动态链接器提供了一个简单的接口,允许应用程序在运行时加载和链接共享库。
dlopen 函数加载和链接共享库 filename。用已用带 RTLD_GLOBAL 选项打开了的库解析 filename中的外部符号。如果当前可执行文件是带-rdynamic 选项编译的,那么对符号解析而言,它的全局符号也是可用的。
flag 参数必须要么包括 RTLD_NOW,该标志告诉链接器立即解析对外部符号的引用,要么包括 RTLD_LAZY 标志,该标志指示链接器推迟符号解析直到执行来自库中的代码。这两个值中的任意一个都可以和 RTLD_GLOBAL标志取或。
dlsym:
dlsym函数的输入是一个指向前面已经打开了的共享库的句柄和一个 symbol 名字,如果该符号存在,就返回符号的地址,否则返回 NULL。
dlclose
如果没有其他共享库还在使用这个共享库,dlclose 函数就卸载该共享库
dlerror
dlerror 函数返回一个字符串,它描述的是调用 dlopen、dlsym 或者 dlclose 函数时发生的最近的错误,如果没有错误发生,就返回 NULL。
利用dl相关接口动态链接共享库示例
图7-17 展示了如何利用这个接口动态链接我们的 lbvector.so 共享库,然后调用它的 addvec 函数。要编译这个程序,我们可以用下面的方式调用 GCC:
代码:
共享库和 Java 本地接口
Java定义了一个标准调用规则,叫做Java 本地接口(Java Native Interface,JNI),它允许Java程序调用“本地的”C和C++ 函数。
JNI的基本思想是将本地C函数(如 foo)编译到一个共享库中(如 foo.so)。当一个正在运行的 Java 程序试图调用函数 foo 时,Java 解释器利用 dlopen 接口(或者与其类似的接口)动态链接和加载 foo.so,然后再调用 foo。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!