前言:

动态链接库是程序运行时加载的库,当动态链接库正确部署之后,运行的多个程序可以使用同一个加载到内存中的动态库,因此在 Linux 中动态链接库也可称之为共享库。

动态链接库是目标文件的集合,目标文件在动态链接库中的组织方式是按照特殊方式形成的。库中函数和变量的地址使用的是相对地址(静态库中使用的是绝对地址),其真实地址是在应用程序加载动态库时形成的。

在 Linux 中动态库以 lib 作为前缀,以.so 作为后缀,中间是库的名字自己指定即可,即: libxxx.so
在 Windows 中动态库一般以 lib 作为前缀,以 dll 作为后缀,中间是库的名字需要自己指定,即: libxxx.dll

生成动态链接库:

生成动态链接库是直接使用 gcc 命令并且需要添加 -fPIC(-fpic) 以及 -shared 参数。

-fPIC 或 -fpic 参数的作用是使得 gcc 生成的代码是与位置无关的,也就是使用相对位置。
-shared参数的作用是告诉编译器生成一个动态链接库。

生成动态链接库的具体步骤如下:

  1. 将源文件进行汇编操作,需要使用参数 -c, 还需要添加额外参数 -fpic /-fPIC
# 得到若干个 .o文件
$ gcc 源文件(*.c) -c -fpic
  1. 将得到的.o 文件打包成动态库,还是使用 gcc, 使用参数 -shared 指定生成动态库。
$ gcc -shared 与位置无关的目标文件(*.o) -o 动态库(libxxx.so)
  1. 发布动态库和头文件
# 发布
1. 提供头文件: xxx.h
2. 提供动态库: libxxx.so

动态库制作举例

第一步:使用 gcc 将源文件进行汇编 (参数-c), 生成与位置无关的目标文件,需要使用参数 -fpic或者-fPIC

# 1. 将.c汇编得到.o, 需要额外的参数 -fpic/-fPIC
$ gcc add.c div.c mult.c sub.c -c -fpic -I ./include/

# 查看目录文件信息, 检查是否生成了目标文件
$ tree
.
├── add.c
├── add.o # 生成的目标文件
├── div.c
├── div.o # 生成的目标文件
├── include
│ └── head.h
├── main.c
├── mult.c
├── mult.o # 生成的目标文件
├── sub.c
└── sub.o # 生成的目标文件

第二步:使用 gcc 将得到的目标文件打包生成动态库,需要使用参数 -shared

# 2. 将得到 .o 打包成动态库, 使用gcc , 参数 -shared
$ gcc -shared add.o div.o mult.o sub.o -o libcalc.so

# 检查目录中是否生成了动态库
$ tree
.
├── add.c
├── add.o
├── div.c
├── div.o
├── include
│ └── `head.h ===> 和动态库一起发布
├── `libcalc.so ===> 生成的动态库
├── main.c
├── mult.c
├── mult.o
├── sub.c
└── sub.o

第三步:发布生成的动态库和相关的头文件

# 3. 发布库文件和头文件
1. head.h
2. libcalc.so

动态库的使用

当我们得到了一个可用的动态库之后,需要将其放到一个目录中,然后根据得到的头文件编写测试代码,对动态库中的函数进行调用。

# 1. 拿到发布的动态库
`head.h libcalc.so
# 2. 基于头文件编写测试程序, 测试动态库中提供的接口是否可用
`main.c`
# 示例目录:
.
├── head.h ==> 函数声明
├── libcalc.so ==> 函数定义
└── main.c ==> 函数测试

代码编译:

# 在编译的时候指定动态库相关的信息: 库的路径 -L, 库的名字 -l
$ gcc main.c -o app -L./ -lcalc

# 查看是否生成了可执行程序
$ tree
.
├── app # 生成的可执行程序
├── head.h
├── libcalc.so
└── main.c

# 执行生成的可执行程序, 错误提示 ==> 可执行程序执行的时候找不到动态库
$ ./app
./app: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory

解决动态库无法加载问题:

动态链接器是一个独立于应用程序的进程,属于操作系统,当用户的程序需要加载动态库的时候动态连接器就开始工作了,很显然动态连接器根本就不知道用户通过 gcc 编译程序的时候通过参数 -L 指定的路径。

那么动态链接器是如何搜索某一个动态库的呢,在它内部有一个默认的搜索顺序,按照优先级从高到低的顺序分别是:

  1. 可执行文件内部的 DT_RPATH 段
  2. 系统的环境变量 LD_LIBRARY_PATH
  3. 系统动态库的缓存文件 /etc/ld.so.cache
  4. 存储动态库 / 静态库的系统目录 /lib/, /usr/lib 等

按照以上四个顺序,依次搜索,找到之后结束遍历,最终还是没找到,动态连接器就会提示动态库找不到的错误信息。

上述运行错误就是找不到动态库的路径导致的

解决方案如下:

方案 1: 将库路径添加到环境变量 LD_LIBRARY_PATH 中

  1. 找到相关的配置文件
用户级别: ~/.bashrc —> 设置对当前用户有效
系统级别: /etc/profile —> 设置对所有用户有效
  1. 使用 vim 打开配置文件,在文件最后添加这样一句话
# 自己把路径写进去就行了
export LIBRARY_PATH=$LIBRARY_PATH:动态库的绝对路径
  1. 让修改的配置文件生效
    修改了用户级别的配置文件,关闭当前终端,打开一个新的终端配置就生效了
    修改了系统级别的配置文件,注销或关闭系统,再开机配置就生效了
    不想执行上边的操作,可以执行一个命令让配置重新被加载
# 修改的是哪一个就执行对应的那个命令
# source 可以简写为一个 . , 作用是让文件内容被重新加载
$ source ~/.bashrc (. ~/.bashrc)
$ source /etc/profile (. /etc/profile)

方案 2: 更新 /etc/ld.so.cache 文件

  1. 找到动态库所在的绝对路径(不包括库的名字)比如:/home/hou/Library/
  2. 使用 vim 修改 /etc/ld.so.conf 这个文件,将上边的路径添加到文件中 (独自占一行)
# 1. 打开文件
$ sudo vim /etc/ld.so.conf

# 2. 添加动态库路径, 并保存退出
  1. 更新 /etc/ld.so.conf 中的数据到 /etc/ld.so.cache 中
# 必须使用管理员权限执行这个命令
$ sudo ldconfig

方案 3: 拷贝动态库文件到系统库目录 /lib/ 或者 /usr/lib 中 (或者将库的软链接文件放进去)

# 库拷贝
sudo cp /xxx/xxx/libxxx.so /usr/lib

# 创建软连接
sudo ln -s /xxx/xxx/libxxx.so /usr/lib/libxxx.so

验证

在启动可执行程序之前,或者在设置了动态库路径之后,我们可以通过一个命令检测程序能不能够通过动态链接器加载到对应的动态库,这个命令叫做 ldd

# 语法:
$ ldd 可执行程序名

# 举例:
$ ldd app
linux-vdso.so.1 => (0x00007ffe8fbd6000)
libcalc.so => /home/hou/Linux/3Day/calc/test/libcalc.so (0x00007f5d85dd4000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5d85a0a000)
/lib64/ld-linux-x86-64.so.2 (0x00007f5d85fd6000) ==> 动态链接器, 操作系统提供