动态库的制作
动态库(在Windows上称为DLL,即Dynamic Link Library,在Unix-like系统上称为SO,即Shared Object)的制作过程涉及几个关键步骤:编写源代码、编译源代码为共享的动态对象,并链接它们生成动态库。以下是在Unix-like系统和Windows系统上创建动态库的一般步骤。
-
编写源代码:
创建你的功能代码的源文件,例如file1.cpp
和file2.cpp
。 -
编译源代码为位置无关代码(PIC):
位置无关代码是在内存中可灵活定位的代码,适合用于动态库。g++ -fpic -c file1.cpp g++ -fpic -c file2.cpp
-
创建动态库(.so 文件):
使用g++
或gcc
将位置无关的对象文件链接为动态库。g++ -shared -o libmydynamic.so file1.o file2.o
-
设置动态库的路径:
为了让执行文件在运行时能找到动态库,你可能需要设置LD_LIBRARY_PATH
环境变量或者修改/etc/ld.so.conf
并运行ldconfig
。export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/library
daic@daic:~/Linux/lesson06/library$ tree
.
├── include
│ └── head.h
├── lib
├── main.c
└── src├── add.c├── div.c├── mult.c└── sub.c3 directories, 6 files
daic@daic:~/Linux/lesson06/library$ cd src
daic@daic:~/Linux/lesson06/library/src$ gcc -c -fpic *.c
sub.c:2:10: fatal error: head.h: No such file or directory#include "head.h"^~~~~~~~
compilation terminated.
daic@daic:~/Linux/lesson06/library/src$ gcc -c -fpic *.c -I ../include/
daic@daic:~/Linux/lesson06/library/src$ tree
.
├── add.c
├── add.o
├── div.c
├── div.o
├── mult.c
├── mult.o
├── sub.c
└── sub.o0 directories, 8 files
daic@daic:~/Linux/lesson06/library/src$ cd ../lib
daic@daic:~/Linux/lesson06/library/lib$ gcc -shared ../src/*.o -o libcalc.so
daic@daic:~/Linux/lesson06/library/lib$ tree
.
└── libcalc.so
动态库的使用
daic@daic:~/Linux/lesson06$ tree
.
├── calc
│ ├── add.c
│ ├── div.c
│ ├── head.h
│ ├── main.c
│ ├── mult.c
│ └── sub.c
└── library├── include│ └── head.h├── lib│ └── libcalc.so├── main.c└── src├── add.c├── div.c├── mult.c└── sub.c5 directories, 13 files
daic@daic:~/Linux/lesson06$ cd library/
daic@daic:~/Linux/lesson06/library$ gcc main.c -o app -I ./include/ -L ./lib -l calc
daic@daic:~/Linux/lesson06/library$ tree
.
├── app
├── include
│ └── head.h
├── lib
│ └── libcalc.so
├── main.c
└── src├── add.c├── div.c├── mult.c└── sub.c3 directories, 8 files
daic@daic:~/Linux/lesson06/library$ ./app
./app: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory
这里会遇到动态库加载失败的问题
静态库:gcc进行链接时,会把静态库中代码打包到可执行程序中
动态库:gcc进行链接时,动态库代码不会被打包到可执行程序中
程序启动之后,动态库会被动态加载到内存中,通过ldd(list dynamic depdencies)命令检查动态库依赖关系。
daic@daic:~/Linux/lesson06/library$ ldd main
ldd: ./main: No such file or directory
daic@daic:~/Linux/lesson06/library$ ldd applinux-vdso.so.1 (0x00007ffc19e52000)libcalc.so => not foundlibc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f52bec10000)/lib64/ld-linux-x86-64.so.2 (0x00007f52bf203000)
动态库加载失败原因
ELF(Executable and Linkable Format)
ELF 是一种常用的文件格式,用于定义程序、库文件(如 .so
文件),甚至是核心转储文件在类 Unix 系统(如 Linux)上的结构。ELF 格式是可执行文件和可链接格式的简称,它是最通用的标准文件格式之一,用于可执行程序和库。
ELF 文件包含了程序执行所需的各种信息,包括代码(也称为“文本”段)、数据段、符号表、重定位表等。对于动态链接库和可执行程序而言,ELF 格式提供了存储和运行时所需的结构信息,使得系统加载器可以正确地映射和执行文件。
动态链接过程
ld-linux.so
是 Linux 系统的动态链接器,负责载入 ELF 文件并解析其依赖的共享库文件。动态链接器的工作流程如下:
-
读取 ELF 文件的 DT_RPATH 段:这是动态链接时库文件搜索的首选路径,通常在编译时指定。
-
环境变量
LD_LIBRARY_PATH
:如果DT_RPATH
未定义或不包含所需的库,链接器会查看LD_LIBRARY_PATH
环境变量。这是一个由用户或管理员设置的环境变量,用于指定额外的库搜索路径。 -
/etc/ld.so.cache
文件:这是一个预先编译的库文件路径列表,由ldconfig
命令根据系统中安装的库更新。它提供了一种快速查找库文件位置的方法。 -
标准库目录:如果以上都未找到所需的库,则动态链接器会搜索标准库目录,通常是
/lib
和/usr/lib
。
通过这个过程,动态链接器能够找到所有可执行文件所依赖的共享库,并将它们加载到内存中,使程序可以运行。
这个过程中,如果对特定的库或搜索路径有疑问,或者需要调试与库相关的问题,可以使用 ldd
命令来查看可执行文件所依赖的库及其路径,或者使用 strace
来跟踪程序启动时的系统调用,包括动态链接器的活动。这些工具都是分析和解决与 ELF 文件和动态链接相关问题的有力工具。
解决动态库加载问题
DT_RPATH 我们无法改变,不用管
出现这个错误的原因是操作系统在运行 app
程序时,没有找到需要的动态库 libcalc.so
。动态库在运行时需要被加载,如果系统的动态链接器没有在配置的库路径中找到这个库,就会抛出这样的错误。
解决方案:
-
设置 LD_LIBRARY_PATH 环境变量:
你可以临时通过设置LD_LIBRARY_PATH
环境变量来指定附加的库搜索路径。这个环境变量包含了一系列目录,系统在这些目录中搜索需要的动态库。export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/your/library/dir ./app
在这个例子中,应该将
/path/to/your/library/dir
替换为libcalc.so
文件的实际路径,例如:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/daic/Linux/lesson06/library/lib ./app
-
将库文件复制到标准库路径:(不推荐)
另一个解决方案是将libcalc.so
复制到系统的标准库路径下,如/usr/lib
或/usr/local/lib
。这样,库文件就会被自动识别,无需设置环境变量。sudo cp /home/daic/Linux/lesson06/library/lib/libcalc.so /usr/local/lib/ sudo ldconfig # 更新动态链接器的缓存 ./app
-
在编译时指定 rpath:
在编译app
程序时,你可以使用-rpath
链接选项来指定一个运行时库搜索路径。这个选项告诉动态链接器在特定的位置查找动态库。gcc main.c -o app -I ./include/ -L ./lib -lcalc -Wl,-rpath,/home/daic/Linux/lesson06/library/lib ./app
-
修改
/etc/ld.so.conf
文件:
你也可以将你的库路径添加到/etc/ld.so.conf
文件或者该文件引用的其他文件中,然后运行ldconfig
。这是一个更为永久的解决方案,适用于多个程序或用户需要访问该库。echo "/home/daic/Linux/lesson06/library/lib" | sudo tee -a /etc/ld.so.conf sudo ldconfig ./app
以上任一方法都可以解决动态库未找到的问题。选择哪一种取决于你的具体需求和环境。
临时配置环境变量
如果是为了临时测试,设置 LD_LIBRARY_PATH
可能是最快的方法;如果是为了长期或者系统范围的使用,修改 /etc/ld.so.conf
或使用 rpath
可能更为合适。
daic@daic:~/Linux/lesson06/library/lib$ pwd
/home/daic/Linux/lesson06/library/lib
daic@daic:~/Linux/lesson06/library/lib$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/daic/Linux/lesson06/library/lib
daic@daic:~/Linux/lesson06/library/lib$ echo $LD_LIBRARY_PATH
:/home/daic/Linux/lesson06/library/lib
daic@daic:~/Linux/lesson06/library/lib$ tree
.
└── libcalc.so0 directories, 1 file
daic@daic:~/Linux/lesson06/library/lib$ cd ..
daic@daic:~/Linux/lesson06/library$ tree
.
├── app
├── include
│ └── head.h
├── lib
│ └── libcalc.so
├── main.c
└── src├── add.c├── div.c├── mult.c└── sub.c3 directories, 8 files
daic@daic:~/Linux/lesson06/library$ ldd applinux-vdso.so.1 (0x00007ffc419de000)libcalc.so => /home/daic/Linux/lesson06/library/lib/libcalc.so (0x00007f648c7a4000)libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f648c3b3000)/lib64/ld-linux-x86-64.so.2 (0x00007f648cba8000)
daic@daic:~/Linux/lesson06/library$ ./app
a = 20, b = 12
a + b = 32
a - b = 8
a * b = 240
a / b = 1.666667
但是这样配置的环境变量是临时的,只在当前终端中有用
daic@daic:~/Linux/lesson06/library$ ldd applinux-vdso.so.1 (0x00007ffed4f7c000)libcalc.so => not foundlibc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff495d08000)/lib64/ld-linux-x86-64.so.2 (0x00007ff4962fb000)
daic@daic:~/Linux/lesson06/library$ ./app
./app: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory
在Unix-like系统中,永久设置环境变量通常涉及编辑用户或系统级的配置文件。这些设置会在每次登录或启动一个新的终端会话时自动应用。以下是几种常见的方法来
永久配置环境变量:
对单个用户设置
-
编辑
.bashrc
或.profile
文件:
对于使用 bash shell 的用户,可以在用户的家目录下编辑.bashrc
文件;如果是登录shell,比如通过图形界面登录,编辑.profile
文件会更合适。在文件的末尾添加如下行:export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/path/to/your/library"
将
/path/to/your/library
替换成你的实际库路径。保存文件后,这个变量在下次登录时会自动设置。 -
使改动立即生效:
你可以通过运行以下命令使.bashrc
或.profile
的更改立即生效,而不需要重新登录:source ~/.bashrc # 或者 source ~/.profile
对所有用户设置
如果你希望为系统上的所有用户设置环境变量,可以编辑 /etc/profile
或 /etc/environment
:
-
编辑
/etc/profile
:
在/etc/profile
文件中添加同样的export
命令。这会影响所有使用 bash shell 的用户:export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/path/to/your/library"
这种方式在用户登录时生效,并适用于所有bash用户。
-
编辑
/etc/environment
:
在/etc/environment
中设置环境变量略有不同,因为这个文件不是一个脚本,而是一个存储环境变量的列表,每个变量一行:LD_LIBRARY_PATH="/usr/lib:/usr/local/lib:/path/to/your/library"
注意,这里不能使用
$LD_LIBRARY_PATH
来引用已存在的值,因为/etc/environment
不支持shell命令。这个文件由PAM模块读取,适用于所有用户和登录方式。
注意事项
- 当你修改环境变量时,确保不要覆盖掉其他重要的系统路径和设置。
- 对于使用其他shell(如
zsh
或fish
)的系统,你需要修改对应的配置文件,比如~/.zshrc
或~/.config/fish/config.fish
。 - 对于环境变量的永久配置,确保你了解其对系统安全和稳定性的潜在影响,特别是在全局设置时。
以上方法可以帮助你根据需要为个人或整个系统设置环境变量。选择合适的方法取决于你的具体需求和系统环境。