一 创建并编译共享库
动态链接库一般以lib开头,形如libmymodule.so.1.0.0.
后面跟的三个版本号,从左到右的含义为:
(1) 大版本号,当接口变得和之前不兼容,则新增一个大版本号。
(2) 一般增加了接口,不过旧的接口不变,则新增此版本号。
(3) 接口不做任何变化,只是实现做了修改,则新增此版本号。
假设我们的库只包含 module.cpp, 则用命令
- g++ -fPIC -Wall -c module.cpp
生成module.o,再用命令
- g++ -shared -Wl,-soname,libmymodule.so.1 -o libmymodule.so.1.0.0 module.o
生成libmymodule.so.1.0.0共享库。这里的libmymodule.so.1.0.0也叫做共享库的real name.
- -fPIC是使生成的目标文件“位置无关(Position Independent Code)”从而可以被多个程序共享。
- -shared 指定产生共享库。
- -Wl,-soname,libmymodule.so.1 指定共享库的soname为libmymodule.so.1,若不指定,则无soname. soname的作用后面会提到。可以用objdump -p libmymodule.so.1.0.0 | grep SONAME 查看soname.
一般soname带且只带大版本号,比如这里的libmymodule.so.1, 是因为共享库的相互兼容的不同版本,都具有相同的soname(在升级的时候,需要用soname来对应)。而如果大版本号变了,意味着接口变得不兼容了,也就没有必要让这个版本和之前的共享库兼容了。这个时候,启用一个新的soname是更好的做法。
二 编译主程序
因为gcc中,用-l参数指定的库文件必须满足格式lib*.so 所以我们需要建立软连接libmymodule.so, 并令其指向libmymodule.so.1.0.0
- ln -s libmymodule.so.1.0.0 libmymodule.so
这里的libmymodule.so就是所谓的"link name"。
假设我们的主程序(相对于库来说),代码在main.cpp里,则用命令
- g++ -o main main.cpp -L. -lmymodule
生产可执行程序main。注意link name只在编译的时候需要,主程序会记住根据这个文件最终所指的共享库的soname,用来在运行的时候,查找加载动态库。如果共享库没有指定soname, 那么主程序会记住这个'link name",也就是lib*.so这样形式的名字,做为soname, 在运行的时候,用这个名字来查找加载动态库。
- -L指定搜索库的文件夹,
- -l 指定所依赖的库。注意这里需要去掉前面的“lib”和后面的“.so”
三 运行期
这个时候,运行./main会出现以下错误:
./main: error while loading shared libraries: libmymodule.so.1: cannot open shared object file: No such file or directory
使用ldd main可以查看所依赖的动态库是否被满足,执行“ldd main":
发现有一行libmymodule.so.1 => not found,说明在运行期,加载器没有找到对应的共享库文件。
加载器会在以下地方查找main程序中记住的所需要的库的soname:
(1) /etc/ld.so.cache 这是一个cache,存放soname到共享库文件的soname link (一个文件名为soname的软连接) 的映射(key->value值对)。
(2) /usr/lib 和 /lib
(3) 环境变量LD_LIBRARY_PATH指定的文件夹。
可以用ldconfig 命令,解决这个问题。ldconfig主要做2件事情:
一是扫描/lib和/usr/lib和/etc/ld.so.conf里指定文件夹,对里面的共享库建立soname link (ldconfig会根据文件名里的版本号,自动找到最新的共享库文件,并把soname link指向这个最新的共享库文件)
二是更新/etc/ld.so.cache,建立soname到soname link的映射。
加载器如果在ld.so.cache中查到所需的soname,则会依次找到 soname-->soname link-->实际的共享库文件。如果找不到,则会在/usr/lib和/lib中查找文件名为soname的文件,作为共享库文件加载。注意,在这里,加载器不会去找/etc/ld.so.conf里指定的文件夹,只会找/usr/lib和/lib。所以如果在/etc/ld.so.conf里指定的文件夹,增加了soname link,必须要运行ldconfig来更新ld.so.cache。
ldconfig -n /path/to/dir 命令可以指定某文件夹,不过只会做第一件事,也就是建立soname link-->实际的共享库文件。不会更新ld.so.cache 所以不需要root权限。
ldconfig -p 命令可以打印ld.so.conf里已经有的键值对。
好了,了解了以上知识,我们可以用以下方法解决找不到共享库的问题:
(a) 将libmymodule.so.1.0.0拷到/lib或者/usr/lib里,或者/etc/ld.so.conf指定的文件夹,然后执行sudo ldconfig. 于是ldconfig会自动在/lib或/usr/lib或/etc/ld.so.conf指定的文件夹里生成soname link (文件名为libmymodule.so.1) 指向libmymodule.so.1.0.0,然后在ld.so.cache中增加libmymodule.so.1到/lib /libmymodule.so.1或/usr/lib/libmymodule.so.1的映射(前者是个名字,后者是个软连接文件)
(b) 使用LD_LIBRARY_PATH=/PATH/TO/SO ./main来运行程序。前提是在/PATH/TO/SO中建立一个名字为soname的软连接,使其指向实际的共享文件。
(c) 在/lib或/usr/lib中,手动建立软连接 libmymodule.so.1 令其指向实际的共享库文件 libmymodule.so.1.0.0。 因为前面提到的,加载器在ld.so.cache中找不到要找的键为soname的项,会在/lib或/usr/lib中找名字为soname的文件。这方法虽然可以用,不过感觉比较粗暴。不推荐。
四 升级共享库
如果是用上述(a)方法使用共享库的,只需要将新的共享库,比如 libmymodule.so.1.0.1拷到原来的目录(/lib或/usr/lib或者/etc/ld.so.conf指定的文件夹),然后运行 ldconfig即可。ldconfig会更新soname link, 使其指向最新的共享库文件libmymodule.so.1.0.1,ld.so.cache不需要更新。
如果是用上述(b)(c)方法是用共享库的,需要更新相应的软连接,使其指向最新的共享库文件。
五 动态加载共享库
在主程序中动态加载共享库时,如果指定的是绝对路径("/"开头的),则加载该绝对路径指向的共享库文件。如果不是绝对路径,而是一个文件名,则将这个文件名当作soname,然后依照上述的方法,查找加载相应的共享库文件。
六 注意点
如果在共享库中,要用到主程序的变量或者方法,要在编译主程序时,加上-rdynamic参数,使得所有名字在共享库的空间中可见。