Linux【8】-软件管理-1-5-函式库管理(ldconfig/ldd/so)

在我们的Linux作业系统当中,函式库是很重要的一个项目。因为很多的软体之间都会互相取用彼此提供的函式库来进行特殊功能的运作,例如很多需要验证身份的程式都习惯利用PAM这个模组提供的验证机制来实作,而很多网路连线机制则习惯利用SSL函式库来进行连线加密的机制。所以说,函式库的利用是很重要的。不过,函式库又依照是否被编译到程式内部而分为动态与静态函式库,这两者之间有何差异?哪一种函式库比较好?底下我们就来谈一谈先!

一、动态与静态函式库

首先我们要知道的是,函式库的类型有哪些?依据函式库被使用的类型而分为两大类,分别是静态 (Static) 与动态(Dynamic) 函式库两类。底下我们来谈一谈这两种类型的函式库吧!

1.1 静态函式库的特色:

  • 副档名:(副档名为.a) , 这类的函式库通常副档名为libxxx.a的类型;

  • 编译行为: 这类函式库在编译的时候会直接整合到执行程式当中,所以利用静态函式库编译成的档案会比较大一些喔;

  • 独立执行的状态:这类函式库最大的优点,就是编译成功的可执行档可以独立执行,而不需要再向外部要求读取函式库的内容(请参照动态函式库的说明)。

  • 升级难易度: 虽然执行档可以独立执行,但因为函式库是直接整合到执行档中,因此若函式库升级时,整个执行档必须要重新编译才能将新版的函式库整合到程式当中。也就是说,在升级方面,只要函式库升级了,所有将此函式库纳入的程式都需要重新编译!

1.2 动态函式库的特色:

  • 副档名:(副档名为.so) ,这类函式库通常副档名为libxxx.so 的类型;

  • 编译行为: 动态函式库与静态函式库的编译行为差异挺大的。与静态函式库被整个捉到程式中不同的,动态函式库在编译的时候,在程式里面只有一个『指向(Pointer)』的位置而已。也就是说,动态函式库的内容并没有被整合到执行档当中,而是当执行档要使用到函式库的机制时,程式才会去读取函式库来使用。由于执行档当中仅具有指向动态函式库所在的指标而已,并不包含函式库的内容,所以他的档案会比较小一点。

  • 独立执行的状态: 这类型的函式库所编译出来的程式不能被独立执行,因为当我们使用到函式库的机制时,程式才会去读取函式库,所以函式库档案『必须要存在』才行,而且,函式库的『所在目录也不能改变』,因为我们的可执行档里面仅有『指标』亦即当要取用该动态函式库时,程式会主动去某个路径下读取,呵呵!所以动态函式库可不能随意移动或删除,会影响很多相依的程式软体喔!

  • 升级难易度: 虽然这类型的执行档无法独立运作,然而由于是具有指向的功能,所以,当函式库升级后,执行档根本不需要进行重新编译的行为,因为执行档会直接指向新的函式库档案(前提是函式库新旧版本的档名相同喔!)。

1.3 比较

目前的Linux distribution 比较倾向于使用动态函式库,因为如同上面提到的最重要的一点, 就是函式库的升级方便!由于Linux 系统里面的软体相依性太复杂了,如果使用太多的静态函式库,那么升级某一个函式库时, 都会对整个系统造成很大的冲击!因为其他相依的执行档也要同时重新编译啊!这个时候动态函式库可就有用多了,因为只要动态函式库升级就好,其他的软体根本无须变动。

那么这些函式库放置在哪里呢?绝大多数的函式库都放置在:/lib64, /lib目录下!此外,Linux系统里面很多的函式库其实kernel就提供了,那么kernel的函式库放在哪里?呵呵!就是在 /lib/modules里面啦!里面的资料可多著呢!不过要注意的是, 不同版本的核心提供的函式库差异性是挺大的,所以kernel 2.4.xx版本的系统不要想将核心换成2.6.xx喔!很容易由于函式库的不同而导致很多原本可以执行的软体无法顺利运作呢!

二、ldconfig 与/etc/ld.so.conf

在了解了动态与静态函式库,也知道我们目前的Linux大多是将函式库做成动态函式库之后,再来要知道的就是,那有没有办法增加函式库的读取效能?我们知道记忆体的存取速度是硬碟的好几倍,所以,如果我们将常用到的动态函式库先载入记忆体当中(快取, cache),如此一来,当软体要取用动态函式库时,就不需要从头由硬碟里面读出啰!这样不就可以增进动态函式库的读取速度?没错,是这样的!这个时候就需要ldconfig与/etc/ld.so.conf的协助了。

如何将动态函式库载入快取记忆体当中呢?

  1. 首先,我们必须要在/etc/ld.so.conf里面写下『 想要读入快取记忆体当中的动态函式库所在的目录』,注意喔, 是目录而不是档案;
  2. 接下来则是利用ldconfig 这个执行档将/etc/ld.so.conf 的资料读入快取当中;
  3. 同时也将资料记录一份在/etc/ld.so.cache 这个档案当中呐!

事实上, ldconfig 还可以用来判断动态函式库的连结资讯呢!赶紧利用CentOS 来测试看看。假设妳想要将目前你系统下的mariadb 函式库加入到快取当中时,可以这样做:

[root@study ~]# ldconfig [-f conf] [ -C cache] 
[root@study ~]# ldconfig [-p] 
选项与参数:
-f conf :那个conf 指的是某个档案名称,也就是说,使用conf 作为libarary 
	  函式库的取得路径,而不以/etc/ld.so.conf 为预设值
-C cache:那个cache 指的是某个档案名称,也就是说,使用cache 作为快取暂存
	  的函式库资料,而不以/etc/ld.so.cache 为预设值
-p :列出目前有的所有函式库资料内容(在/etc/ld.so.cache 内的资料!)

范例一:假设我的Mariadb资料库函式库在/usr/lib64/mysql当中,如何读进cache ?
[root@study ~]# vim /etc/ld.so.conf.d/vbird.conf 
/usr/lib64/mysql    <==这一行新增的啦!

[root@study ~]# ldconfig   <==画面上不会显示任何的资讯,不要太紧张!正常的!

[root@study ~]# ldconfig -p
924 libs found in cache `/etc/ld.so.cache'
        p11-kit-trust.so (libc6,x86-64) => /lib64/p11-kit-trust.so
        libzapojit-0.0.so.0 (libc6,x86-64) => /lib64/libzapojit-0.0.so.0
....(底下省略).... 
#函式库名称=>该函式库实际路径

透过上面的动作,我们可以将Mariadb 的相关函式库给他读入快取当中,这样可以加快函式库读取的效率呢!在某些时候,你可能会自行加入某些Tarball 安装的动态函式库,而你想要让这些动态函式库的相关连结可以被读入到快取当中, 这个时候你可以将动态函式库所在的目录名称写入/etc/ld.so.conf.d/yourfile.conf 当中,然后执行ldconfig 就可以啦!

三、ldconfig详解

ldconfig是一个动态链接库管理命令,其目的为了让动态链接库为系统所共享。

3.1 ldconfig的主要用途:

  • 默认搜寻/lilb和/usr/lib,以及配置文件/etc/ld.so.conf内所列的目录下的库文件。
  • 搜索出可共享的动态链接库,库文件的格式为:lib***.so.**,进而创建出动态装入程序(ld.so)所需的连接和缓存文件。
  • 缓存文件默认为/etc/ld.so.cache,该文件保存已排好序的动态链接库名字列表。
  • ldconfig通常在系统启动时运行,而当用户安装了一个新的动态链接库时,就需要手工运行这个命令。

3.2 ldconfig命令参数说明:

-v或--verbose:用此选项时,ldconfig将显示正在扫描的目录及搜索到的动态链接库,还有它所创建的连接的名字.

-n :用此选项时,ldconfig仅扫描命令行指定的目录,不扫描默认目录(/lib,/usr/lib),也不扫描配置文件/etc/ld.so.conf所列的目录.

-N :此选项指示ldconfig不重建缓存文件(/etc/ld.so.cache).若未用-X选项,ldconfig照常更新文件的连接.

-X : 此选项指示ldconfig不更新文件的连接.若未用-N选项,则缓存文件正常更新.

-f CONF : 此选项指定动态链接库的配置文件为CONF,系统默认为/etc/ld.so.conf.

-C CACHE :此选项指定生成的缓存文件为CACHE,系统默认的是/etc/ld.so.cache,此文件存放已排好序的可共享的动态链接库的列表.

-r ROOT :此选项改变应用程序的根目录为ROOT(是调用chroot函数实现的).选择此项时,系统默认的配置文件/etc/ld.so.conf,实际对应的为ROOT/etc/ld.so.conf.如用-r/usr/zzz时,打开配置文件/etc/ld.so.conf时,实际打开的是/usr/zzz/etc/ld.so.conf文件.用此选项,可以大大增加动态链接库管理的灵活性.

-l :通常情况下,ldconfig搜索动态链接库时将自动建立动态链接库的连接.选择此项时,将进入专家模式,需要手工设置连接.一般用户不用此项.

-p或--print-cache :此选项指示ldconfig打印出当前缓存文件所保存的所有共享库的名字.

-c FORMAT 或--format=FORMAT :此选项用于指定缓存文件所使用的格式,共有三种:ld(老格式),new(新格式)和compat(兼容格式,此为默认格式).

-V : 此选项打印出ldconfig的版本信息,而后退出.

- 或 --help 或--usage : 这三个选项作用相同,都是让ldconfig打印出其帮助信息,而后退出.、

3.3 需要注意的地方:

1、往/lib和/usr/lib里面加东西,是不用修改/etc/ld.so.conf文件的,但是添加完后需要调用下ldconfig,不然添加的library会找不到。

2、如果添加的library不在/lib和/usr/lib里面的话,就一定要修改/etc/ld.so.conf文件,往该文件追加library所在的路径,然后也需要重新调用下ldconfig命令。比如在安装MySQL的时候,其库文件/usr/local/mysql/lib,就需要追加到/etc/ld.so.conf文件中。命令如下:

# echo "/usr/local/mysql/lib" >> /etc/ld.so.conf

# ldconfig -v | grep mysql

3、如果添加的library不在/lib或/usr/lib下,但是却没有权限操作写/etc/ld.so.conf文件的话,这时就需要往export里写一个全局变量LD_LIBRARY_PATH,就可以了。

四、程式的动态函式库解析: ldd

说了这么多,那么我如何判断某个可执行的binary 档案含有什么动态函式库呢?很简单,利用 ldd 就可以晓得了!例如我想要知道/usr/bin/passwd 这个程式含有的动态函式库有哪些,可以这样做:

[root@study ~]# ldd [-vdr] [filename] 
选项与参数:
-v :列出所有内容资讯;
-d :重新将资料有遗失的link 点秀出来!
-r :将ELF 有关的错误内容秀出来!

范例一:找出/usr/bin/passwd这个档案的函式库资料 
[root@study ~]# ldd /usr/bin/passwd 
....(前面省略).... 
        libpam.so.0 = > /lib64/libpam.so.0 (0x00007f5e683dd000)             <==PAM模组
        libpam_misc.so.0 => /lib64/libpam_misc.so.0 (0x00007f5e681d8000)
        libaudit.so.1 => /lib64/libaudit.so.1 (0x00007f5e67fb1000)         <==SELinux 
        libselinux.so.1 => /lib64/libselinux.so.1 (0x00007f5e67d8c000)     <==SELinux 
....(底下省略).... 
#我们前言的部分不是一直提到passwd有使用到pam的模组吗!怎么知道?
# 利用ldd 察看一下这个档案,看到libpam.so 了吧?这就是pam 提供的函式库

范例二:找出/lib64/libc.so.6这个函式的相关其他函式库!
[root@study ~]# ldd -v /lib64/libc.so.6
        /lib64/ld-linux-x86-64.so.2 (0x00007f7acc68f000)
        linux-vdso.so.1 => (0x00007fffa975b000)

        Version information:   <==使用-v选项,增加显示其他版本资讯!
        /lib64/libc.so.6:
                ld-linux-x86-64.so.2 (GLIBC_2.3) => /lib64/ld-linux-x86-64.so.2
                ld-linux-x86-64.so.2 (GLIBC_PRIVATE) => /lib64/ld-linux-x86-64.so.2

未来如果你常常升级安装RPM的软体时(下一章节会介绍),应该常常会发现那个『 相依属性』的问题吧!没错!我们可以先以ldd来视察『相依函式库』之间的相关性!以先取得了解!例如上面的例子中,我们检查了libc.so.6这个在/lib64当中的函式库,结果发现他其实还跟ld-linux-x86-64.so.2有关!所以我们就需要来了解一下,那个档案到底是什么软体的函式库呀?使用-v这个参数还可以得知该函式库来自于哪一个软体!像上面的资料中,就可以得到该libc.so.6其实可以支援GLIBC_2.3等的版本!

四、讨论

4.1 添加新的库的路径

vim /etc/ld.so.conf.d/smina.conf

/data/software/smina/library/boost-1.54/lib
/data/software/smina/library/eigen3/lib
/data/software/smina/library/openbabel-2.4.1/lib

然后使其生效 : ldconfig

查看是否生产boost的动态库

ldconfig -p |grep boost

能够看到 boost_iostreams

五、报错

5.1 No such file or directory

[root@g03 bin]# lmutil

报错:

/data/software/moe2018/bin/lmutil: /data/software/moe2018/bin-lnx64/lmutil: /lib64/ld-lsb-x86-64.so.3: bad ELF interpreter: No such file or directory
/data/software/moe2018/bin/lmutil: line 51: /data/software/moe2018/bin-lnx64/lmutil: Success

解决方法:

yum install glibc.i686

5.2 XZ_5.2 not found

版本从图,

xz: /mnt/nfs/data/software/anaconda2/bin/../lib/liblzma.so.5: version `XZ_5.2' not found (required by xz)

yum list |grep lzma
yum install xz-lzma-compat.x86_64

[sam@g02 flare]$  xz -V
xz: /mnt/nfs/data/software/anaconda2/bin/../lib/liblzma.so.5: version `XZ_5.2' not found (required by xz)
[sam@g02 flare]$ ll /mnt/nfs/data/software/anaconda2/lib |grep zma
-rwxr-xr-x  2 root root  293K May 17  2018 liblzma.a
-rwxr-xr-x  1 root root   989 Oct 29  2018 liblzma.la
lrwxrwxrwx  1 root root    16 Nov 25  2019 liblzma.so -> liblzma.so.5.2.2
lrwxrwxrwx  1 root root    16 Nov 25  2019 liblzma.so.5 -> liblzma.so.5.2.2
-rwxr-xr-x  1 root root  154K Apr 18  2019 liblzma.so.5.2.2
-rwxr-xr-x  2 root root  170K May 17  2018 liblzma.so.5.2.4

解决办法:

mv liblzma.so liblzma.so.bak
mv liblzma.so.5 liblzma.so.5.bak


ln -s liblzma.so.5.2.4 liblzma.so       
ln -s liblzma.so.5.2.4 liblzma.so.5   

[root@g02 lib]# xz -v
xz: Compressed data cannot be written to a terminal
xz: Try `xz --help' for more information.

5.3 /usr/bin/ld: cannot find -lxxx 的解决办法

在软件编译过程中,经常会碰到类似这样的编译错误:/usr/bin/ld: cannot find -lhdf5

这表示找不到库文件 libhdf5.so,若是其它库文件,则是 cannot find -lxxx 了,其中 xxx 是库文件的名字。 解决方法有:

5.3.1. 安装此库文件和相关软件

一般库文件属于某个软件,google搜索该软件并安装,或者使用 yum 安装。

5.3.2. 将库文件所在路径添加到gcc的搜索路径

使用以下命令查询gcc能否搜寻到指定的库文件:

$ gcc -lhdf5 --verbose

查询库文件 libhdf5.so 是否能在搜索路径中找到。 若安装了软件,找到了库文件的路径。但是依然会提示上述错误。则表示gcc的搜索路径不包含该库文件所在的路径。将库文件所在的路径加入到搜寻路径中的方法为:

5.3.2.1 使用 /etc/ld.so.conf 配置文件

将库文件所在的路径加入到 /etc/ld.so.conf 尾部,并使之生效:

$ sudo echo '/opt/biosoft/hdf5-1.8.15-patch1/lib/' >> /etc/ld.so.conf
libhdf5.so 在路径 /opt/biosoft/hdf5-1.8.15-patch1/lib/ 下,将该路径加添加到配置文件中
$ sudo ldconfig

运行该命令,重新载入 /ext/ld.so.conf 中的路径,使修改生效。

5.3.2.2 修改环境变量
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/biosoft/hdf5-1.8.15-patch1/lib/
修改环境变量 LD_LIBRARY_PATH,加入库文件所在路径。使用 export 命令使修改生效。

$ echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/biosoft/hdf5-1.8.15-patch1/lib/' >> ~/.bashrc
$ source ~/.bashrc

将上述 export 命令加入到配置文件 ~/.bashrc,使之永久生效。

$ export LIBRARY_PATH=/opt/biosoft/hdf5-1.8.15-patch1/lib/:$LIBRARY_PATH

若修改变量 LD_LIBRARY_PATH 不奏效,则修改变量 LIBRARY_PATH 。

5.4 library not found for -lxxx

静态库无法链接报错:

library not found for -lxxx

动态库无法装载报错:

dyld: library not loaded …/libxxx.dylib

解决办法:

方法一:直接添加库

通常解决办法是:库存在,直接添加路径。

也就是通常遇到这个问题的时候,库是已经编译安装好了的,但是 IED 不能找到。这样的话就直接添加库的路径就好了。

1.第一步,自己找到这个库。

库一般放在系统默认处或者安装到特定地方。

Linux 系统默认库放在:

 /lib
 /usr/lib
 /usr/local/lib
 ...

安装到特定地方,如我 Mac 的用 Homebrew 安装到:

/usr/local/Cellar/ice/3.7.0/lib

2.第二步:添加路径

添加库一般以下三种方法任选其一:

系统环境变量添加

系统级:修改/etc/profile或者/etc/bashrc

用户级:修改~/.bashrc或者~/.bash_profile

#添加库的bin文件夹路径
export PATH =$PATH:$HOME/bin

#添加到gcc头文件
export C_INCLUDE_PATH=$C_INCLUDE_PATH:/MyLib

#添加到g++头文件路径
export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/MyLib

#添加到动态库
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/MyLib

#添加到静态库
export LIBRARY_PATH=$LIBRARY_PATH:/MyLib

3.IED 编译环境添加

因 IDE 不同而不同,如 Qt 在项目-构建设置-构建环境处添加

具体请参考:QT 无法链接动态库 dyld library not loaded … libhdf5.100.dylib

代码添加

 # 如 Qt pro 文件添加
 LIBS += -L/usr/local/Cellar/ice/3.7.0/lib -lIceUtil

这样,IDE就能找到库啦!

方法二:手动编译添加库

若是你的库不存在,也就是说安装的时候没有编译生成这个库文件或者安装后莫名的不见了,这样只能重新安装或者手动编译添加库。以下讲手动编译添加库。

思路是:找库,如果找不到,手动编译生成库文件,拷贝到库文件目录,用上面添加路径的方法添加路径让IDE找到。

1.第一步:找库

对的,还是要找找的,不然怎么知道没有呢![捂脸]

可以在一些常放库的文件夹下找,尽量靠近根目录,如:

sudo find /usr -name "libIceUtil*"

扩展:

如果找到名为libIceUtil.3.7.0.a的库,但是找不到libIceUtil.a的库,可以试着拷贝libIceUtil.3.7.0.a库成名为libIceUtil.a的库

cp ./libIceUtil.3.7.0.a ./libIceUtil.a

这个方法适用于:

同版本下,缺失没版本号的库文件 不同版本下,库文件没因为版本的改变而发生改变

2.第二步,手动编译

#因为IceUtil是Ice的库,所以克隆下Ice来

git clone https://github.com/zeroc-ice/ice.git

#因为我需要的是C++版
cd ice/cpp

#直接编译
make

编译好后就能找到这个库了:cpp/lib/libIceUtil.a

其他库请参考官方的编译安装教程

附:Building Ice for C++ on macOS

3.第三步,拷贝库到安装后的文件夹

到ice/cpp/lib目录下:

cp ./libIceUtil.a /usr/local/Cellar/ice/3.7.0/lib

4.第四步:添加路径

上面已经说了三种方法,这里直接代码添加:

# 如 Qt pro 文件添加
LIBS += -L/usr/local/Cellar/ice/3.7.0/lib -lIceUtil

5.5 so文件放进了/usr/local/lib,但是今天在写程序的时候Makefile遇到了问题:仍然报“error while loading shared libraries”错误

原因:

默认情况下,编译器只会使用/lib和/usr/lib这两个目录下的库文件,通常通过源码包进行安装时,如果不指定–prefix,会将库安装在/usr/local/lib目录下;当运行程序需要链接动态库时,提示找不到相关的.so库,会报错。也就是说,/usr/local/lib目录不在系统默认的库搜索目录中,需要将目录加进去。

解决办法:

首先打开/etc/ld.so.conf文件,执行

sudo vi /etc/ld.so.conf

加入动态库文件所在的目录:在"include ld.so.conf.d/*.conf"下方增加"/usr/local/lib"。

include ld.so.conf.d/*.conf
/usr/local/lib

保存后,在命令行终端执行:

sudo /sbin/ldconfig -v

其作用是将文件/etc/ld.so.conf列出的路径下的库文件缓存到/etc/ld.so.cache以供使用,因此当安装完一些库文件,或者修改/etc/ld.so.conf增加了库的新搜索路径,需要运行一下ldconfig,使所有的库文件都被缓存到文件/etc/ld.so.cache中,如果没做,可能会找不到刚安装的库

参考资料

药企,独角兽,苏州。团队长期招人,感兴趣的都可以发邮件聊聊:tiehan@sina.cn
个人公众号,比较懒,很少更新,可以在上面提问题,如果回复不及时,可发邮件给我: tiehan@sina.cn