Linux 上模块化库的动态链接是如何工作的

了解如何使用动态库将多个 C 对象文件组合成单个可执行文件。
3 位读者喜欢这篇文章。
Links

Paul Lewin。由 Opensource.com 修改。CC BY-SA 2.0

当您使用 C 编程语言编写应用程序时,您的代码通常有多个源文件。

最终,这些文件必须编译成一个可执行文件。您可以通过创建静态动态库(后者也称为共享库)来做到这一点。这两种类型的库在创建和链接方式上有所不同。两者都有优点和缺点,具体取决于您的用例。

动态链接是最常见的方法,尤其是在 Linux 系统上。动态链接使库保持模块化,因此一个库可以在任意数量的应用程序之间共享。模块化还允许共享库独立于依赖它的应用程序进行更新。

在本文中,我将演示动态链接的工作原理。在以后的文章中,我将演示静态链接。

链接器

链接器是一个命令,它将程序的几个部分组合在一起,并重新组织它们的内存分配。

链接器的功能包括

  • 集成程序的所有部分
  • 确定新的内存组织,以便所有部分组合在一起
  • 恢复地址,以便程序可以在新的内存组织下运行
  • 解析符号引用

由于所有这些链接器功能,会创建一个名为可执行文件的可运行程序。在您可以创建动态链接的可执行文件之前,您需要一些要链接的库和一个要编译的应用程序。准备好您最喜欢的文本编辑器,并跟随我一起操作。

创建对象文件

首先,使用以下函数签名创建头文件 mymath.h

int add(int a, int b);
int sub(int a, int b);
int mult(int a, int b);
int divi(int a, int b);

使用以下函数定义创建 add.csub.cmult.cdivi.c。我将所有代码放在一个代码块中,因此请将其分成四个文件,如注释所示

// add.c
int add(int a, int b){
return (a+b);
}

//sub.c
int sub(int a, int b){
return (a-b);
}

//mult.c
int mult(int a, int b){
return (a*b);
}

//divi.c
int divi(int a, int b){
return (a/b);
}

现在使用 GCC 生成对象文件 add.osub.omult.odivi.o

$ gcc -c add.c sub.c mult.c divi.c

-c 选项跳过链接步骤,仅创建对象文件。

创建共享对象文件

动态库在最终可执行文件的执行期间被链接。只有动态库的名称被放置在最终的可执行文件中。实际的链接发生在运行时,此时可执行文件和库都被放置在主内存中。

除了可共享之外,动态库的另一个优点是它可以减小最终可执行文件的大小。应用程序在使用库时,只包含库的名称,而不是库的冗余副本,最终的可执行文件就会被创建。

您可以从现有的示例代码创建动态库

$ gcc -Wall -fPIC -c add.c sub.c mult.c divi.c

选项 -fPIC 告诉 GCC 生成位置无关代码 (PIC)。 -Wall 选项不是必需的,并且与代码的编译方式无关。 尽管如此,它仍然是一个有价值的选项,因为它启用了编译器警告,这在排除故障时非常有用。

使用 GCC 创建共享库 libmymath.so

$ gcc -shared -o libmymath.so \
add.o sub.o mult.o divi.o

您现在已经创建了一个简单的示例数学库 libmymath.so,您可以在 C 代码中使用它。 当然,有很多非常复杂的 C 库,这是它们的开发人员用来生成您或我安装以在 C 代码中使用的最终产品的过程。

接下来,您可以在一些自定义代码中使用您的新数学库,然后链接它。

创建动态链接的可执行文件

假设您编写了一个用于数学的命令。 创建一个名为 mathDemo.c 的文件并将此代码粘贴到其中

#include <mymath.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
  int x, y;
  printf("Enter two numbers\n");
  scanf("%d%d",&x,&y);
 
  printf("\n%d + %d = %d", x, y, add(x, y));
  printf("\n%d - %d = %d", x, y, sub(x, y));
  printf("\n%d * %d = %d", x, y, mult(x, y));

  if(y==0){
    printf("\nDenominator is zero so can't perform division\n");
      exit(0);
  }else{
      printf("\n%d / %d = %d\n", x, y, divi(x, y));
      return 0;
  }
}

请注意,第一行是一个 include 语句,按名称引用您自己的 libmymath 库。 要使用共享库,您必须安装它。 如果您不安装您使用的库,那么当您的可执行文件运行时并搜索包含的库时,它将无法找到它。 如果您需要在不将库安装到已知目录的情况下编译代码,则有覆盖默认设置的方法。 但是,对于一般用途,期望库存在于已知位置,所以这就是我在这里演示的内容。

将文件 libmymath.so 复制到标准系统目录,例如 /usr/lib64,然后运行 ldconfigldconfig 命令会创建所需的链接和缓存,指向在标准库目录中找到的最新共享库。

$ sudo cp libmymath.so /usr/lib64/
$ sudo ldconfig

编译应用程序

从您的应用程序源代码 (mathDemo.c) 创建一个名为 mathDemo.o 的对象文件

$ gcc -I . -c mathDemo.c

-I 选项告诉 GCC 在它后面列出的目录(在本例中为 mymath.h)中搜索头文件。 在本例中,您指定当前目录,用一个点 (.) 表示。 创建一个可执行文件,使用 -l 选项按名称引用您的共享数学库

$ gcc -o mathDynamic mathDemo.o -lmymath

GCC 找到 libmymath.so 是因为它存在于默认系统库目录中。 使用 ldd 验证使用的共享库

$ ldd mathDemo
    linux-vdso.so.1 (0x00007fffe6a30000)
    libmymath.so => /usr/lib64/libmymath.so (0x00007fe4d4d33000)
    libc.so.6 => /lib64/libc.so.6 (0x00007fe4d4b29000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fe4d4d4e000)

查看 mathDemo 可执行文件的大小

$ du ./mathDynamic
24   ./mathDynamic

当然,这是一个小型应用程序,它占用的磁盘空间量也反映了这一点。 为了比较,相同代码的静态链接版本(您将在我的下一篇文章中看到)是 932K!

$ ./mathDynamic
Enter two numbers
25
5

25 + 5 = 30
25 - 5 = 20
25 * 5 = 125
25 / 5 = 5

您可以使用 file 命令验证它是否是动态链接的

$ file ./mathDynamic
./mathDynamic: ELF 64-bit LSB executable, x86-64,
dynamically linked,
interpreter /lib64/ld-linux-x86-64.so.2,
with debug_info, not stripped

成功!

动态链接

共享库会生成一个轻量级的可执行文件,因为链接发生在运行时。 因为它在运行时解析引用,所以执行需要更多时间。 但是,由于日常 Linux 系统上的绝大多数命令都是动态链接的,并且在现代硬件上,节省的时间可以忽略不计。 其固有的模块化是开发人员和用户的强大功能。

在本文中,我描述了如何创建动态库并将它们链接到最终的可执行文件中。 我将在下一篇文章中使用相同的源代码来创建静态链接的可执行文件。

接下来阅读什么

编译代码时动态链接库

编译软件可以让您灵活地运行您的系统。 LD_LIBRARY_PATH 变量以及 -L 和 -l GCC 选项是该组件的一部分…

标签
User profile image.
Jayashree Huttanagoudar 是 Red Hat India Pvt ltd 的高级软件工程师。 她在 Middleware OpenJDK 团队工作。 她总是渴望学习新事物,这增加了她的工作。

评论已关闭。

Creative Commons License本作品已根据 Creative Commons Attribution-Share Alike 4.0 International License 获得许可。
© . All rights reserved.