Linux 支持两种类型的库,每一种库都有各自的优缺点。
静态库包含在编译时静态绑定到一个程序的函数。
动态库则不同,它是在加载应用程序时被加载的,而且它与应用程序是在运行时绑定的。
静态库较适宜于较小的应用程序,因为它们只需要最小限度的函数。
而对于需要多个库的应用程序来说,则适合使用共享库,因为它们可以减少应用程序对内存(包括运行时中的磁盘占用和内存占用)的占用。
这是因为多个应用程序可以同时使用一个共享库;因此,每次只需要在内存上复制一个库。要是静态库的话,每一个运行的程序都要有一份库的副本。
GNU/Linux 提供两种处理共享库的方法(每种方法都源于 Sun Solaris)。可以动态地将程序和共享库链接并让 Linux 在执行时加载库(如果它已经在内存中了,则无需再加载)。
另外一种方法是使用一个称为动态加载的过程,这样程序可以有选择地调用库中的函数。
使用动态加载过程,程序可以先加载一个特定的库(已加载则不必),然后调用该库中的某一特定函数。这是构建支持插件的应用程序的一个普遍的方法。
Linux下动态库文件的文件名形如 libxxx.so,其中so是 Shared Object 的缩写,即可以共享的目标文件。
realname的一般格式为lib$(name).so.$(major).$(minor).$(revision),$(name)是动态库的名字,$(major).$(minor).$(revision)分别表示主版本号,子版本号和修正版本号。
生成命令如下:
$ g++ -g test1.cpp -shared -fPIC -o test1.so.1.1.1 -std=c++11
realname=test1.so.1.1.1
命令就是
$ g++ -g xxx.cpp -shared -fPIC -o $(realname) dependence flags
-Wl选项告诉编译器将后面的参数传递给链接器。-soname则指定了动态库的soname。
-Wl 表示后面的参数也就是-soname,libhello.so.1直接传给连接器ld进行处理。
如:
* 其一般格式为lib$(name).so.$(major).$(minor),即lib+动态库名+.so+主版本号,soname会被写入库文件中。
* 同时指定realname和soname
$ g++ test1.cpp -shared -fPIC -Wl,-soname,libtest1.so.1.1 -o libtest1.so.1.1.1 -std=c++11
用于使用hello.c生成一个名为libhello.so.1.2的共享库,且该共享库的soname为libhello.so.1。
soname的关键功能是它提供了兼容性的标准,当要升级系统中的一个库时,并且新库的soname和老库的soname一样,用旧库链接生成的程序使用新库依然能正常运行。这个特性使得在Linux下,升级使得共享库的程序和定位错误变得十分容易。
在Linux中,应用程序通过使用soname,来指定所希望库的版本,库作者可以通过保留或改变soname来声明,哪些版本是兼容的,这使得程序员摆脱了共享库版本冲突问题的困扰。
可以通过readelf -d来查看每个动态库的SONAME。
readelf -d libtest1.so.1.1.1
在程序执行期间,程序会查找拥有 soname名字的文件,而不是库的文件名,换句话说,soname是库的区分标志。
目的主要是允许系统中多个版本的库文件共存,习惯上在命名库文件的时候通常与soname相同,如:
libxxxx.so.major.minor,其中,xxxx是库的名字,major是主版本号,minor 是次版本号。
一般对于不影响兼容性的升级,只修改次版本号。如果修改了主版本号,可能就不能兼容以前的版本了。
所以,可以使用这种方式构建动态库:gcc -fPIC -shared -Wl,soname,libhello.so.major -o libhello.so.major.minor hello.c
简单的来说,soname指定了库的名字,而不去管生成的是什么名字的库,在做连接是将这个soname指定的名字加入执行文件中,而程序运行是也是去加载soname指定的库文件名。
所以,如果程序连接了新升级的库,只需要将这个新库拷贝到目录下面后,对其以soname做一个符号链接就能调用。
如果库升级了,但是程序依旧使用旧的链接库,那么只需对这个将旧库名字软链接到新升级的库中去即可。
对于一个新安装到系统中的库,有时需要执行ldconfig命令,来自动完成动态库的更新。
Linux系统的这种动态库管理方式值得我们在实际项目的动态库管理中使用,既保证了动态库的升级,又能得到方便地使用。