关于动态与静态链接的总结:
1)静态库由ar命令创建,动态库由链接器创建,如果一个可执行文件只链接静态库,称它为静态链接
2)静态库是目标文件的存档,而动态库本身就是一个目标文件,例如:gcc----->hello.o---->ar------>libhello.a与gcc----->hello.so
3)静态库是将部分或全部的目标代码复制到可执行文件中,部分复制是指如果链接程序从存档文件中读取目标文件,如果只用到一个或两个目标文件,则只会复制用到的目标文件
动态库不会复制任何目标代码到可执行文件中.只会记录有关的动态库信息.
4)静态链接的程序在运行期间不依赖于静态库
5)修复共享库更容易,只需修复共享库的错误,就能同时修复系统中所有使用该库的程序,如果是静态库,就要重建所有使用该库的程序.
以下有两个小测试:
测试1)演示动态链接库程序在运行时加载动态链接库的过程
================================================
实验:
/*新建程序hello.c*/
cat << EOF > hello.c
> #include <stdio.h>
> int main(){printf("Hello World\n");}
> EOF
/*新建一个空的程序empty.c*/
cat /dev/null > empty.c
/*采用共享库的方式链接*/
gcc -shared -fpic -o empty.so empty.c
/*-fpic 使输出的对象模块是按照可重定位地址方式生成的,使生成的代码是位置无关的,因为重建共享目标库需要位置无关,并且这类代码支持大的偏移。*/
/*-share 指定生成动态库*/
gcc -o hello hello.c empty.so
/*提示没有找到emtpy.so动态库*/
./hello
./hello: error while loading shared libraries: empty.so: cannot open shared object file: No such file or directory
/*指定LD_LIBRARY_PATH=.,并运行程序./hello*/
LD_LIBRARY_PATH=. ./hello
======================================================
最后可以运行了
解析:
1)程序在第一次运行失败是因为没有找到动态库文件empty.so.它是如何去找共享库的呢.
首先跟据环境变量LD_LIBRARY_PATH查找动态链接库文件empty.so,如果没有找到empty.so,它会到ld.so.cache中寻找empty.so
最后到 /lib和/usr/lib两个目录下寻找
如果没有找到empty.so,打印输出error信息,并退出
这里我们执行export LD_LIBRARAY_PATH=/etc,然后执行strace跟踪执行流程:
execve("./hello", ["./hello"], [/* 38 vars */]) = 0
uname({sys="Linux", node="testdb", ...}) = 0
brk(0) = 0x8361000
open("/etc/ld.so.preload", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/etc/tls/i686/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/etc/tls/i686/mmx", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/etc/tls/i686/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/etc/tls/i686", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/etc/tls/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/etc/tls/mmx", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/etc/tls/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/etc/tls", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/etc/i686/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/etc/i686/mmx", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/etc/i686/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/etc/i686", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/etc/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/etc/mmx", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/etc/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/etc", {st_mode=S_IFDIR|0755, st_size=8192, ...}) = 0
open("/etc/ld.so.cache", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/lib/tls/i686/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/tls/i686/mmx", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/lib/tls/i686/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/tls/i686", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/lib/tls/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/tls/mmx", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/lib/tls/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/tls", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
open("/lib/i686/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/i686/mmx", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/lib/i686/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/i686", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
open("/lib/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/mmx", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/lib/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
open("/usr/lib/tls/i686/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/tls/i686/mmx", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/usr/lib/tls/i686/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/tls/i686", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/usr/lib/tls/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/tls/mmx", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/usr/lib/tls/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/tls", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/usr/lib/i686/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/i686/mmx", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/usr/lib/i686/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/i686", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/usr/lib/mmx/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/mmx", 0xbfff9c10) = -1 ENOENT (No such file or directory)
open("/usr/lib/libempty.so", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib", {st_mode=S_IFDIR|0755, st_size=53248, ...}) = 0
writev(2, [{"./hello", 7}, {": ", 2}, {"error while loading shared libra"..., 36}, {": ", 2}, {"libempty.so", 11}, {": ", 2}, {"cannot open shared object file", 30}, {": ", 2}, {"No such file or directory", 25}, {"\n", 1}], 10./hello: error while loading shared libraries: libempty.so: cannot open shared object file: No such file or directory
) = 118
exit_group(127) = ?
结果表明:hello程序会先到/etc目标下找,
然后打开ld.so.cache,寻找它需要的共享库
open("/etc/ld.so.cache", O_RDONLY) = -1 ENOENT (No such file or directory)
最后到/lib和/usr/lib下寻找它的共享库
它的共享库包括:
ldd hello
libempty.so => not found
libc.so.6 => /lib/tls/libc.so.6 (0x00e8e000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x004bf000)
OK,这里需要说明的是采用gcc -o hello hello.c empty.so进行编译的程序,ld.so.cache是不会帮助程序找到empty.so共享库的.
因为是ldconfig只会搜索以lib开头以.so为后缀的共享库文件.如果采用empty.so为共享库文件.在ld.so.conf中指定其路径,ldconfig也不会将其写到ld.so.cache中
=========================================
实验:
export LD_LIBRARY_PATH=.
./hello
=========================================
这里执行成功后,在ld.so.cache中也看不到empty.so的身影.
=========================================
实验:
strings /etc/ld.so.cache|grep empty.so
=========================================
将empty改名为libempty.so,再重新进行编译链接
=========================================
实验:
unset LD_LIBRARY_PATH
mv empty.so libempty.so
pwd >> /etc/ld.so.conf
ldconfig
gcc -o hello hello.c libempty.so
strings /etc/ld.so.cache|grep empty.so
=========================================
更好的用法是使用-l选项.
例如:gcc -o hello hello.c -L . -lempty
其中-L 是指定搜索的目标,否则指示找不到libempty.so
指定'-l'选项和指定文件名的唯一区别是,-l 选项用'lib'和'.so'或'.a'把 library 包裹起来,而且搜索一些目录.
这里指的搜索一些目录是-L指定的目录
现在搜索的顺序是
1)变量LD_LIBRARY_PATH指定的路径
2)ld.so.cache中包含的lib库文件名及路径
3)到/lib和/usr/lib/中搜寻
那么在ld.so.cache在什么时候加载了libempty.so文件呢?
答案是用产生libempty.so文件后,再执行ldconfig,最后生成的
产生libempty.so产生的方式可以是拷贝或是gcc -shared等方式,而ldconfig命令是跟据/etc/ld.so.conf中的内容生成ld.so.cache
ldconfig会遍列ld.so.conf指定的所有路径,查找到以lib开头并以.so为后缀的ELF文件
如果libempty.so被删除,再执行ldconfig,就会将失效的记录删除掉
结论:
1)LD_LIBRARY_PATH和ld.so.conf及ld.so.cache毫无关系.
2)只有采用以lib开头以.so为后缀的共享库才会被ld.so.cache使用,用-l 选项和直接用文件的区别是-l选项会用'lib'和'.so'把 library 包裹起来,
而直接用文件则要明显的指定文件开头为lib和后缀为.so的共享库文件.如果不指定,ldconfig是不会利用这个共享库的.
3)ld.so.cache是用ldconfig产生,而ldconfig会根据ld.so.conf搜索所有的lib库
4)搜索共享库的顺序是,先找LD_LIBRARY_PATH指定的路径,然后找ld.so.cache指定的路径,最后到/lib和/usr/lib下找
5)搜索/lib/和/usr/lib中的共享库文件,并不是只要在这两个目录下就可以,它是搜索固定的子目录,比如/usr/lib/i686/mmx/等等,最后再搜索/lib/和/usr/lib
6)产生新的hello文件后,可以移动 libempty.so共享库文件,但前提是只能把它移动到hello程序可以找到的地方.
也就是上面三个搜索目标(LD_LIBRARY_PATH,ld.so.cache,lib和/usr/lib)
7)ldconfig只会搜索以lib开头以.so为后缀的共享库文件,而不会搜索以lib开头,以.a为后缀的静态库文件.
测试2:
用下面三种链接方式进行编译分别进行测试:
第一种把它作为两个目标链接起来,这个方法最简单.
第二种方法是把目标方文件以静态库的方式进行链接.
第三种方法是以共享目标文件的方式进行链接.
1)新建一个名为piggy.c的共享目标文件,它什么都不做,但会占用内存空间,[0x10000]声明了1MB的全局缓冲区
实验:
=========================================
cat << EOF > piggy.c
>char bank[0x10000];
>EOF
cat << EOF > hello.c
> #include <unistd.h>
> int main(){pause();}
> EOF
=========================================
1.1编译链接:
=========================================
实验:
gcc -o hello hello.c piggy.c
=========================================
1.2)查看内存的占用情况:
=========================================
实验:
size ./hello
text data bss dec hex filename
1136 520 65568 67224 10698 ./hello
=========================================
1.3)用nm命令显示已存在的bank数据,显示占用了1MB的内存空间
=========================================
实验:
nm -S hello|grep bank
080495a0 00100000 B bank
=========================================
2)用静态链接库的方法,进行链接
2.1)生成.o的目标文件
=========================================
实验:
gcc -c piggy.c
=========================================
2.2)用ar命令创建目标文件的存档libpig.a
=========================================
实验:
ar clq libpig.a piggy.o
=========================================
2.3)链接静态库,生成可执行文件hello
=========================================
实验:
gcc -o hello hello.c -L ./ -lpig --static
=========================================
2.4)查看内存的占用情况:
=========================================
实验:
size ./hello
text data bss dec hex filename
656930 3488 12568 672986 a44da ./hello
=========================================
2.5)用nm命令显示是否包括bank数组
=========================================
实验:
nm -S hello|grep bank
=========================================
用静态库链接的可执行程序,可执行文件的大小有了明显的增加.但是没有包括数组bank.
3)用动态链接库的方法,进行链接
3.1)生成动态函数库 piggy.so
=========================================
实验:
gcc -shared -o piggy.so piggy.c
=========================================
3.2)链接动态库,生成可执行文件hello
=========================================
实验:
gcc -o hello hello.c ./piggy.so
=========================================
3.3)查看内存的占用情况:
=========================================
实验:
size ./hello
text data bss dec hex filename
1411 536 16 1963 7ab ./hello
=========================================
3.4)用nm命令显示是否包括bank数组
=========================================
实验:
nm -S hello|grep bank
=========================================
用动态库链接的可执行程序,比静态库的可执行程序有了明显的减小.并且没有看到数组bank
3.5)最后用pmap命令来运行以查看进程内存的映射:
=========================================
实验:
./hello &
jobs -x pmap -q %1
4281: ./hello
00111000 1176K r-x-- /lib/tls/libc-2.3.4.so
00237000 8K r-x-- /lib/tls/libc-2.3.4.so
00239000 8K rwx-- /lib/tls/libc-2.3.4.so
0023b000 8K rwx-- [ anon ]
009e7000 4K r-x-- /tmp/piggy.so (deleted)
009e8000 4K rwx-- /tmp/piggy.so (deleted)
009e9000 1024K rwx-- [ anon ]
00be9000 88K r-x-- /lib/ld-2.3.4.so
00bff000 4K r-x-- /lib/ld-2.3.4.so
00c00000 4K rwx-- /lib/ld-2.3.4.so
08048000 4K r-x-- /tmp/hello (deleted)
08049000 4K rw--- /tmp/hello (deleted)
b7ef1000 8K rw--- [ anon ]
bff78000 544K rw--- [ stack ]
ffffe000 4K ----- [ anon ]
==========================================
输出结果表明有1MB的匿名映射,是给bank数组分配的存储空间.
结论:
第一种方式会占用更多的内存.例如piggy.c中定义1MB的数组,在运行时它会占用1MB的内存空间,但由于采用的是动态链接方式,二进制文件会比轻小.
第二种方式则不会包括piggy.c中定义的1MB数组,链接器能够确定没有引用piggy.o,所以不会把它链接进来,但由于采用的是静态链接,二进制文件会比较大.
第三种方式程序的bss最小,不会加载1MB的数组,但在运行期间同样会占用1MB的内存空间.
这里有一点要说明.编译器默认会优先选择动态链接,如果只想链接静态目标,则必须在gcc/g++中写明-static选项.