注意:
(1)静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。
(2)动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在。
在创建函数库前,我们先来准备举例用的源程序,并将函数库的源程序编译成.o文件。
vim编辑以下文件
// hello.c 文件
#include
void hello(const char *name){
printf("Hello %s!\n",name);
}
// hello.h 头文件
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif
// main.c 文件
#include
int main(){
hello("Kevin");
return 0;
}
本质上,静态链接库就是.o文件的集合
- 编译动态链接库.so
gcc -shared -fPIC -o libmyhello.so hello.c
注:
(1)-shared 表示生成共享库(也就是动态链接库)
(2)-fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的。共享库被加载时,在内存的位置不是固定的。PIC就是position independent code PIC使.so文件的代码段变为真正意义上的共享,如果不加-fPIC,则加载.so文件的代码段时,代码段引用的数据对象需要重定位,重定位会修改代码段的内容,这就造成每个使用这个.so文件代码段的进程在内核里都会生成这个.so文件代码段的copy.每个copy都不一样,取决于这个.so文件代码段和数据段内存映射的位置.不加fPIC编译出来的so,是要再加载时根据加载到的位置再次重定位的.(因为它里面的代码并不是位置无关代码)如果被多个应用程序共同使用,那么它们必须每个程序维护一份so的代码副本了.(因为so被每个程序加载的位置都不同,显然这些重定位后的代码也不同,当然不能共享) 。-fPIC 的使用,会生成 PIC 代码,.so 要求为 PIC,以达到动态链接的目的,否则,无法实现动态链接。 non-PIC 与 PIC 代码的区别主要在于 access global data, jump label 的不同。 比如一条 access global data的指令, non-PIC 的形势是:ld r3, var1 PIC的形式则是:ld r3, var1-offset@GOT,意思是从GOT 表的 index 为 var1-offset 的地方处 指示的地址处装载一个值,即var1-offset@GOT处的4个 byte ,其实就是 var1 的地址。这个地址只有在运行的时候才知道,是由 dynamic-loader(ld-linux.so) 填进去的。 再比如 jump label 指令 non-PIC 的形势是:jump printf ,意思是调用 printf。 PIC 的形式则是:jump
printf-offset@GOT, 意思是跳到 GOT 表的 index 为 printf-offset 的地方处指示的地址去执行,
这个地址处的代码摆放在 .plt section,
每个外部函数对应一段这样的代码,其功能是呼叫dynamic-loader(ld-linux.so) 来查找函数的地址(本例中是 printf),然后将其地址写到 GOT 表的 index 为 printf-offset 的地方, 同时执行这个函数。这样,第2次呼叫printf 的时候,就会直接跳到 printf 的地址,而不必再查找了。 GOT 是 data section, 是一个 table,除专用的几个 entry,每个 entry 的内容可以再执行的时候修改; PLT 是 text section, 是一段一段的
code,执行中不需要修改。 每个 target 实现 PIC 的机制不同,但大同小异。比如 MIPS 没有 .plt, 而是叫
.stub,功能和 .plt 一样。 可见,动态链接执行很复杂,比静态链接执行时间长;但是,极大的节省了 size,PIC和动态链接技术是计算机发展史上非常重要的一个里程碑。
2. 使用动态链接库
# gcc -o main main.c -L. -lmyhello
# ./main
# ./main: error while loading shared libraries: libmyhello.so: cannot open shared object file: No such file or directory
出错了。快看看错误提示,原来是找不到动态库文件libmyhello.so。程序在运行时,会在/usr/lib和/lib等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。我们将文件libmyhello.so复制到目录/usr/lib中,再试试。(使用”-lmyhello”标记来告诉GCC驱动程序在连接阶段引用共享函数库libmyhello.so。”-L.”标记告诉GCC函数库可能位于当前目录。否则GNU连接器会查找标准系统函数目录:它先后搜索1.elf文件的 DT_RPATH段—2.环境变量LD_LIBRARY_PATH—3./etc/ld.so.cache文件列表—4./lib/,/usr/lib目录找到库文件后将其载入内存,但是我们生成的共享库在当前文件夹下,并没有加到上述的4个路径的任何一个中,因此,执行后会出现错误)
# mv libmyhello.so /usr/lib
# ./main
# Hello Kevin!
没有报错,运行成功。
把/usr/lib/libmyhello.so删除。
第二种方法:
既然连接器会搜寻LD_LIBRARY_PATH所指定的目录,那么我们可以将这个环境变量设置成当前目录:
先执行:
export LD_LIBRARY_PATH=$(pwd)
再执行:
./main
成功!
第三种方法,就是使用ldconfig
注: 当用户在某个目录下面创建或拷贝了一个动态链接库,若想使其被系统共享,可以执行一下”ldconfig 目录名”这个命令.此命令的功能在于让ldconfig将指定目录下的动态链接库被系统共享起来,意即:在缓存文件/etc/ld.so.cache中追加进指定目录下的共享库.本例让系统共享了/home/kevin/clib目录下的动态链接库.该命令会重建/etc/ld.so.cache文件
# ldconfig /home/kevin/clib
# ./main
# Hello Kevin!
运行成功
可以查看程序执行时调用动态库的过程:
# ldd main
linux-vdso.so.1 => (0x00007fff88dcf000)
libmyhello.so => /usr/lib/libmyhello.so (0x00007f9104ed6000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9104b11000)
/lib64/ld-linux-x86-64.so.2 (0x00007f9105100000)
- ld会去找GCC命令中的参数-L
- 再找gcc的环境变量LIBRARY_PATH
- 再找内定目录 /lib /usr/lib /usr/local/lib 这是当初compile gcc时写在程序内的
- 编译目标代码时指定的动态库搜索路径;
- 环境变量LD_LIBRARY_PATH指定的动态库搜索路径;
- 配置文件/etc/ld.so.conf中指定的动态库搜索路径;
- 默认的动态库搜索路径/lib;
- 默认的动态库搜索路径/usr/lib。
LIBRARY_PATH环境变量:指定程序静态链接库文件搜索路径
LD_LIBRARY_PATH环境变量:指定程序动态链接库文件搜索路径