# 正文
继续整理从这个页面学到的东西:https://tldp.org/HOWTO/Program-Library-HOWTO。
之前的在这里:
找到一个linux静态库动态库的好资料.0
找到一个linux静态库动态库的好资料.1
找到一个linux静态库动态库的好资料.2
这一篇继续看这个: https://tldp.org/HOWTO/Program-Library-HOWTO/shared-libraries.html
这个页面主要讲共享库,原文是shared libraries。就是linux里常见的后缀为.so的文件,其中so是缩写了“shared object”[e1]。共享库是在程序启动时就加载。只要共享库是被正确得安装了,那么后续启动的所有程序都会自动使用新的共享库。共享库的设计十分精巧,这些设计精巧到支持你这么整:
- 如果新的共享库与之前的不兼容,就可以让一些程序仍然使用旧库的前提下,让新程序使用新的库。
- 在执行某个程序时使用指定的、“重写”过的库或“重写”过的函数[e2]。
- 在程序正在使用着已有共享库的过程中做上边2件事情。
要做到上边说的事情,得遵守若干关于共享库的惯用方式。要搞清楚这些共享库的惯用方式,首先要搞清楚共享库的各种名字,比如so名、真实名。还得搞清楚共享库该保存在哪里。这的“so名”和“真实名”是直接翻译自原文的“soname”和“real name”,我觉得这里使用原词更方便一些,所以下文就是用“soname”和“real name”了。先上个图:
上图红色圆圈里的,就是soname,就说libselinux.so.1吧,“lib”是个前缀,“.so”是扩展名,“.1”是个版本号。一般最底层的C库名字并不以“lib”作为前缀,好比上图的linux-vdso.so.1。原文还有一个概念叫“fully-qualified soname”,就是带路径的soname。一个“fully-qualified soname”通常是个软连接,比如下图的/lib64/libpcap.so.2:
上边图上的/lib64/libcap.so.2.48就是real name了,其中的“.2”的2是次版本号,“.48”的48是release号;release号是可选的,不一定得有。“.2.48”这种次版本号+release号的约定,是为了让人看到即知晓具体的库版本。
还有一个名字,就是编译器调用共享库时使用的名字,即以“.so”结束的,比如/lib64/libcap.so。原文作者管这个叫“linker name”,咱也跟着叫吧。
原文作者认为要盘共享库,关键就是搞清楚这些个名字。还说:
列出程序需要的共享库时,只列出so名即可(上上个图,带圆圈的那个);
然后说,当创建一个共享库时,只是创建了一个带有版本信息的文件;
当需要安装一个新版本的共享库时,就要把它放到指定的目录,再执行ldconfig(8)。
ldconfig会检查目录里有哪些文件,创建指向文件的符号链接、同时设置缓存文件/etc/ld.so.cache
ldconfig不会去管linker name(链接器使用的名字)的事情。
通常,linker name是在安装共享库时手动设置:即创建一个指向最新版本的共享库的软连接。
原文作者说他问过H.J. Lu[e3]这个问题:为何ldconfig不设计为自动设置linker name?得到的回答基本是这个意思:用户也有可能想让开发环境使用旧的库,所以ldconfig不做任何假定,而是让用户必须单独进行一下更新软连接的操作。
总结一下:/usr/lib/libreadline.so.3是个“fully-qualified soname”,它通常是个由ldconfig创建的软连接,指向一个真实名类似 /usr/lib/libreadline.so.3.0的共享库文件;还有一个链接器使用的名字linker name,/usr/lib/libreadline.so,它是指向/usr/lib/libreadline.so.3的软连接,通常是手动设置的。
到这里应该去试一下的,但是要试一下就得用ldconfig,所以还得再说一下ldconfig。我们要先知道有一个东东叫程序加载器,原词program loader,它是操作系统的提供的,负责在启动程序时将程序文件和库文件放到内存里,并为程序的执行做一些准备性工作[e4]。有的程序需要很多的共享库,那么在启动时查找若干目录并加载它们,这就很低效,所以就引入了缓存机制。
ldconfig(8)就是用来建立这些缓存的,如此,程序启动时程序加载器就直接使用缓存里的共享库,效率就比较高了[e5]。
现在我们去看ldconfig(8)的man page。上边说ldconfig会搜索一些目录,创建一些必要的符号连接,这些符号连接指向最新的共享库(shared linraries);“一些目录”包含命令行传入的目录、/etc/ld.so.conf里配置的目录、以及/lib、/usr/lib(/lib64、/usr/lib64)。这些缓存由run-time linker(即ld.so或ld-linux.so)使用。
ldconfig创建的缓存都是保存在/etc/ld.so.cache里。/etc/ld.so.cache是一个二进制文件,保存了动态库的列表;ldconfig -p可以打印这个列表:
好了,我们现在编一个动态库,试试上边说的这些。其实《找到一个linux静态库动态库的好资料.1.docx》里面的共享库示例就说到一个,刚才看了下,其实很适合贴到这里来。不过我打算再练习一下,弄得稍微不一样一点儿,所以再试一次,一边试一边记录,写到哪里算哪里。
前边说ldconfig会根据/etc/ld.so.conf配置的目录创建缓存,而/etc/ld.so.conf里面只有一行:include ld.so.conf.d/*.conf:
所以我们在/etc/ld.so.conf.d/目录下新加一个配置文件test.conf,里面配上本次测试用的目录/root/nice-teammate:
现在我们编一个real name为libhello.so.0.0的共享库,命令贴在这里了:
gcc -fPIC -shared -Wl,-soname,libhello.so.0 -o libhello.so.0.0 libhello.c -lc
然后,我们使用ldconfig将其加入缓存:
我们发现ldconfig创建了一个软链接名曰libhello.so.0。
看起来这个libhello.so.0与gcc的选项-Wl,-soname,libhello.so.0有关,所以去掉这个选项再试一把:
可以发现ldconfig列出的soname为libhello.so.0.0,也不创建什么软连接了(因为也不需要)。
现在我们还是按指定soname为libhello.so.0的方式:
然后手动创建一个软连接:ln -sf libhello.so.0 libhello.so
创建一个main.c代码如下:
然后编译可执行程序main,使用命令:gcc -o main main.c -L ./ -lhello
哦,可能是不手动创建给编译器用的软链接libhello.so,那gcc的-lhello就不方便了。
看起来已经是一片相当凑合的文章了,欧。
# ENDNOTES
e1
e2
这里的“重写”即override,类似java语法里子类重写父类方法的意思,原文是:override specific libraries or even specific functions in a library when executing a particular program.
e3
e4
在unix系统中,程序加载器除了去调exec系统调用(原文是:The loader is the handler for the system call execve().),任务还包括:
- 校验权限,内存需求等
- 把保存着指令的可执行文件放入内存
- 把命令行的参数放到内存
- 初始化寄存器
- 跳到程序的入口
参考:https://en.wikipedia.org/wiki/Loader_(computing)
看下了《UNIX环境高级编程》的8.3节,基本也是这个意思。
e5
这一个小自然段就是https://tldp.org/HOWTO/Program-Library-HOWTO/shared-libraries.html上的,原文是:Searching all of these directories at program start-up would be grossly inefficient, so a caching arrangement is actually used. The program ldconfig(8) by default reads in the file /etc/ld.so.conf, sets up the appropriate symbolic links in the dynamic link directories (so they'll follow the standard conventions), and then writes a cache to /etc/ld.so.cache that's then used by other programs. This greatly speeds up access to libraries. The implication is that ldconfig must be run whenever a DLL is added, when a DLL is removed, or when the set of DLL directories changes; running ldconfig is often one of the steps performed by package managers when installing a library. On start-up, then, the dynamic loader actually uses the file /etc/ld.so.cache and then loads the libraries it needs.