关于共享库(Shared Library)的一些总结

共享库,即程序启动时动态加载的库文件。合理地使用共享库,可以有效地实现代码的重用。

编程需求

假设我有一个库文件 vmath,用来实现简单的四则运算:

/* vmath.h */
#ifndef VMATH_H
#define VMATH_H

extern int vm_add(int, int);
extern int vm_sub(int, int);
extern int vm_mul(int, int);
extern int vm_div(int, int);

#endif

各函数的具体实现如下:

/* vmath.c */
#include "vmath.h"

int vm_add(int op1, int op2) {
  return op1 + op2;
}

int vm_sub(int op1, int op2) {
  return op1 - op2;
}

int vm_mul(int op1, int op2) {
  return op1 * op2;
}

int vm_div(int op1, int op2) {
  int res;
  if (op2 != 0) {
    res = op1 / op2;
  }
  return res;
}

都是很简单的代码,就不多解释了。现在我有一个程序,想借用这个 vmath 库来实现简单的四则运算:

/* vm_test.c */
#include <stdio.h>
#include "vmath.h"

int main(int argc, char *argv[]) {
  int op1, op2;

  if (argc != 3) {
    fprintf(stderr, "Usage: %s <op1> <op2>\n", argv[0]);
    return -1;
  }

  sscanf(argv[1], "%d", &op1);
  sscanf(argv[2], "%d", &op2);

  printf("Operand #1: %d\n", op1);
  printf("Operand #2: %d\n", op2);

  printf("Addition: %d\n", vm_add(op1, op2));
  printf("Subtraction: %d\n", vm_sub(op1, op2));
  printf("Multiplication: %d\n", vm_mul(op1, op2));
  printf("Division: %d\n", vm_div(op1, op2));

  printf("Fin\n");
  return 0;
}

我们看到程序头部嵌入了 vmath 的头文件 vmath.h。程序读取 main 函数的两个参数:argv[1] 和 argv[2],将他们转换为 int,并保存进入 op1 和 op2 内,接着通过调用 vmath 的函数,依次计算他们的和、差、积和商。

共享库的生成

那我们希望 vmath 是以共享库的形式存在,则编译步骤较之静态库,也会略有不同,首先对 vmath.c 进行编译:

gcc -c -fPIC vmath.c -o vmath.o

编译器毫无悬念地生成了 vmath.o 对象文件,接下去我们需要将 .o 文件转成 .so 文件,s 即 shared:

gcc -shared -Wl,-soname,libvmath.so.1 vmath.o -o libvmath.so.1.0.0

当前目录下瞬间多出一个文件 libvmath.so.1.0.0,即我们的共享库文件,搞定啦!么么哒!这是不可能的……

要看懂上面这条命令,我们需要首先弄明白共享库的三个名字:

  • 共享库名(Soname):顾名思义,就是库的名称,标准格式为 lib + <库名> + .so + .<主版本号>。以我们的共享库 vmath 为例,那它的 soname 便是 libvmath.so.1。在系统中,soname 通常是以链接(Symbolic link)的形式存在,指向该共享库的真实名;

  • 真实名(Real name):即真正包含代码的文件名,标准格式为 <Soname> + .<次版本号> + .<发行号>。共享库 vmath 的真实名则为 libvmath.so.1.0.0;

  • 连接名(Linker name):即编译器在程序连接阶段,请求共享库时使用的名称,通常为不带任何版本号的 soname,vmath 的连接名则为 libvmath.so。连接名也是以链接的形式存在,指向该共享库的 soname。

现在我们回过头来看之前的那条编译器指令,其作用是将 vmath.o 生成一个 soname 为 libvmath.so.1 的共享库 libvmath.so.1.0.0。

现在我们已经获得我们的共享库 libvmath.so.1.0.0,接下去就是要为其添加 Soname 和 linker name:

ln -sf libvmath.so.1.0.0 libvmath.so.1
ln -sf libvmath.so.1 libvmath.so

前者生成一个指向 libvmath.so.1.0.0 的链接 libvmath.so.1,即共享库的 soname,后者生成一个指向 libvmath.so.1 的链接 libvmath.so,即共享库的 linker name。

万事俱备,接下去就剩下对测试程序 vm_test.c 的编译了:

gcc -c vm_test.c -o vm_test.o
gcc -L. -lvmath vm_test.o -o vm_test

-L 指定共享库所在的路径(path),此处为当前目录。-l 指定所需的共享库,编译器读取到 -lvmath,便会自动在指定的路径内寻找 libvmath.so。就这么妥妥地编译好了,接下去试运行一下:

./vm_test 12 3
./vm_test: error while loading shared libraries: libvmath.so.1: cannot open shared object file: No such file or directory

你妹!显然 vm_test 在启动之际,未能找到共享库。之前我们只是在 gcc 连接阶段指定共享库的所在和名称,但是最终生成的程序本身,并不知道共享库的相关信息。为了程序在启动时,能顺利找到共享库,我们可以采取一系列办法。

共享库的调用

LD_LIBRARY_PATH

第一种方法,我们可以通过环境变量 LD_LIBRARY_PATH 来指定共享库的所在位置:

export LD_LIBRARY_PATH=/home/vesontio/devel/lib:$LD_LIBRARY_PATH
./vm_test 12 3
Operand #1: 12
Operand #2: 3
Addition: 15
Subtraction: 9
Multiplication: 36
Division: 4
Fin

我这里用 export 将共享库所在的路径保存进环境变量 LD_LIBRARY_PATH,这里我们假设共享库文件 libvmath.so.1.0.0 和它的 soname 都保存在 /home/vesontio/devel/lib 下面。然后尝试运行 vm_test,妥妥的。

rpath

第二种方法是使用 rpath,即 runtime search path,是被硬生生写入可执行文件的路径,帮助程序在运行时寻找所需的库文件。rpath 需要在编译生成可执行文件时被指定,我们重新生成 vm_test:

gcc -L. -lvmath -Wl,-rpath=/home/vesontio/devel/lib vm_test.o -o vm_test
unset LD_LIBRARY_PATH
./vm_test 12 3
Operand #1: 12
Operand #2: 3
Addition: 15
Subtraction: 9
Multiplication: 36
Division: 4
Fin

我们重新生成了 vm_test,命令参数唯一的区别就是用 -rpath 指定了共享库所在的路径。在试运行前,我们故意去掉环境变量 LD_LIBRARY_PATH,但是程序还是依旧华丽丽地运行了,因为共享库的路径已经被写死在可执行文件里了。所以 vm_test 不再需要借助 LD_LIBRARY_PATH 来寻找 libvmath.so.1 了。

ldconfig

ldconfig 是 Linux 系统下共享库的管理工具,可以帮助系统内的应用程序搜索所需的共享库。ldconfig 默认会搜索标准的共享库路径,包括 /lib 和 /usr/lib,但是如果你的共享库被放在了其他地方,那就需要手动修改 ldconfig 的配置文件,即 /etc/ld.so.conf:

su
vim /etc/ld.so.conf

修改 ldconfig 的配置文件,你需要管理员 root 权限,用 su 登录之后,将我们的共享库的路径写入 ld.so.conf 内,然后刷新一下 ldconfig 的缓存:

ldconfig

现在再去运行一下 vm_test:

./vm_test 12 3
Operand #1: 12
Operand #2: 3
Addition: 15
Subtraction: 9
Multiplication: 36
Division: 4
Fin

又妥了!但是记住:ldconfig 只会帮助程序在运行时寻找共享库,但是在 gcc 的连接阶段,还是得手动用 -L 指定共享库的路径,并且用 -l 指定所需共享库。

以上三种方法都可以实现共享库的调用,至于哪种方法更合适,那就看开发人员自己的需求,根据实际情况来决定。


你可能感兴趣的:(关于共享库(Shared Library)的一些总结)