xredis
因为项目中使用到了 xredis (C++开发的redis客户端,是对hiredis的C++封装),在 makefile 中发现使用到了 -Wl,-soname 这个语法,之前没怎么了解过,特此记录
makefile 节选如下:
XREDIS_MAJOR=1
XREDIS_MINOR=10.1
# Fallback to gcc when $CC is not in $PATH.
CC:=g++
OPTIMIZATION?=-O3
WARNINGS=-Wall -W -Wwrite-strings
DEBUG?= -g -ggdb
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG) $(ARCH)
REAL_LDFLAGS=$(LDFLAGS) $(ARCH)
DYLIBSUFFIX=so
STLIBSUFFIX=a
DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(XREDIS_MAJOR).$(XREDIS_MINOR)
DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(XREDIS_MAJOR)
DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
STLIB_MAKE_CMD=ar rcs $(STLIBNAME)
尝试 make 一下
[root@localhost xredis-1.10.1]# make
g++ -O3 -fPIC -Wall -W -Wwrite-strings -g -ggdb -c src/xRedisClient.cpp -o src/xRedisClient.o
g++ -O3 -fPIC -Wall -W -Wwrite-strings -g -ggdb -c src/xRedisClient_keys.cpp -o src/xRedisClient_keys.o
g++ -O3 -fPIC -Wall -W -Wwrite-strings -g -ggdb -c src/xRedisClient_sets.cpp -o src/xRedisClient_sets.o
g++ -O3 -fPIC -Wall -W -Wwrite-strings -g -ggdb -c src/xRedisClient_strings.cpp -o src/xRedisClient_strings.o
g++ -O3 -fPIC -Wall -W -Wwrite-strings -g -ggdb -c src/xRedisClient_connection.cpp -o src/xRedisClient_connection.o
g++ -O3 -fPIC -Wall -W -Wwrite-strings -g -ggdb -c src/xRedisClient_hashs.cpp -o src/xRedisClient_hashs.o
g++ -O3 -fPIC -Wall -W -Wwrite-strings -g -ggdb -c src/xRedisClient_lists.cpp -o src/xRedisClient_lists.o
g++ -O3 -fPIC -Wall -W -Wwrite-strings -g -ggdb -c src/xRedisClient_sortedsets.cpp -o src/xRedisClient_sortedsets.o
g++ -O3 -fPIC -Wall -W -Wwrite-strings -g -ggdb -c src/xRedisPool.cpp -o src/xRedisPool.o
g++ -O3 -fPIC -Wall -W -Wwrite-strings -g -ggdb -c src/xRedisFunc.cpp -o src/xRedisFunc.o
g++ -shared -Wl,-soname,libxredis.so.1.10.1 -o libxredis.so src/xRedisClient.o src/xRedisClient_keys.o src/xRedisClient_sets.o src/xRedisClient_strings.o src/xRedisClient_connection.o src/xRedisClient_hashs.o src/xRedisClient_lists.o src/xRedisClient_sortedsets.o src/xRedisPool.o src/xRedisFunc.o
ar rcs libxredis.a src/xRedisClient.o src/xRedisClient_keys.o src/xRedisClient_sets.o src/xRedisClient_strings.o src/xRedisClient_connection.o src/xRedisClient_hashs.o src/xRedisClient_lists.o src/xRedisClient_sortedsets.o src/xRedisPool.o src/xRedisFunc.o
关键信息为
g++ -shared -Wl,-soname,libxredis.so.1.10.1 -o libxredis.so
这里编译生成的对象是 libxredis.so 这个动态库,其 soname 为 libxredis.so.1.10.1
可以看看编译后生成的产物:
# 有一个 libxredis.a 静态库和一个 libxredis.so 动态库
[root@localhost xredis-1.10.1]# ll libxredis.*
-rw-r--r--. 1 root root 3405976 Oct 5 17:40 libxredis.a
-rwxr-xr-x. 1 root root 1319008 Oct 5 17:40 libxredis.so
可以使用 readelf 命令查看动态库的 soname
# 为了简洁些,使用了 grep 作过滤
[root@localhost xredis-1.10.1]# readelf -d libxredis.so | grep soname
0x000000000000000e (SONAME) Library soname: [libxredis.so.1.10.1]
soname
soname 是 Short for shared object name 的缩写,直译就是共享库(动态库)的缩写。
-Wl,-soname -Wl 告诉编译器将后面的参数传递到连接器。而 -soname 指定了共享库的 soname。
那 soname 是怎么怎么产生作用的呢?
使用 ldd 命令或者 readelf 都可以查看应用程序依赖的动态库
# 为了简洁些,使用了 grep 作过滤
[root@localhost lib]# ldd /usr/xxx/bin/yyy | grep xredis
libxredis.so.1.10.1 => /usr/xxx/lib/libxredis.so.1.10.1 (0x00007f2c4ea68000)
# 在 ldd 命令打印的结果中,“=>”左边的表示该程序需要连接的共享库之 so 名称,右边表示由 Linux 的共享库系统找到的对应的共享库在文件系统中的具体位置。
# 为了简洁些,使用了 grep 作过滤
[root@localhost lib]# readelf -d /usr/xxx/bin/yyy | grep xredis
0x0000000000000001 (NEEDED) Shared library: [libxredis.so.1.10.1]
可以看到 yyy 这个模块是依赖于 libxredis.so.1.10.1 这个动态库,但是还记得我们编译生成的产物么?我们编译生成的可是 libxredis.so 这个库啊~
# 将编译生成的 libxredis.so 放到 /home/wuxt/lib 下
[root@localhost lib]# pwd
/home/wuxt/lib
[root@localhost lib]# ls
libxredis.so
# 将 /home/wuxt/lib 加入动态库查找路径
[root@localhost lib]# cat /etc/ld.so.conf
include ld.so.conf.d/*.conf
/home/wuxt/lib
执行 ldconfig 命令,将动态库加入缓存中
[root@localhost lib]# ldconfig -v
/home/wuxt/lib:
libxredis.so.1.10.1 -> libxredis.so (changed)
我们发现这里做了个软链接
[root@localhost lib]# ll
total 1292
-rwxr-xr-x. 1 root root 1319008 Oct 5 18:00 libxredis.so
lrwxrwxrwx. 1 root root 12 Oct 5 19:35 libxredis.so.1.10.1 -> libxredis.so
将动态库的 soname 指向动态库本身,这样当 yyy 模块需要依赖 libxredis.so.1.10 时,就会找到具体的 libxredis.so 这个库。
问题
但我认为这个 xredis 的 makefile 编写有问题,回到 xredis 的编译
g++ -shared -Wl,-soname,libxredis.so.1.10.1 -o libxredis.so
soname 应该是 libxredis.so.1 或者是 libxredis.so,生成的对象是带完整版本的 libxredis.so.1.10.1,这样当 xredis 升级至 1.10.2 时,还是由 libxredis.so.1 或者是 libxredis.so 指向 libxredis.so.1.10.2。
举例几个常用的库的 soname
cJSON
看下 makefile 编写
# 节选 cjson 的 makefile
LIBVERSION = 1.7.15
CJSON_SOVERSION = 1
UTILS_SOVERSION = 1
CJSON_SO_LDFLAG=-Wl,-soname=$(CJSON_LIBNAME).so.$(CJSON_SOVERSION)
UTILS_SO_LDFLAG=-Wl,-soname=$(UTILS_LIBNAME).so.$(UTILS_SOVERSION)
编译 cJSON
[root@localhost cJSON]# ll libcjson.so*
lrwxrwxrwx. 1 root root 13 Oct 5 21:10 libcjson.so -> libcjson.so.1
lrwxrwxrwx. 1 root root 18 Oct 5 21:10 libcjson.so.1 -> libcjson.so.1.7.15
-rwxr-xr-x. 1 root root 46731 Oct 5 21:10 libcjson.so.1.7.15
查看 libcjson.so.1.7.15 的 soname
[root@localhost cJSON]# readelf -d libcjson.so.1.7.15 | grep soname
0x000000000000000e (SONAME) Library soname: [libcjson.so.1]
系统自带的库:
libz.zo
[root@localhost lib]# ll libz.so*
lrwxrwxrwx. 1 root root 13 Jan 19 2021 libz.so -> libz.so.1.2.7
lrwxrwxrwx. 1 root root 13 Jan 19 2021 libz.so.1 -> libz.so.1.2.7
-rwxr-xr-x. 1 root root 109093 Sep 27 2020 libz.so.1.2.7
[root@localhost lib]#
[root@localhost lib]# readelf -d libz.so.1.2.7 | grep soname
0x000000000000000e (SONAME) Library soname: [libz.so.1]
libc.so 不过这个有点奇怪,libc-2.17.so 的 soname 是 libc.so.6?
[root@localhost lib64]# ll libc.so*
-rw-r--r--. 1 root root 253 Jan 19 2015 libc.so
lrwxrwxrwx. 1 root root 12 May 19 2017 libc.so.6 -> libc-2.17.so
[root@localhost lib64]#
[root@localhost lib64]# readelf -d libc-2.17.so | grep soname
0x000000000000000e (SONAME) Library soname: [libc.so.6]
[root@localhost lib64]#
总结
Linux 系统的这种动态库管理方式值得我们在实际项目的动态库管理中使用,既保证了动态库的升级,又能得到方便地使用。
Linux 的动态库的命名格式是 libbar.so.x.y.z,最后一个 z 版本的变动一定是兼容的。y 版本升级一般向前兼容。所以这个 y 和 z 不能写死。x 版本变动一般是不兼容升级。所以使用 soname 是最为合理的。