面试 | Linux 下的动态链接库问题

面试 | Linux 下的动态链接库问题,第1张

在 Linux 开发时,我们经常会看到一些形如 xxx.so 的名称出现,其中 so 是 Shared Object 的缩写,即可以共享的目标文件,也就是我们所称为的动态链接库,和在 Windows 下大家玩 游戏 时遇到的 xxx.dll 错误中的文件是一个类型的。

面试中经常会问到以下问题:

库是写好的现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。本质上来说库是一种可执行代码的二进制形式,可以被 *** 作系统载入内存执行。

库有两种:

在一个程序的编译过程中,分为以下几个步骤: 预处理 编译 汇编 链接 。本文中讨论的链接库就是针对最后一个步骤「链接」而言的。

动态库和静态库的区别

左图为静态链接库,右图为动态链接库

对于静态链接库而言在链接阶段,会将汇编生成的「目标文件.o」与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接:

静态链接可以理解为最后生成了一个「单文件免安装绿色版」的程序,优点在于移植的时候只需要移动这一个文件,缺点在于文件体积非常大,为了解决这样的问题,就有了动态链接库。动态链接库在程序编译时并不会被连接到目标代码中,而是在程序运行时才被载入。

动态库连接到系统空间,如果多个程序连接了同一个库,那么只需要一份,优点在于编译程序的时候不会将对应的库文件全部打包在生成的程序中,而是保留了到对应库的链接,缺点就是移植的时候如果只移动了对应的程序没有安装相关的库的话,就会看到类似以下喜闻乐见的结果了。

在 Linux 下一个动态库有y三个不同名字的文件组成:

当程序在内部列出所需要的链接库时,仅仅使用 soname。当你创建一个链接库时,使用 real name。安装一个新的链接库时,把它复制到一个DLL文件夹里,然后运行程序 ldconfig。ldconfig 检查存在的 real name 文件,并且创建指向它符号链接 soname 文件。可能大家比较常见到的有 libsodium 等。

有了上面关于库的一些基础知识之后,我们可以开始尝试创建一个动态库来供程序使用了。

比如我们有一个求最大值的函数 max(int a,int b,int c) ,放在文件 max.c 中文件内容如下:

可以通过:

将其编译为共享库,-fPIC是编译选项,PIC是 Position Independent Code 的缩写,表示要生成位置无关的代码,这是动态库需要的特性; -shared是链接选项,告诉 gcc 生成动态库而不是可执行文件。为了让用户知道我们的动态库中有哪些接口可用,我们需要编写对应的头文件,比如可以写一个 max.h :

设置一个驱动函数来测试我们编写的动态库:

通过 gcc test.c -L. -lmax来生成 a.out,其中-lmax表示要链接 libmax.so,-L.表示搜索要链接的库文件时包含当前路径。

但是这样直接运行的话,会出现一个错误:

由于 Linux 是通过/etc/ld.so.cache文件搜寻要链接的动态库的,而 /etc/ld.so.cache 是 ldconfig 程序读取 /etc/ld.so.conf 文件生成的,本次使用的动态库 libmax.so 并不在对应的目录下,就会导致程序无法找到对应的动态链接库,这样我们的解决方法有二:

小结

​动态链接库是各个系统中的一个重要的组成部分且在 Linux 开发相关领域中尤为重要,也是一个面试的高频考点,除了动态链接库以外,还有以下相关知识也是高频考点,在面试前一定要准备好:

本文作者:Nova Kwok

静态库

可以把它想象成是一些代码的集合,在可执行程序运行前就已经加到了代码中,成为了执行程序的一部分,一般是以.a为后缀的文件名,Windows下后缀为.lib。静态库的命名也分为三部分,1、前缀:lib,2、库的名称:随意,如lisi,3、后缀:.a。

静态库优缺点

上面简单介绍了静态库,那它自然也会有优缺点,这里来介绍下它的优缺点。

优点:1、在最后,函数库是被打包到应用程序中的,实现函数本地化、寻址方便、高效。2、程序在运行的时候,与函数库没有关系,移植性更强。

缺点:1、消耗资源较大,每个进程在使用静态库的时候,都要复制一份才可以,这也就造成了内存的消耗。2、在程序更新、部署、发布的时候,使用静态库相对麻烦,如果一个静态库更新了,那它的应用程序都需要重新编译,再发送给用户,有的时候可能只是一个小的改动,但对于用户来说,会导致整个程序重新下载。

动态库

在程序编译时不会被连接到目标代码中,在后期运行时才会载入,不同的应用程序如果调用相同的库,内存中只有一份共享库的拷贝,也就避免了空间的浪费问题。一般以.so作为文件后缀名,也分为三部分:1、前缀:lib,2、库名称:自定义,3、后缀:.so

动态库优缺点

优点:1、节省内存2、部署、升级相对方便,只需要更换动态库,再重新启动服务即可。

缺点:1、加载速度比静态库慢2、移植性较差,需要把所有用到的动态库进行移植。

Linux 系统,也同样面临和Window一样的问题,如何控制动态库的多个版本问题。Window之前没有处理好,为此专门有个名词来形容这个问题 “Dll hell”,其严重影响软件的升级和维护。 Dll hell 是指windows 上动态库新版本覆盖旧版本,但是却不兼容老版本。常常发生在程序升级之后,动态库更新,原有程序运行不起来;或者装新软件,但是已有的软件运行不起来。 同样Linux *** 作系统,也有同样的问题,那么它是怎么解决的呢?

Linux 为解决这个问题,引入了一套机制,如果遵守这个机制来做,就可以避免这个问题。 但是这只事一个约定,不是强制的。但是建议遵守这个约定,否则同样也会出现 Linux 版的Dll hell 问题。 下面来介绍一个这个机制。 这个机制是通过文件名,来控制dll (shared library) 的版本。

Linux 上的Dll ,叫shared library,其有三个名字,分别有不同的目的。

第一个是共享库本身的文件名(real name),其通常包含版本号,常常是是这样: libmath.so.1.1.1234 。 lib是Linux 上的库的约定前缀,math 是共享库名字,so 是共享库的后缀名,1.1.1234的是共享库的版本号,其主版本号+小版本号+build号。主版本号,代表当前动态库的版本,如果动态库的接口有变化,那么这个版本号就要加1;后面的两个版本号(小版本号 和 build 号)是告诉你详细的信息,比如为一个hot-fix 而生成的一个版本,其小版本号加1,build号也应有变化。 这个文件名包含共享库的代码。

第二个是动态库的soname( Short for shared object name),其是应用程序加载dll 时候,其寻找共享库用的文件名。其格式为

lib + math+.so + ( major version number)

其只包含major version number,换句话说,也就是只要其接口没有变,应用程序都可以用,不管你其后minor build version or build version。

问题来了,程序运行时怎么通过soname 找个real name? Soname 存在哪里?如果与real name 关联起来?什么时候存的?

这就是接下来要介绍的第三个共享库的名字,link name,顾名思义,就是在编译过程,link 阶段用的文件名。 其将sonmae 和real name 关联起来。

第三个名字,共享库的连接名(link name),是专门为build 阶段连接而用的名字。这个名字就是lib + math +.so ,比如libmath.so。其是不带任何版本信息的。在共享库编译过程中,连接(link) 阶段,编译器将生成一个共享库及real name,同时将共享库的soname,写在共享库文件里的文件头里面。可以用命令 readelf -d sharelibrary 去查看。


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/yw/8259541.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-14
下一篇 2023-04-14

发表评论

登录后才能评论

评论列表(0条)

保存