作者:gnuhpc
出处:http://www.cnblogs.com/gnuhpc/
实验环境:Ubuntu Linux 10.04 32bit
1.库文件简介
库文件是一个包含了编译后代码、数据的文件,用于与程序其他代码连编,它可以使得程序模块化、编译速度更快,并且易于更新。库文件分为三种(实质为两种,在随后两句话有解释):静态库(在程序之前就已经装载进其中了,也就是说编译的时候就将库中需要的代码拷贝至可执行文件)、共享库(在程序启动之时加载进去,也就是说可执行文件中只包含一个它需要的函数表,具体实现的完整机器码需要从外界在启动时加载,这些机器码在运行前从共享库拷贝到内存中——这个过程称为动态链接)、动态加载库(dynamically loaded,DL)(在程序运行中任何时候都可以被加载进程序中使用,事实上DL并非是一个完全不同的库类型,共享库可以用作DL而被动态加载(静态库在Linux貌似无法用dlopen加载)。注意有些人使用dynamically linked libraries (DLLs)来指代共享库,有些人使用DLL这个词来形容任何可以被用作DL的库文件,这个请区分对待。
在具体使用中,我们应该多使用共享库,这使得用户可以独立于使用该库文件的程序而更新库。DL的确非常有用,但有时候我们可能并不需要那些灵活性,而对于静态库,由于更新起来实在费劲,我们一般不使用。
2.静态库的建立
静态库就是一堆普通的目标文件(object file),习惯上静态库以.a为后缀,这是使用ar命令生成的。静态库允许用户不用重新编译代码就可以链接程序,以节省重新编译的时间,其实这个时间已经在强大的机器配置和快速的编译器中显得微不足道了,这个常常用来提供程序而不是源代码。速度上,静态ELF(Executable and Linking Format)库文件比共享库或者动态加载库快1%-5%,但实际上常常因为其他因素而并不一定快。
我们写主文件prog.c:
1: #include2: void ctest1(int *);3: void ctest2(int *);4:5: int main()
6: {7: int x;
8: ctest1(&x);9: printf("Valx=%d/n",x);
10:11: return 0;
12: }13:
然后写这两个函数的实现:
ctest1.c
1: void ctest1(int *i)2: {3: *i=5;4: }
ctest2.c
1: void ctest2(int *i)2: {3: *i=100;4: }
我们首先编译这两个函数实现的源文件:
gnuhpc@gnuhpc-desktop:~/MyCode/lib/statics$ gcc -Wall -c ctest1.c ctest2.c
gnuhpc@gnuhpc-desktop:~/MyCode/lib/statics$ ls
ctest1.c ctest1.o ctest2.c ctest2.o prog.c
然后创建静态库libctest.a:
gnuhpc@gnuhpc-desktop:~/MyCode/lib/statics$ ar -cvq libctest.a ctest1.o ctest2.o
a - ctest1.o
a - ctest2.o
我们查看一下这个库中的文件:
gnuhpc@gnuhpc-desktop:~/MyCode/lib/statics$ ar -t libctest.a
ctest1.o
ctest2.o
此时我们可以编译我们的程序了,一般都会在/usr/local/lib/或者/usr/lib/中寻找库(按顺序),所以此处使用-L选项指定除了标准库位置外的指定目录(本例为当前目录)也要找,另外注意-l选项,后边的参数是去掉lib和.a的部分,放在要编译的文件名之后,否则会报错,另外使用多个库时要注意依赖关系,库A使用库B的函数,那么在指定库时,库A 就要出现在库A的前边(这是个好习惯,虽然有时很麻烦,而且有的编译器也会自动查找所有的库而使这个考虑显得多此一举)。当然,你还可以不用-l选项而直接将libctest.a和源文件放在一起编译:
gnuhpc@gnuhpc-desktop:~/MyCode/lib/statics$ gcc -o test prog.c -L./ –lctest
gnuhpc@gnuhpc-desktop:~/MyCode/lib/statics$ ls
ctest1.c ctest1.o ctest2.c ctest2.o libctest.a prog.c test
gnuhpc@gnuhpc-desktop:~/MyCode/lib/statics$ ./test
Valx=5
3.共享库的建立
共享库是在程序启动时加载的库文件。当共享库加载完毕后所有启动的起来的程序都将使用新的共享库。因此,共享库的方式既减少了可执行文件的大小,又减少了其内存消耗(多个程序共用内存中一段共享库代码时)。另外,它又提供了不需要重新编译即可升级程序的方法。由于这些好处,gcc的-l选项一般都会先检查同名的.so文件后再试图连接.a静态库文件。(除非你指定了‘-static’选项强制使用静态库)。
在创建共享库之前,还需要了解一些知识:
共享库的主要有三个步骤:
现在我们举个例子来说明,首先我们编译源代码,使用-fPIC选项生成共享库所需的位置独立代码(position-independent code (PIC)):
gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ gcc -Wall -fPIC -c *.c
gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ ls
ctest1.c ctest1.o ctest2.c ctest2.o prog.c prog.o
然后我们创建库文件:
gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ gcc -shared -Wl,-soname,libctest.so.1 -o libctest.so.1.0 *.o
gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ ls
ctest1.c ctest1.o ctest2.c ctest2.o libctest.so.1.0 prog.c prog.o
-shared选项指明生成共享目标文件,-W1(注意是小写L而不是一)指明传入链接器的参数,在此我们设定了该库的soname为libctest.so.1,-o则指明了生成的目标库文件为libctest.so.1.0(这个就是real name)。
最后创建所需的符号链接:
gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ sudo mv libctest.so.1.0 /usr/local/lib/libctest.so.1.0
gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ sudo ln -sf /usr/local/lib/libctest.so.1.0 /usr/local/lib/libctest.so.1
gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ sudo ln -sf /usr/local/lib/libctest.so.1.0 /usr/local/lib/libctest.so
创建的libctest.so就是上面所谓linker name,用于编译时-lctest选项。
创建的libctest.so.1就是soname,我们在上边说过程序在运行时需要这个名字的符号链接。
此时我们的共享库就建好了,接着我们编译程序:
gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ gcc -Wall -L/usr/local/lib prog.c -lctest -o prog
gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ ls
ctest1.c ctest1.o ctest2.c ctest2.o prog prog.c prog.o
我们编译完毕,该库并不会包含在可执行文件中,只有在执行时来会动态加载进来。我们可以通过ldd列出一个可执行程序所有的依赖,在我的系统中还找不到/usr/local/bin的路径:
gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ ldd prog
linux-gate.so.1 => (0x00a5c000)
libctest.so.1 => not found
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x00a6f000)
/lib/ld-linux.so.2 (0x00451000)
此时,运行会报找不到库的错误:
gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ ./prog
./prog: error while loading shared libraries: libctest.so.1: cannot open shared object file: No such file or directory
我们可以将所需库的路径加入到系统路径中,有三种方法可以完成:
我们使用A方法中的-f选项:
gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ vi libctest.conf
gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ sudo ldconfig -f libctest.conf
gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ ./prog
Valx=5
gnuhpc@gnuhpc-desktop:~/MyCode/lib/shared$ ldd prog
linux-gate.so.1 => (0x00f6f000)
libctest.so.1 => /usr/local/lib/libctest.so.1 (0x005d9000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x00718000)
/lib/ld-linux.so.2 (0x001e6000)
其中libctest.conf中写入路径:/usr/local/lib。程序运行正常。
4.动态加载库的使用
动态加载库是在非程序启动时动态加载进入程序的库,这对于实现插件或动态模块有很大的帮助。在Linux中,动态加载库的形式并不特殊,它使用上述两种程序库,使用提供的API在程序运行时动态加载。注意,在不同平台上动态加载库的API并不相同,所以可能会有移植问题出现。
我们可以通过nm命令先查看一下我们创建的库里面有哪些symbol(可以理解为函数方法)供我们使用:
gnuhpc@gnuhpc-desktop:~/MyCode/lib$ nm /usr/local/lib/libctest.so
00001f18 a _DYNAMIC
00001ff4 a _GLOBAL_OFFSET_TABLE_
w _Jv_RegisterClasses
00001f08 d __CTOR_END__
00001f04 d __CTOR_LIST__
00001f10 d __DTOR_END__
00001f0c d __DTOR_LIST__
000005a0 r __FRAME_END__
00001f14 d __JCR_END__
00001f14 d __JCR_LIST__
00002014 A __bss_start
w __cxa_finalize@@GLIBC_2.1.3
00000540 t __do_global_ctors_aux
00000420 t __do_global_dtors_aux
00002010 d __dso_handle
w __gmon_start__
000004d7 t __i686.get_pc_thunk.bx
00002014 A _edata
0000201c A _end
00000578 T _fini
000003a0 T _init
00002014 b completed.7021
000004dc T ctest1
000004ec T ctest2
00002018 b dtor_idx.7023
000004a0 t frame_dummy
000004fc T main
U printf@@GLIBC_2.0
这个命令对静态库和共享库都支持,第二列为symbol类型,小写字母表示符号是本地的,大写字母表示符号是全局(外部)的,几个常见的字母含义如下:T为代码段普通定义,D为已初始化数据段,B为未初始化数据段,U为未定义(用到该符号但是没有在该库中定义)。
我们创建ctest.h:
1: #ifndef CTEST_H2: #define CTEST_H3:4: #ifdef __cplusplus5: extern "C" {6: #endif7:8: void ctest1(int *);9: void ctest2(int *);10:11: #ifdef __cplusplus12: }13: #endif14:15: #endif
这里使用extern C是为了使得该库既可以用于C语言又可以用于C++。
我们动态加载库进来:progdl.c
1: #include2: #include3: #include "ctest.h"
4:5: int main(int argc, char **argv)6: {7: void *lib_handle;
8: double (*fn)(int *);9: int x;
10: char *error;
11:12: lib_handle = dlopen("/usr/local/lib/libctest.so", RTLD_LAZY);
13: if (!lib_handle)
14: {15: fprintf(stderr, "%s/n", dlerror());
16: exit(1);17: }18:19: fn = dlsym(lib_handle, "ctest1");
20: if ((error = dlerror()) != NULL)
21: {22: fprintf(stderr, "%s/n", error);
23: exit(1);24: }25:26: (*fn)(&x);27: printf("Valx=%d/n",x);
28:29: dlclose(lib_handle);30: return 0;
31: }
里面的方法解释如下:
1: dlerror(); /* clear error code */
2: s = (actual_type) dlsym(handle, symbol_being_searched_for);3: if ((err = dlerror()) != NULL) {
4: /* handle error, the symbol wasn't found */
5: } else {
6: /* symbol found, its value is in s */
7: }
我们编译该代码gcc -g -rdynamic -o progdl progdl.c -ldl,即可得到可执行文件(其中-g选项是为了gdb调试所用),其中的库为动态加载后又关闭的。我们使用gdb看一下代码:
(gdb) b main
Breakpoint 1 at 0x804878d: file progdl.c, line 12.
(gdb) r
Starting program: /home/gnuhpc/MyCode/lib/dynamic/progdlBreakpoint 1, main (argc=1, argv=0xbffff4a4) at progdl.c:12
12 lib_handle = dlopen("/usr/local/lib/libctest.so", RTLD_LAZY);
(gdb) f
#0 main (argc=1, argv=0xbffff4a4) at progdl.c:12
12 lib_handle = dlopen("/usr/local/lib/libctest.so", RTLD_LAZY);
(gdb) s
13 if (!lib_handle)
(gdb) n
19 fn = dlsym(lib_handle, "ctest1");
(gdb)
20 if ((error = dlerror()) != NULL)
(gdb)
26 (*fn)(&x);
(gdb)
27 printf("Valx=%d/n",x);
(gdb) p x
$1 = 5
(gdb) p fn
$2 = (double (*)(int *)) 0x28c4dc
可以看到fn获得了ctest1的地址。
参考文献:
http://www.yolinux.com/TUTORIALS/LibraryArchives-StaticAndDynamic.html
http://www.linuxjournal.com/article/3687
http://www.dwheeler.com/program-library/Program-Library-HOWTO/