共享库的定义:
本质上来说库是一种可执行代码的二进制形式(符合ELF文件格式,即不同操作系统之间可移植的二进制文件格式,readelf -l查看),可以被操作系统载入内存执行。
功能上讲库的执行代码可在多进程、线程(运行程序)间共享,节省了空间,提高了效率,具备很高的灵活性。
共享库的分类:
库可分为两种:静态库(.a)和动态库(.so),不同点在于被载入的时刻不同;
静态库的代码在编译过程中已经被载入可执行程序,因此体积较大(用ar命令创建静态库);
静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。
动态库的代码是在可执行程序运行时才载入内存的,在编译过程中只简单的引用,因此代码体积较小;
动态库在程序编译时并不会被连接到目标代码中,而是在程序运行时才被载入,因此在程序运行时还需要动态库存在。
动态链接库详解:
动态库,即Shared Object,共享对象。动态库的特点是程序运行时可以动态选择加载各种程序模块;优点在于不同的应用程序调用相同的库,那么在内存里只需要有一份该共享库的实例,节省内存。
动态链接:应用程序执行时,操作系统将程序依赖的目标文件加载到内存,如果依赖关系满足,则开始进行链接,即符号解析(符号:编译器在对程序进行编译时生成符号表,例如:函数),地址重定位(重定位:将程序逻辑地址空间转变成内存中实际物理地址空间的过程),假如依赖文件已经被载入内存,直接进行链接。
补充说明:应用程序加载时,动态链接器(共享库装载器ld-***.so)将所有依赖的动态库装载到进程地址空间(虚拟地址空间/逻辑地址空间),将程序中未决议符号绑定到相应的动态链接库,进行重定位工作。
当程序被调用的时候,Linux共享库装载器也自动被调用,作用是保证程序所需要的所有适当版本的库都被调入内存。
当程序隐式或显示调用一个动态库里的函数时,系统都要把这个动态库映射到这个进程的虚拟地址空间里,这使得动态库成为进程的一部分,以这个进程的身份执行,使用这个进程的堆栈。
创建动态链接库使用参数说明:
装载时重定位可能导致指令部分无法进程间共享的问题,因为动态库被一个进程重定位成自己使用的值后,其他进程就无法使用该共享对象了,这种装载重定位就是单独使用-shared参数,不使用-fPIC的情况。
-fPIC:意义就在于解决上述情况,基本思想是把指令中需要进行重定位修改的那部分分离出来,跟数据部分一起,使得每个进程都有一个副本,即地址无关代码技术;
-shared:告诉编译器产生共享库代码;
-WI:告诉编译器将后面的参数传递到链接器;
-fPIC和-fpic区别在于-fpic产生的代码小,执行速度快。但是-fpic在某些平台上会有限制,因为地址无关代码是硬件平台相关的。比如全局符号的数量、代码长度等。-fPIC则没有这种限制。readelf -d ***|grep TEXTREL,如果没有输出,则是PIC的,TEXTREL即代码段重定位表,PIC的动态库是不存在TEXTREL的。
动态链接库中的全局变量,在进程间共享,但写时拷贝
写时拷贝(copy-on-write),针对共享资源所应用的一种技术,例:多个进程访问同一个资源时,属性是可读,但不可写,如果需要修改,则copy一份出来,各进程都有自己的备份,故共享,但写时拷贝。POSIX利用写时拷贝来实现fork函数,产生的新进程不是拷贝整个地址空间,而是通过将页面标记为写时复制的方式,与父进程共享这些页面。如果子进程在这些页面中写入数据了,则生成一份进程私有的拷贝。如果没有写操作,则2个进程继续共享页面,不会执行拷贝动作。不管怎么样,内存管理器只拷贝一个进程试图要写入数据的那些页面,而不是整个地址空间。
LINUX下使用动态链接库,源程序需要包含dlfcn.h头文件,此文件定义了调用动态链接库的函数的原型。
1、dlopen
原型void *dlopen(const char *filename,int flag);
dlopen用于打开指定文件filename的动态链接库,并返回操作句柄,失败返回NULL。
filename:如果不是绝对路径,查找顺序是LD_LIBRARY_PATH -> ld.so.cache缓存 -> /lib,/usr/lib。
flag:RTLD_NOW 立刻计算库的依赖性;
RTLD_LAZY 在需要的时候才计算;
RTLD_GLABAL 使得那些在以后才加载的库可以获得其中的符号(函数);
dlopen打开一个新库,并把它装入内存。主要用来加载库中的符号,这些符号在编译的时候是不知道的。
2、dlsym
原型void *dlsym(void *handle,char *symbol);
取函数动态执行地址。
handle:dlopen函数返回的句柄;
symbol:所调用函数的符号名称;
int (*symbol)(char *argu,char *argu1);
symbol=(int (*)(char *,char *))dlsym(handle,"symbol");
3、dlclose
原型int dlclose(void *handle);
用于关闭指定句柄的动态链接库,当所要关闭库的使用计数为0时,才会真正被系统卸载;
4、dlerror
原型const char *dlerror(void);
返回值为NULL时,表示操作函数执行成功;
查看方法:
可通过ldd命令查看一个可执行程序依赖的共享库。
ldd /lib64/libpthread.so.*
共享库的识别:
1、安装在/lib、/usr/lib下,系统动态载入器(dynamic linker/loader)ld-linux.so*默认可以找到;
2、编辑/etc/ld.so.conf文件,加入库文件所在目录的路径,运行ldconfig,重建ld.so.cache文件;
3、对于elf格式文件,可以设置环境变量LD_LIBRARY_PATH,将路径包含进去;