linux 共享库编译介绍

  • 基础使用
gcc -fPIC -c a.c
gcc -fPIC -c b.c
gcc -shared -Wl -o libmyab.so ao b.o
// -fPIC 表示位置无关代码,动态库是运行时加载,PIC表示库放到内存任何地址上运行都行,用的是相对地址,而不是绝对地址
// -c 只编译,生成.o文件,不进行链接
  • 高阶使用
gcc -share -Wl,-soname, libmyab.so.1 -o libmyab.so.1.0.1 a.o b.o
gcc -share -Wl,-soname, your_soname -o library_name(real_name) file_list library_list
  • 基本概念

按照共享库的命名惯例,每个共享库有三个文件名:real name, soname和linker name,这三个name 有什么区别呢?

一般真正的库文件的名字叫real name, 包含了完整的共享库版本号
soname 是一个符号链接的名字,只包含共享库的主版本号,主版本号一致即可保证库函数的接口一致,因此应用程序的.dynamic段只记录共享库的soname,只要soname一致,这个共享库就可以用。表示库函数接口没有大的变化。如libmyab.so.1和libmyab.so.2是两个主版本号不同的libmyab,有些应用依赖于libmyab.so.1,有些应用依赖于libmyab.so.2,但是对于依赖libmyab.so.1的应用程序来说,真正的库文件不管是libmyab.so.1.10还是libmyab.so.1.11都可以用。所以使用共享库可以很方便地升级库文件而不需要重新编译应用程序,这是静态库所没有的优点。注意libc的版本编号有一点特殊,libc-2.8.90.so的主版本号是6而不是2或是2.8

linker name 仅在编译链接时使用,gcc的-L选项应该指定linker name所在的目录。有的linker name是库文件的一个符号链接,有的linker name是一段链接脚本。

centos /lib64下

举例说:上面libxml2.so 这个就是linker name,linkxml2.so.2,这个是soname, 标识了主版本号,libxml2.so.2.9.2这个是real name, 给出了完整的版本号

共享库加载:

在所有基于GNUglibc的系统中,在启动一个ELF二进制执行程序时,一个特殊的程序“程序装载器”会被自动装载并运行。在linux中,这个程序装载器就是/lib/ld-linux.so.X(X是版本号)。它会查找并装载应用程序所依赖的所有共享库。被搜索的目录保存在/etc/ld.so.conf文件中。如果程序的每次启动,都要去搜索一番,性能上会有所消耗。linux系统已经考虑到这一点。对共享库采用缓存管理,ldconfig就是实现这一功能的工具,其缺省读取/etc/ld.so.conf文件,对所有的共享库按照一定规范建立符号连接,然后将信息写入/etc/ld.so.cache。/etc/ld.so.cache的存在大大地加快了程序的启动速度

下面我们用一个例子【编译一个加减乘除的运算so】来详细说明下共享库(so->shared object)的编译过程:

//我的目录结构:
|-- add.c
|-- cmath.h
|-- dive.c
|-- main.c
|-- mul.c
|-- sub.c

//我的程序代码
//add.c
int add(int a, int b) {
	return a+b;
}

//sub.c
int sub(int a, int b) {
	return a-b;
}

//mul.c
int mul(int a, int b) {
	return a*b;
}

//dive.c
int dive(int a, int b) {
	return a/b;
}

//cmath.h
#ifndef CMATH_H_
#define CMATH_H_
int add(int a, int b);
int sub(int a, int b);
int mul(int a, int b);
int dive(int a, int b);
#endif

//main.c
#include <stdio.h>
#include "cmath.h"

int main(void) {
	printf("%d", add(3,4));
	return 0;
}
  • 编译产生.o文件
gcc -fPIC -c *.c

//执行后的目录结构,会产生四个.0文件
|-- add.c
|-- add.o
|-- cmath.h
|-- dive.c
|-- dive.o
|-- main.c
|-- main.o
|-- mul.c
|-- mul.o
|-- sub.c
`-- sub.o
  • 将.o编译生成共享库
gcc -shared -Wl,-soname,libmym.so.1 -o libmysm.so.1.10 *.o
//执行完的目录结构,会产生一个libmym.so.1.10
|-- add.c
|-- add.o
|-- app
|-- cmath.h
|-- dive.c
|-- dive.o
|-- libmysm.so.1.10
|-- main.c
|-- main.o
|-- mul.c
|-- mul.o
|-- sub.c
`-- sub.o
  • 将main,c 和 libmym.so.1.10一起编译,生成app程序
gcc main.c libmysm.so.1.10 -o app

  • 我们来执行下./app
./app: error while loading shared libraries: libmym.so.1: cannot open shared object file: No such file or directory

说找不到so,我们接着ldd查看下app所依赖的so

[root@VM_16_5_centos src]# ldd app 
	linux-vdso.so.1 =>  (0x00007ffccebeb000)
	libmym.so.1 => not found
	libc.so.6 => /usr/lib64/libc.so.6 (0x00007f9d5bcf7000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f9d5c0c4000)

这个时候我们来聊到一个文件 /etc/ld.so.conf

include ld.so.conf.d/*.conf
/usr/local/lib64
/usr/local/lib
/usr/lib
/usr/lib64
/root/ccode/src

ld.so.conf 存放的是一个so搜索目录的文件列表,可以简单理解成类似path环境,这里面存放的so的环境path

由于是自定义的so. 我们的libmym.so.1.10存放在/root/ccode/src下,所以我们把这个路径加到ld.so.conf里

然后用下列命令更新下共享库路径:

ldconfig -v

再次运行下app程序

[root@VM_16_5_centos src]# ldd app 
	linux-vdso.so.1 =>  (0x00007ffcae9e1000)
	libmym.so.1 => /root/ccode/src/libmym.so.1 (0x00007f8390595000)
	libc.so.6 => /usr/lib64/libc.so.6 (0x00007f83901c8000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f8390797000)
[root@VM_16_5_centos src]# ./app 
7[root@VM_16_5_centos src]#