【Linux系统编程】Linux动态库详解

00. 目录

01. 概述

所谓“程序库”,简单说,就是包含了数据和执行码的文件。其不能单独执行,可以作为其它执行程序的一部分来完成某些功能。

库的存在可以使得程序模块化,可以加快程序的再编译,可以实现代码重用,可以使得程序便于升级。

程序库可分静态库(static library)共享库(shared library)

不管是Linux还是Windows中的库文件其本质和工作模式都是相同的, 只不过在不同的平台上库对应的文件格式和文件后缀不同。程序中调用的库有两种 静态库动态库,不管是哪种库文件本质是还是源文件,只不过是二进制格式只有计算机能够识别。

在项目中使用库一般有两个目的,一个是为了使程序更加简洁不需要在项目中维护太多的源文件,另一方面是为了源代码保密,毕竟不是所有人都想把自己编写的程序开源出来。

当我们拿到了库文件(动态库、静态库)之后要想使用还必须有这些库中提供的API函数的声明,也就是头文件,把这些都添加到项目中,就可以我们的功能啦。

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

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

02. 动态库文件格式

共享库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。

动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。

按照习惯,一般以“.so”做为文件后缀名。共享库的命名一般分为三个部分:

  • 前缀:lib
  • 库名称:自己定义即可
  • 后缀:.so

所以最终的动态库的名字应该为:libxxx.so

【温馨提示】

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

03. 动态库生成过程

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

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

在这里插入图片描述

04. 动态库制作

4.1 源码结构如下所示


deng@local:~/tmp$ tree lib
lib
├── add.c
├── add.h
├── mdiv.c
├── mdiv.h
├── mul.c
├── mul.h
├── sub.c
└── sub.h

0 directories, 8 files
deng@local:~/tmp$

4.2 将所有的.c文件生成对应的.o文件

生成目标文件,此时要加编译选项:-fPIC(fpic)

deng@local:~/tmp/lib$ gcc -fPIC -c add.c
deng@local:~/tmp/lib$ gcc -fPIC -c sub.c
deng@local:~/tmp/lib$ gcc -fPIC -c mul.c
deng@local:~/tmp/lib$ gcc -fPIC -c mdiv.c
deng@local:~/tmp/lib$ ls
add.c  add.o   mdiv.h  mul.c  mul.o  sub.h
add.h  mdiv.c  mdiv.o  mul.h  sub.c  sub.o
deng@local:~/tmp/lib$




# 或者以下方式
deng@local:~/tmp/lib$ gcc -fPIC -c add.c sub.c mul.c mdiv.c
deng@local:~/tmp/lib$ ls
add.c  add.o   mdiv.h  mul.c  mul.o  sub.h
add.h  mdiv.c  mdiv.o  mul.h  sub.c  sub.o
deng@local:~/tmp/lib$


参数:-fPIC 创建与地址无关的编译程序(pic,position independent code),是为了能够在多个应用程序间共享。

4.3 生成动态库

deng@local:~/tmp/lib$ gcc -shared add.o sub.o mul.o mdiv.o -o libtest.so
deng@local:~/tmp/lib$ ls
add.c  add.o       mdiv.c  mdiv.o  mul.h  sub.c  sub.o
add.h  libtest.so  mdiv.h  mul.c   mul.o  sub.h
deng@local:~/tmp/lib$



4.4 动态库发布

静态库制作完成之后,需要将.so文件和头文件一起发布给用户。

05. 动态库测试

5.1 工程结构如下所示


deng@local:~/tmp$ tree test/
test/
├── add.h
├── libtest.a
├── mdiv.h
├── mul.h
├── sub.h
└── test.c

0 directories, 6 files
deng@local:~/tmp$


5.2 编译test.c,生成可执行文件

deng@local:~/tmp/test$ gcc test.c -o test -L./ -I./ -ltest
deng@local:~/tmp/test$ ls
add.h  libtest.so  mdiv.h  mul.h  sub.h  test  test.c
deng@local:~/tmp/test$




参数说明:

  • -L:表示要连接的库所在目录
  • -I./: I(大写i) 表示指定头文件的目录为当前目录
  • -l(小写L):指定链接时需要的库,去掉前缀和后缀

5.3 执行验证

deng@local:~/tmp/test$ ./test
x + y = 18
x - y = 12
x * y = 45
x / y = 5
deng@local:~/tmp/test$

06. 动态库无法加载问题

问题描述:

deng@local:~/tmp/test$ ./test
./test: error while loading shared libraries: libtest.so: cannot open shared object file: No such file or directory
deng@local:~/tmp/test$

6.1 库的工作原理

  • 静态库加载原理

    在程序编译的最后一个阶段也就是链接阶段,提供的静态库会被打包到可执行程序中。当可执行程序被执行,静态库中的代码也会一并被加载到内存中,因此不会出现静态库找不到无法被加载的问题。

  • 动态库加载原理

    • 在程序编译的最后一个阶段也就是链接阶段:
      • 在gcc命令中虽然指定了库路径(使用参数 -L ), 但是这个路径并没有记录到可执行程序中,只是检查了这个路径下的库文件是否存在。
      • 同样对应的动态库文件也没有被打包到可执行程序中,只是在可执行程序中记录了库的名字。
    • 可执行程序被执行起来之后:
      • 程序执行的时候会先检测需要的动态库是否可以被加载,加载不到就会提示上边的错误信息
      • 当动态库中的函数在程序中被调用了, 这个时候动态库才加载到内存,如果不被调用就不加载
      • 动态库的检测和内存加载操作都是由动态连接器来完成的

6.2 动态连接器

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

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

(1) 可执行文件内部的 DT_RPATH 段

(2) 系统的环境变量 LD_LIBRARY_PATH

(3) 系统动态库的缓存文件 /etc/ld.so.cache

(4) 存储动态库/静态库的系统目录 /lib/, /usr/lib

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

6.3 动态库无法加载解决办法

可执行程序生成之后, 根据动态链接器的搜索路径, 我们可以提供三种解决方案,我们只需要将动态库的路径放到对应的环境变量或者系统配置文件中,同样也可以将动态库拷贝到系统库目录(或者是将动态库的软链接文件放到这些系统库目录中)。

方式一: 将库路径添加到环境变量 LD_LIBRARY_PATH 中

(1) 找到相关的配置文件

  • 用户级别: ~/.bashrc 设置对当前用户有效
  • 系统级别: /etc/profile 设置对所有用户有效

(2) 在上述任意配置文件中最后添加一下内容

# 动态库的绝对路径
#export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库的绝对路径


export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/deng/tmp/test/


(3) 配置文件生效

  • 修改了用户级别的配置文件, 关闭当前终端, 打开一个新的终端配置就生效了
  • 修改了系统级别的配置文件, 注销或关闭系统, 再开机配置就生效了
  • 不想执行上边的操作, 可以执行一个命令让配置重新被加载
deng@local:~/tmp/test$ source ~/.bashrc
deng@local:~/tmp/test$

方式二:更新 /etc/ld.so.cache 文件

(1) 修改 /etc/ld.so.conf 这个文件, 在文件最后添加一下库的路径

/home/deng/tmp/test

(2) 更新 /etc/ld.so.conf中的数据到 /etc/ld.so.cache

deng@local:~/tmp/test$ sudo ldconfig
deng@local:~/tmp/test$


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

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

deng@local:~/tmp/test$ sudo cp libtest.so /usr/lib/


# 创建软连接
#sudo ln -s /xxx/xxx/libxxx.so /usr/lib/libxxx.so
deng@local:~/tmp/test$ sudo cp libtest.so /lib/

07. 动态库优缺点

  • 优点:
    • 可实现不同进程间的资源共享
    • 动态库升级简单, 只需要替换库文件, 无需重新编译应用程序
    • 可以控制何时加载动态库, 不调用库函数动态库不会被加载
  • 缺点:
    • 加载速度比静态库慢, 以现在计算机的性能可以忽略
    • 发布程序需要提供依赖的动态库

在这里插入图片描述

08. 技术交流

物联网、嵌入式、C++、C方向等等兴趣爱好者可以添加WX一起交流。 点击主页私信,添加博主微信。

09. 附录

猜你喜欢

转载自blog.csdn.net/dengjin20104042056/article/details/131926799
今日推荐