Linux之静态库,动态库,动态加载库

来源:互联网 发布:手持式网络测试仪 编辑:程序博客网 时间:2024/05/17 23:50

一. 静态库

静态库是.o文件的集合,一般通过ar工具使用-rsv将所有的.o文件打包为libXXXX.a的格式. 在生成可执行文件时,使用ld工具对libXXXX.o进行链接。静态库,只是在程序链接的时候起作用,程序在运行时将脱离静态库。这是因为静态库的代码和数据已经集成到最终的可执行程序中了。其缺点就是最终生成的可执行文件占用的空间比较大,而且当库更新后,程序需要进行重新链接。这增加了程序升级的复杂性。

以下为3个源文件1.c,2.c以及3.c:

[lichao@sg01 a]$ cat 1.c #include<stdio.h>int func1(){        printf("calling func1...\n");        return 0;}[lichao@sg01 a]$ cat 2.c #include<stdio.h>void func2() {        printf("calling func2...\n");}[lichao@sg01 a]$ cat main.c #include<stdio.h>int main() {        func1();        func2();        return 0;}
具体的静态库操作演示如下:
[lichao@sg01 a]$ ls1.c  2.c  main.c[lichao@sg01 a]$ gcc -c 1.c 2.c main.c [lichao@sg01 a]$ ls1.c  1.o  2.c  2.o  main.c  main.o[lichao@sg01 a]$ ar -rsv libprint.a 1.o 2.oar: creating libprint.aa - 1.oa - 2.o[lichao@sg01 a]$ gcc -o main main.o -L. -lprint[lichao@sg01 a]$ ./main calling func1...calling func2...[lichao@sg01 a]$ gcc -o main main.o libprint.a[lichao@sg01 a]$ ./main calling func1...calling func2...
在使用静态库生成可执行程序时,有两种方法:

  • 如果静态库的名字是约定俗称的以“lib”开头并且以“a”为后缀,则可以用-L指示搜索的位置,用-lname来表示libname.a
  • 如果静态库的名字不符合约定俗称的名字规范,则可以直接将静态库的名字写在可执行文件和-L选项的后面
注意:-l是连接器选项,所以应该放在所有目标文件的后面

二. 动态库

动态库的目的就是为了消除静态库的这两个缺点。动态库也可以理解为共享库,通常的后缀为so(shared object). 在程序链接的过程中,并不像静态库那样需要把数据和代码拷贝到最终的可执行程序文件中,而只是在程序文件的某个区域中做一些标记。具体的说,链接器在链接的过程中,会在指定的动态库中搜索、解析被主程序调用的函数和引用的数据,如果找到对应的符号信息,则在主程序的XCOFF头结构的loader区域中,建立包含引用的动态库的映射信息。在主程序的执行阶段,系统的相关模块会读取定义在主程序XCOFF头结构loader区域中的相关信息,查找并加载相关的动态库。当所有引用的动态库加载到内存后,程序才开始执行。

动态共享库的名字一般有三种,分别是linker-name, so-name, real-name.以下面的so为例:

lrwxrwxrwx 1 root root     20 2008-05-25 13:54 libncurses.so -> /lib/libncurses.so.5
lrwxrwxrwx 1 root root     13 2008-05-26 15:18 libncurses.so.5 -> libtermcap.so

其中,libncurses.so为linker-name,因为在编译时实际用的名字。libncurses.so.5是so-name,so-name通常还包含主版本号(5),副版本号和发行号.libtermcap.so是real-name,即实际保存数据和代码的库.

在执行一个可执行的二进制ELF文件时,一个特殊的程序“程序装载器”会被自动装载并执行。在Linux中这个装载器就是/lib/ld-linux.so.X(X是版本号). 装载器会首先查找应用程序所依赖的所有的共享库。而这些查找的目录就保存在文件/etc/ld.so.conf文件中.为了加速这个查找和搜索过程,可以使用ldconfig命令,它缺省时读取/etc/ld.so.conf文件,将所有的动态共享库按照一定的规则建立符号链接,然后将这些信息写入/etc/ld.so.cache这个文件中.正是/etc/ld.so.cache这个文件的存在大大加快了程序的启动速度。还可以使用LD_LIBRARY_PATH这个环境变量来设置ld的装载目录,这样装载器就会先搜索LD_LIBRARY_PATH指定的目录,然后再搜索目录的路径. 如果这种方式也不使用的话,可以使用--library-path将搜索的目录传给装载器,具体格式如下:

/lib/ld-linux-so.2 --library-path PATH EXECUTABLE

构成动态共享库(.so)的目标文件(.o)需要使用-fpic参数来生成,典型的用法是:

gcc -fpic -c xxx.c 

生成动态共享库的语法格式为:

gcc -shared -Wl,soname,target_so_name.so -o library_so_name library_list

以下三个文件为实验的源文件:1.c 2.c 3.c

[lichao@sg01 so]$ ls -llrttotal 12-rw-rw-r-- 1 lichao lichao 65 Mar 28 21:27 main.c-rw-rw-r-- 1 lichao lichao 67 Mar 28 21:27 2.c-rw-rw-r-- 1 lichao lichao 76 Mar 28 21:27 1.c[lichao@sg01 so]$ gcc -fpic -c 1.c 2.c [lichao@sg01 so]$ gcc -c main.c [lichao@sg01 so]$ gcc -shared -Wl,-soname,libprint.so -o libprint.so.0.0.1 1.o 2.o [lichao@sg01 so]$ ls1.c  1.o  2.c  2.o  libprint.so.0.0.1  main.c  main.o[lichao@sg01 so]$ gcc -o main main.o libprint.so.0.0.1 [lichao@sg01 so]$ ./main ./main: error while loading shared libraries: libprint.so: cannot open shared object file: No such file or directory[lichao@sg01 so]$ su rootPassword: [root@sg01 so]# cp libprint.so.0.0.1 /usr/local/lib[root@sg01 so]# exitexit[lichao@sg01 so]$ ./main ./main: error while loading shared libraries: libprint.so: cannot open shared object file: No such file or directory
单纯这样将SO文件拷贝到库中是不行的,因为库搜索的时候是按照libprint.so去搜索的。需要在这里再建立一个链接到libprint.so.0.0.1的符号链接文件。操作如下:
[lichao@sg01 so]$ su rootPassword: [root@sg01 so]# cd /usr/local/lib[root@sg01 lib]# ln -s libprint.so.0.0.1 libprint.so[root@sg01 lib]# ls -llrttotal 142212....-rwxr-xr-x  1 root   root       6067 Mar 28 21:49 libprint.so.0.0.1lrwxrwxrwx  1 root   root         17 Mar 28 21:51 libprint.so -> libprint.so.0.0.1[root@sg01 lib]# exitexit[lichao@sg01 so]$ ./main calling func1...calling func2...
这样就可以了,如果不用做版本维护的话,可以使用更简单的操作:
[lichao@sg01 so]$ ls1.c  2.c  main.c[lichao@sg01 so]$ gcc -fpic -shared -o libprint.so 1.c 2.c [lichao@sg01 so]$ ls1.c  2.c  libprint.so  main.c[lichao@sg01 so]$ su root Password: [root@sg01 so]# cp libprint.so /usr/local/lib[root@sg01 so]# exitexit[lichao@sg01 so]$ gcc -o main main.c -L. -lprint[lichao@sg01 so]$ ./main calling func1...calling func2...

三. 动态加载库

动态加载库和动态共享库的区别不大,主要是利用操作系统提供的接口函数完成对动态库的加载和使用。普通的动态共享库是由操作系统在执行可执行程序之前由加载器负责加载完成的。动态加载库提供了更高的灵活性,这对于编写插件式或者模块式的大型框架是非常有用的。
系统提供的三个接口函数分别为:dlopen,dlerror,dlsym,dlclose。这四个接口的说明如下:
   (1) dlopen   
   函数原型:void *dlopen(const char *libname,int flag);
   功能描述:dlopen必须在dlerror,dlsym和dlclose之前调用,表示要将库装载到内存,准备使用。如果要装载的库依赖于其它库,必须首先装载依赖库。如果dlopen操作失   
                       败,返回NULL值;如果库已经被装载过,则dlopen会返回同样的句柄。参数中的libname一般是库的全路径,这样dlopen会直接装载该文件;如果只是指定了库
                       名称,在dlopen
                在查找库的过程中会按照如下路径进行搜索:
              a.根据环境变量LD_LIBRARY_PATH查找
              b.根据/etc/ld.so.cache查找
              c.查找依次在/lib和/usr/lib目录查找。
                 flag参数表示处理未定义函数的方式,可以使用RTLD_LAZY或RTLD_NOW。RTLD_LAZY表示暂时不去处理未定义函数,先把库装载到内存,等用到没定义的函数
                     再说;RTLD_NOW表示马上检查是否存在未定义的函数,若存在,则dlopen以失败告终。
   (2) dlerror
   函数原型:char *dlerror(void);
   功能描述:dlerror可以获得最近一次dlopen,dlsym或dlclose操作的错误信息,返回NULL表示无错误。dlerror在返回错误信息的同时,也会清除错误信息。
   (3) dlsym
   函数原型:void *dlsym(void *handle,const char *symbol);
   功能描述:在dlopen之后,库被装载到内存。dlsym可以获得指定函数(symbol)在内存中的位置(指针)。如果找不到指定函数,则dlsym会返回NULL值。但判断函数是否存在最好的方法是使用dlerror函数,
   (4) dlclose
   函数原型:int dlclose(void *);
   功能描述:将已经装载的库句柄减一,如果句柄减至零,则该库会被卸载。如果存在析构函数,则在dlclose之后,析构函数会被调用。  
演示的源代码如下:
[lichao@sg01 dynamic_load]$ cat 1.c#include<stdio.h>int func1(){        printf("calling func1...\n");        return 0;}[lichao@sg01 dynamic_load]$ cat 2.c#include<stdio.h>void func2() {        printf("calling func2...\n");}[lichao@sg01 dynamic_load]$ cat main.c #include <stdio.h>#include <dlfcn.h>#include <stdlib.h>int main() {        void * handle;        void (*func)();         handle=dlopen("/usr/local/lib/libmyso.so",RTLD_NOW);        if(handle == NULL) {                fprintf(stdout,dlerror());                exit(-1);        }        func=dlsym(handle,"func1");        if(func == NULL){                fprintf(stdout,dlerror());                exit(-2);        }        func();        func=dlsym(handle,"func2");        if(func == NULL) {                fprintf(stdout,dlerror());                exit(-3);        }        func();        return 0;}
演示结果如下:
[lichao@sg01 dynamic_load]$ ls1.c  2.c  main.c[lichao@sg01 dynamic_load]$ gcc -fpic -shared -o libmyso.so 1.c 2.c[lichao@sg01 dynamic_load]$ ls1.c  2.c  libmyso.so  main.c[lichao@sg01 dynamic_load]$ su rootPassword: [root@sg01 dynamic_load]# cp libmyso.so /usr/local/libcp: overwrite `/usr/local/lib/libmyso.so'? y[root@sg01 dynamic_load]# exitexit[lichao@sg01 dynamic_load]$ gcc -o main main.c -ldl[lichao@sg01 dynamic_load]$ ./main calling func1...calling func2...

0 0