动态连结 VS 静态联结
在 Linux 中执行档我们可以编程成静态联结以及动态连结以下我们举一个简短的程序作为例子:
#include
int main()
{
printf( test);
}
若我们执行 :
[root@hlchou /root]# gcc testc o test
所产生出来的执行档 test预设为使用动态函式库所以我们可以用以下的指令 :
[root@hlchou /root]# ldd test
libcso => /lib/libcso (x)
/lib/ldlinuxso => /lib/ldlinuxso (x)
来得知目前该执行档共用了哪些动态函式库以我们所举的 test 执行档来说共用了两个动态函式库分别为 libcso 与 ldlinuxso我们还可以透过下面的 file 指令来得知该执行档的相关属性如下
[root@hlchou /root]# file test
test: ELF bit LSB executable Intel version dynamically
linked (use s shared libs) not stripped
not stripped 表示这个执行档还没有透过 strip 指令来把执行时用不到的符号以及相关除错的资讯删除举个例子来说目前这个test 执行档大小约为 bytes
[root@hlchou /root]# ls l test
rwxrxrx root root Oct : test
经过strip后则变为 bytes
[root@hlchou /root]# strip test
[root@hlchou /root]# ls l test
rwxrxrx root root Oct : test
不过读者必须注意到一点经过 strip 过的执行档就无法透过其它的除错软件从里面取得函式在编程时所附的相关资讯这些资讯对我们在除错软件时可以提供不少的帮助各位在应用上请自行注意
相对于编程出来使用动态函式库的执行档 test我们也可以做出静态联结的执行档 test
[root@hlchou /root]# gcc static testc o test
透过指令 ldd我们可以确定执行档 test 并没有使用到动态函式库
[root@hlchou /root]# ldd test
not a dynamic executable
再透过指令 file可以注意到 test 目前为 statically linked且亦尚未经过 strip
[root@hlchou /root]# file test
test: ELF bit LSB executable Intel version statically
linked not stripped
相信大伙都会好奇使用静态联结且又没有经过 strip 删去不必要的符号的执行档的大小会是多少透过 ls l来看我们发现大小变成 bytes 比起静态联结的执行档大了相当多
[root@hlchou /root]# ls l test
rwxrxrx root root Oct : test
若再经过 strip则档案大小变为 bytes
[root@hlchou /root]# strip test
[root@hlchou /root]# ls l test
rwxrxrx root root Oct : test
与使用动态函式库的执行档 test 比较起来大了约 倍 (/)因此整体来说在使用的环境中使用动态函式库并且经过 strip 处理的话可以让整体的空间较为精简许多执行档都会用到同一组的函式库像 libc 中的函式是每个执行档都会使用到的若是使用动态函式库则可以尽量减少同样的函式库内容重复存在系统中进而达到节省空间的目的
笔者一年前曾写过一个可以用来删去动态函式库中不必要函式的工具针对这个只用到了 printf 的程序来产生新的 libcso 的话我们可以得到一个精简过的 libcso 大小约为 bytes
[root@hlchoua lib]# ls l libcso*
rwxrxrx root root Nov : libcso
lrwxrwxrwx root root Nov : libcso > libcso
与静态联结的执行档大小 bytes 比较起来若是在这个环境中使用了动态函式库的话成本约为 + = bytes不过这是只有一个执行档的情况下使用动态函式库的环境会小输给使用静态联结的环境在一个基本的 Linux 环境中如果大量的使用动态函式库的话像是有 个以上的执行档的话那用动态函式库的成本就大大的降低了像如果两个执行档都只用到了 printf那静态联结的成本为 * = bytes而使用动态函式库的成本为 * + = bytes两者相差约一倍
很明显的我们可以看到动态函式库在 Linux 环境中所发挥的妙用它大幅的降低了整体环境的持有成本提高了环境空间的利用率
ldlinuxso 在 RedHat 中我们可以在 /lib 或是 /usr/lib 目录底下找到许多系统上所安装的动态函式库在文章的这个部分笔者将把整个函式库大略的架构作一个说明
其实 Linux 跟 Windows 一样提供了一组很基本的动态函式库在 Windows 上面我们知道 kerneldll 提供了其它动态函式库基本的函式呼叫而在 Linux 上面则透过 ldlinuxso 提供了其它动态函式库基本的函式在笔者电脑的 RedHat 上ldlinuxso 是透过 link 到 ldso(这部分需视各人所使用的 glibc 版本不同而定)
rwxrxrx root root Jan : ldso
lrwxrwxrwx root root Jan : ldlinuxso > ldso
ldlinuxso 是属于 Glibc (GNU C Library) 套件的一部分只要是使用 Glibc 动态函式库的环境就可以见到 ldlinuxso 的蹤影
接下来我们透过指令 ldd 来验证出各个函式库间的阶层关系首先如下图我们执行了 ldd lsldd pwd 与 ldd vi可以看出各个执行档呼叫了哪些动态函式库像执行档 ls 呼叫了 /lib/libcso (x)与 /lib/ldlinuxso (x)而括号内的数字为该函式库载入记忆体的位置在本文的稍后会介绍到函式库载入时的细节到时读者会有更深入的了解
其实我们不难发现在 Linux 上使用动态函式库的执行档几乎都会去呼叫 libcso 与 ldlinuxso 这两个动态函式库笔者过去修改 Glibc 的套件时也了解到在 Linux 中函式库的关系ldlinuxso 算是最底层的动态函式库它本身为静态联结主要的工作是提供基本的函式给其他的函式库而我们最常会呼叫的 libcso 则是以 ldlinuxso 为基础的一个架构完成的动态函式库它几乎负责了所有我们常用的标准 C 函式库像是我们在 Linux 下写的 Socket 程序其中的connect()bind()send() 之类的函式都是由 libcso 所提供的
也因此libcso 的大小也是相当可观的在 RedHat 中经过 strip 后大小约为 bytes
[root@hlchoua /root]# ldd /bin/ls
libcso => /lib/libcso (x)
/lib/ldlinuxso => /lib/ldlinuxso (x)
[root@hlchoua /root]# ldd /bin/pwd
libcso => /lib/libcso (x)
/lib/ldlinuxso => /lib/ldlinuxso (x)
[root@hlchoua /root]# ldd /bin/vi
libtermcapso => /lib/libtermcapso (x)
libcso => /lib/libcso (xb)
/lib/ldlinuxso => /lib/ldlinuxso (x)
如下我们透过 ldd 验证 vi 所用到的动态函式库 /lib/libtermcapso它本身是呼叫了 libcso 的函式所组成的
[root@hlchoua /root]# ldd /lib/libtermcapso
libcso => /lib/libcso (x)
/lib/ldlinuxso => /lib/ldlinuxso (x)
接下来我们依序测试了 /lib/libcso 与 /lib/ldlinuxso
[root@hlchoua /root]# ldd /lib/libcso
/lib/ldlinuxso => /lib/ldlinuxso (x)
[root@hlchoua /root]# ldd /lib/ldlinuxso
statically linked
我们可以整理以上的结论画成如下的一个架构图
在这个图中我们可以清楚的明白 ldlinuxso 负责了最基础的函式而 libcso 再根据这些基本的函式架构了完整的 C 函式库供其它的动态函式库或是应用程序来呼叫
透过笔者所写的一个 ELF 工具程序(注二)我们也可以清楚的看到 libcso呼叫了 ldlinuxso 哪些函式
[root@hlchoua /root]# /Ielf /lib/libcso|more
========================================================
open_target_file:/lib/libcso
==>ldlinuxso
__register_frame_table
cfsetispeed
xdr_int_t
utmpname
_dl_global_scope_alloc
__strcasestr
hdestroy_r
rename
__iswctype_l
__sigaddset
xdr_callmsg
pthread_setcancelstate
xdr_union
__wcstoul_internal
setttyent
strrchr
__sysv_signal ┅(more)