如何设计动态链接库

动态库的创建

  Linux 系统上经常见到的 *.so 文件其实就是动态库,将一个 *.c 文件编译成动态库很简单:

1
2
$ gcc -g -c -fPIC -Wall demo.c
$ gcc -g -shared -o libdemo.so demo.o

  -fPIC选项用来指导编译器生成位置无关代码(position-independent code),在Linux/x86-64系统上必须指定这个选项。

动态库规范

  动态库的升级通常也分为小版本的升级和主版本的升级,通常来说主版本的升级是不兼容的,而小版本的升级则是兼容的。动态库通常有一种规范的命名方式,为的是能够清晰地了解不同版本之间的关系:

1
2
3
libdemo.so.1.0.1 # 主版本 1,小版本 0.1
libdemo.so.1.0.2 # 主版本 1,小版本 0.2
libdemo.so.2.0.1 # 主版本 2,小版本 0.1

  在编译动态库的时候,通常会为动态库创建一个 soname,作为动态库的别名,例如,这里我们给动态库libdemo.so.1.0.1创建了一个 soname 叫做libdemo.1

1
2
$ gcc -g -c -fPIC -Wall demo.c
$ gcc -g -shared -Wl,-soname,libdemo.so.1 -o libdemo.so.1.0.1 demo.o

  在生产环境下,用户自己创建的动态库一般不能随便放置(防止找不到或者不小心被删除了),所以推荐放在系统目录/usr/local/lib/下面:

1
$ sudo mv libdemo.so.1.0.1 /usr/local/lib/

  通常来说,系统会在/etc/ld.so.cache这个文件里面缓存系统的动态库列表。如果用户自己添加了动态库,那么还需要用ldconfig命令去更新系统的动态库缓存列表,同时这个命令会自动创建一个与 soname 同名的符号链接,指向对应的动态库:

1
2
3
4
5
6
$ sudo ldconfig -v # 更新系统动态库缓存列表
/usr/local/lib:
libdemo.so.1 -> libdemo.so.1.0.1 (changed)
$ ls -l /usr/local/lib/
lrwxrwxrwx 1 root root 16 Mar 21 22:54 libdemo.so.1 -> libdemo.so.1.0.1
-rwxr-xr-x 1 root root 8776 Mar 21 22:54 libdemo.so.1.0.1

  通过这些步骤就顺利安装好了动态库了。那么还有一个问题,在编译程序时,怎么让 linker 找到动态库的位置呢?最简单的方法可以这样做:

1
$ gcc -g -Wall -o main main.c /usr/local/lib/libdemo.so.1.0.1

  在链接的过程中,如果动态库拥有 soname,那么 linker 会将这个 soname 嵌入到可执行文件中,如果动态库没有 soname,嵌入的则是动态库的真名。我们可以用readelf命令看到可执行文件main的 header 中是否包含动态库的信息:

1
2
readelf -d main | grep libdemo
0x0000000000000001 (NEEDED) Shared library: [libdemo.so.1]

  可以看到,可执行文件main中只记录动态库的 soname。


  上面的编译命令需要用户知道动态库的具体位置,然而用户在编译程序时,才不会想知道动态库的位置呢!解决这个问题很简单,我们可以创建一个符号链接,让它指向libdemo.so.1就好了:

1
$ sudo ln -s /usr/local/lib/libdemo.so.1 /usr/local/lib/libdemo.so

  这样编译程序的时候就简单多了:

1
$ gcc -g -Wall -o main main.c -ldemo

参考资料