服务器之家:专注于服务器技术及软件下载分享
分类导航

Linux|Centos|Ubuntu|系统进程|Fedora|注册表|Bios|Solaris|Windows7|Windows10|Windows11|windows server|

服务器之家 - 服务器系统 - Linux - 怎样在 Linux 中使用动态和静态库

怎样在 Linux 中使用动态和静态库

2021-04-22 23:42Linux中国Stephan Avenwedde Linux

了解 Linux 如何使用库,包括静态库和动态库的差别,有助于你解决依赖问题。

怎样在 Linux 中使用动态和静态库

了解 Linux 如何使用库,包括静态库动态库的差别,有助于你解决依赖问题。

Linux 从某种意义上来说就是一堆相互依赖的静态和动态库。对于 Linux 系统新手来说,库的整个处理过程简直是个迷。但对有经验的人来说,被构建进操作系统的大量共享代码对于编写新应用来说却是个优点。

为了让你熟悉这个话题,我准备了一个小巧的 应用例子 来展示在普通的 Linux 发行版(在其他操作系统上未验证)上是经常是如何处理库的。为了用这个例子来跟上这个需要动手的教程,请打开命令行输入:

  1. $ git clone https://github.com/hANSIc99/library_sample
  2. $ cd library_sample/
  3. $ make
  4. cc -c main.c -Wall -Werror
  5. cc -c libmy_static_a.c -o libmy_static_a.o -Wall -Werror
  6. cc -c libmy_static_b.c -o libmy_static_b.o -Wall -Werror
  7. ar -rsv libmy_static.a libmy_static_a.o libmy_static_b.o
  8. ar: creating libmy_static.a
  9. a - libmy_static_a.o
  10. a - libmy_static_b.o
  11. cc -c -fPIC libmy_shared.c -o libmy_shared.o
  12. cc -shared -o libmy_shared.so libmy_shared.o
  13. $ make clean
  14. rm *.o

当执行完这些命令,这些文件应当被添加进目录下(执行 ls 来查看):

  1. my_app
  2. libmy_static.a
  3. libmy_shared.so

关于静态链接

 

当你的应用链接了一个静态库,这个库的代码就变成了可执行文件的一部分。这个动作只在链接过程中执行一次,这些静态库通常以 .a 扩展符结尾。

静态库是多个目标object文件的归档archive(ar)。这些目标文件通常是 ELF 格式的。ELF 是 可执行可链接格式Executable and Linkable Format 的简写,它与多个操作系统兼容。

file 命令的输出可以告诉你静态库 libmy_static.a 是 ar 格式的归档文件类型。

  1. $ file libmy_static.a
  2. libmy_static.a: current ar archive

使用 ar -t,你可以看到归档文件的内部。它展示了两个目标文件:

  1. $ ar -t libmy_static.a
  2. libmy_static_a.o
  3. libmy_static_b.o

你可以用 ax -x <archive-file> 命令来提取归档文件的文件。被提出的都是 ELF 格式的目标文件:

  1. $ ar -x libmy_static.a
  2. $ file libmy_static_a.o
  3. libmy_static_a.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

关于动态链接

 

动态链接指的是使用共享库。共享库通常以 .so 的扩展名结尾(“共享对象shared object” 的简写)。

共享库是 Linux 系统中依赖管理的最常用方法。这些共享库在应用启动前被载入内存,当多个应用都需要同一个库时,这个库在系统中只会被加载一次。这个特性减少了应用的内存占用。

另外一个值得注意的地方是,当一个共享库的 bug 被修复后,所有引用了这个库的应用都会受益。但这也意味着,如果一个 bug 还没被发现,那所有相关的应用都会遭受这个 bug 影响(如果这个应用使用了受影响的部分)。

当一个应用需要某个特定版本的库,但是链接器linker只知道某个不兼容版本的位置,对于初学者来说这个问题非常棘手。在这个场景下,你必须帮助链接器找到正确版本的路径。

尽管这不是一个每天都会遇到的问题,但是理解动态链接的原理总是有助于你修复类似的问题。

幸运的是,动态链接的机制其实非常简洁明了。

为了检查一个应用在启动时需要哪些库,你可以使用 ldd 命令,它会打印出给定文件所需的动态库:

  1. $ ldd my_app
  2.         linux-vdso.so.1 (0x00007ffd1299c000)
  3.         libmy_shared.so => not found
  4.         libc.so.6 => /lib64/libc.so.6 (0x00007f56b869b000)
  5.         /lib64/ld-linux-x86-64.so.2 (0x00007f56b8881000)

可以注意到 libmy_shared.so 库是代码仓库的一部分,但是没有被找到。这是因为负责在应用启动之前将所有依赖加载进内存的动态链接器没有在它搜索的标准路径下找到这个库。

对新手来说,与常用库(例如 bizp2)版本不兼容相关的问题往往十分令人困惑。一种方法是把该仓库的路径加入到环境变量 LD_LIBRARY_PATH 中来告诉链接器去哪里找到正确的版本。在本例中,正确的版本就在这个目录下,所以你可以导出它至环境变量:

  1. $ LD_LIBRARY_PATH=$(pwd):$LD_LIBRARY_PATH
  2. $ export LD_LIBRARY_PATH

现在动态链接器知道去哪找库了,应用也可以执行了。你可以再次执行 ldd 去调用动态链接器,它会检查应用的依赖然后加载进内存。内存地址会在对象路径后展示:

  1. $ ldd my_app
  2.         linux-vdso.so.1 (0x00007ffd385f7000)
  3.         libmy_shared.so => /home/stephan/library_sample/libmy_shared.so (0x00007f3fad401000)
  4.         libc.so.6 => /lib64/libc.so.6 (0x00007f3fad21d000)
  5.         /lib64/ld-linux-x86-64.so.2 (0x00007f3fad408000)

想知道哪个链接器被调用了,你可以用 file 命令:

  1. $ file my_app
  2. my_app: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=26c677b771122b4c99f0fd9ee001e6c743550fa6, for GNU/Linux 3.2.0, not stripped

链接器 /lib64/ld-linux-x86–64.so.2 是一个指向 ld-2.30.so 的软链接,它也是我的 Linux 发行版的默认链接器:

  1. $ file /lib64/ld-linux-x86-64.so.2
  2. /lib64/ld-linux-x86-64.so.2: symbolic link to ld-2.31.so

回头看看 ldd 命令的输出,你还可以看到(在 libmy_shared.so 边上)每个依赖都以一个数字结尾(例如 /lib64/libc.so.6)。共享对象的常见命名格式为:

  1. libXYZ.so.<MAJOR>.<MINOR>

在我的系统中,libc.so.6 也是指向同一目录下的共享对象 libc-2.31.so 的软链接。

  1. $ file /lib64/libc.so.6
  2. /lib64/libc.so.6: symbolic link to libc-2.31.so

如果你正在面对一个应用因为加载库的版本不对导致无法启动的问题,有很大可能你可以通过检查整理这些软链接或者确定正确的搜索路径(查看下方“动态加载器:ld.so”一节)来解决这个问题。

更为详细的信息请查看 ldd 手册页。

动态加载

动态加载的意思是一个库(例如一个 .so 文件)在程序的运行时被加载。这是使用某种特定的编程方法实现的。

当一个应用使用可以在运行时改变的插件时,就会使用动态加载。

查看 dlopen 手册页 获取更多信息。

动态加载器:ld.so

在 Linux 系统中,你几乎总是正在跟共享库打交道,所以必须有个机制来检测一个应用的依赖并将其加载进内存中。

ld.so 按以下顺序在这些地方寻找共享对象:

  1. 应用的绝对路径或相对路径下(用 GCC 编译器的 -rpath 选项硬编码的)
  2. 环境变量 LD_LIBRARY_PATH
  3. /etc/ld.so.cache 文件

需要记住的是,将一个库加到系统库归档 /usr/lib64 中需要管理员权限。你可以手动拷贝 libmy_shared.so 至库归档中来让应用可以运行,而避免设置 LD_LIBRARY_PATH

  1. unset LD_LIBRARY_PATH
  2. sudo cp libmy_shared.so /usr/lib64/

当你运行 ldd 时,你现在可以看到归档库的路径被展示出来:

  1. $ ldd my_app
  2.         linux-vdso.so.1 (0x00007ffe82fab000)
  3.         libmy_shared.so => /lib64/libmy_shared.so (0x00007f0a963e0000)
  4.         libc.so.6 => /lib64/libc.so.6 (0x00007f0a96216000)
  5.         /lib64/ld-linux-x86-64.so.2 (0x00007f0a96401000)

在编译时定制共享库

 

如果你想你的应用使用你的共享库,你可以在编译时指定一个绝对或相对路径。

编辑 makefile(第 10 行)然后通过 make -B 来重新编译程序。然后 ldd 输出显示 libmy_shared.so 和它的绝对路径一起被列出来了。

把这个:

  1. CFLAGS =-Wall -Werror -Wl,-rpath,$(shell pwd)

改成这个(记得修改用户名):

  1. CFLAGS =/home/stephan/library_sample/libmy_shared.so

然后重新编译:

  1. $ make

确认下它正在使用你设定的绝对路径,你可以在输出的第二行看到:

  1. $ ldd my_app
  2.     linux-vdso.so.1 (0x00007ffe143ed000)
  3.         libmy_shared.so => /lib64/libmy_shared.so (0x00007fe50926d000)
  4.         /home/stephan/library_sample/libmy_shared.so (0x00007fe509268000)
  5.         libc.so.6 => /lib64/libc.so.6 (0x00007fe50909e000)
  6.         /lib64/ld-linux-x86-64.so.2 (0x00007fe50928e000)

这是个不错的例子,但是如果你在编写给其他人用的库,它是怎样工作的呢?新库的路径可以通过写入 /etc/ld.so.conf 或是在 /etc/ld.so.conf.d/ 目录下创建一个包含路径的 <library-name>.conf 文件来注册至系统。之后,你必须执行 ldconfig 命令来覆写 ld.so.cache 文件。这一步有时候在你装了携带特殊的共享库的程序来说是不可省略的。

查看 ld.so 的手册页 获取更多详细信息。

怎样处理多种架构

 

通常来说,32 位和 64 位版本的应用有不同的库。下面列表展示了不同 Linux 发行版库的标准路径:

红帽家族

  • 32 位:/usr/lib
  • 64 位:/usr/lib64

Debian 家族

  • 32 位:/usr/lib/i386-linux-gnu
  • 64 位:/usr/lib/x86_64-linux-gnu

Arch Linux 家族

  • 32 位:/usr/lib32
  • 64 位:/usr/lib64

FreeBSD(技术上来说不算 Linux 发行版)

  • 32 位:/usr/lib32
  • 64 位:/usr/lib

知道去哪找这些关键库可以让库链接失效的问题成为历史。

虽然刚开始会有点困惑,但是理解 Linux 库的依赖管理是一种对操作系统掌控感的表现。在其他应用程序中运行这些步骤,以熟悉常见的库,然后继续学习怎样解决任何你可能遇到的库的挑战。

原文地址:https://linux.cn/article-13318-1.html

延伸 · 阅读

精彩推荐
  • LinuxLinux上设置用户通过SFTP访问目录的权限的方法

    Linux上设置用户通过SFTP访问目录的权限的方法

    这篇文章主要介绍了Linux上设置用户通过SFTP访问目录的权限的方法,SFTP可以理解为使用SSH协议进行FTP传输的协议,因而同时要对OpenSSH进行相关设置,需要的朋...

    OSChina10022019-06-19
  • Linux详解Linux系统下PXE服务器的部署过程

    详解Linux系统下PXE服务器的部署过程

    这篇文章主要介绍了Linux系统下PXE服务器的部署过程,包括对PXE的API架构作了一个基本的简介,需要的朋友可以参考下...

    运维之道9812019-07-04
  • LinuxLinux lnmp下无法使用mail发邮件的两种解决方法

    Linux lnmp下无法使用mail发邮件的两种解决方法

    在配置了lnmp环境后,出现了mail函数不能发送邮件的问题,其实有两种方法,一是使用sendmail组件,而是使用postfix。下面为大家一一介绍下 ...

    Linux之家4042019-09-17
  • Linux确保Linux系统安全的前提条件 漏洞防护

    确保Linux系统安全的前提条件 漏洞防护

    Linux 作为开放式的操作系统受到很多程序员的喜爱,很多高级程序员都喜欢编写Linux操作系统的相关软件。这使得Linux操作系统有着丰富的软件支持,还有无...

    Linux之家2642020-04-11
  • Linuxlinux中rmdir命令使用详解(删除空目录)

    linux中rmdir命令使用详解(删除空目录)

    今天学习一下linux中命令: rmdir命令。rmdir是常用的命令,该命令的功能是删除空目录,一个目录被删除之前必须是空的 ...

    linux命令大全5372019-11-19
  • LinuxLinux中环境变量配置的步骤详解

    Linux中环境变量配置的步骤详解

    Linux中环境变量包括系统级和用户级,系统级的环境变量是每个登录到系统的用户都要读取的系统变量,而用户级的环境变量则是该用户使用系统时加载的...

    Myths7882022-02-10
  • Linux手把手教您在 Linux 上使用 GPG 加解密文件

    手把手教您在 Linux 上使用 GPG 加解密文件

    在本教程中,我将告诉你如何用 GPG 加密和解密文件。这是一个简单的教程,你可以在你的 Linux 系统上尝试所有的练习。这将帮助你练习 GPG 命令,并在你...

    Linux中国6962021-12-15
  • LinuxLinux常用的日志文件和常用命令

    Linux常用的日志文件和常用命令

    成功地管理任何系统的关键之一,是要知道系统中正在发生什么事。 Linux 中提供了异常日志,并且日志的细节是可配置的。Linux 日志都以明文形式存储,所...

    Linux教程网2632020-04-18