Makefile文件如下:
OBJS_DIR=./objs
CCFLAGS= -shared -Wall -fPIC -Wl,-soname,libcudart.so.4 -g
LDFLAGS=
ifeq ($(hook),1)
CCFLAGS+=-DRUN_REAL_LIBCUDA
LDFLAGS+= -ldl
endif
RTAPI_SRCS = runtime_api.c driver_intf.c
RTAPI_OBJS = $(addprefix ${OBJS_DIR}/, $(RTAPI_SRCS:.c=.o))
$(OBJS_DIR)/%.o: %.c
gcc $(CCFLAGS) -I./ -c $< -o $@
.PHONY:
all:mk-dirs rtapi
rtapi: mk-dirs $(RTAPI_OBJS)
gcc $(CCFLAGS) -o libcudart.so.4.0.0 $(RTAPI_OBJS) $(LDFLAGS)
ln -sf libcudart.so.4.0.0 libcudart.so.4
mk-dirs:
mkdir -p $(OBJS_DIR)
clean:
rm -rf $(OBJS_DIR)
rm -rf libcudart.so*
这里主要解析CCFlags变量的含义:
-shared 生成共享库
-Wall 输出编译过程中的警告
-fPIC 生成与地址无关的二进制代码,这样的动态库可以被加载到所有进程都可以访问的一块内存中,而不用每一个进程都在各自的内存中保留动态库的copy,造成内存浪费,但是不要妄想通过在动态库里定义全局变量实现进程间通信,因为,linux采用写时拷贝(copy-on-write技术),在某一进程试图改变动态库里的全局变量时,linux会将动态库的全局变量拷贝至该进程的私有内存空间(如果只读不写,则不拷贝),因此无法通过此路径实现进程间通信,参考:
http://www.cnblogs.com/lovevivi/archive/2013/01/10/2854584.html
https://blog.csdn.net/zxh821112/article/details/8969541这篇文章中有介绍不适用-fPIC选项的后果,将产生装载时重定向的代码(修改代码中所有地址),使只加载一次动态库,所有进程共享这一内存成为不可能。
-Wl,-soname -Wl指明接下来的参数传递给链接器,-soname制定共享库的soname(简单共享名,short for shared object name),soname是库的唯一标示,而不是库的文件名,但一般soname和库的文件名相关(库文件名包含详细版本信息,soname只包含主版本信息),这样同一主版本下,此版本更新方便升级,不同版本的库虽然文件名不同但是soname一定要一致,如果主版本更新,则一般不兼容上一版本,所以使用此库的可执行程序要重新改写源码并编译。
上面库的文件名为libcudart.so.4.0.0,库的soname为libcudart.so.4与库的软链接相同
-g 用于gdb调试。
LDFLAGS变量的含义:
-ldl 链接一个名字为libdl.a的库,这个库里有几个函数dlopen、dlsym、dlclose、dlerror,可以用于显示的打开动态库,并且获取其中的函数地址。
RTAPI_OBJS = $(addprefix ${OBJS_DIR}/, $(RTAPI_SRCS:.c=.o))
这句话的效果是 首先将RTAPI_SRCS变量中所有的.c结尾的字符串替换成.o结尾的字符串, 然后调用makefile的addprefix函数为.o添加前缀./objs/
https://blog.csdn.net/zhoudengqing/article/details/41775353
下面我们来说一下生成动态库和使用动态库的注意事项:
-Wl,-soname,xxx 选项用来指定共享库的唯一标识:简单共享名,不指定时默认为:
1.若-o指定的要生成的库的文件名为标准的lib**.so.x.x.x这种格式,则soname是去掉版本信息的部分,即lib**.so(若没有版本信息,则库文件名和soname同名)
2.若-o指定的为非标准格式,则gcc会取库文件名的主体部分(点号前面的部分,没有点号则取全部文件名)加上lib前缀和.so后缀作为soname。
soname是主程序运行时加载动态库的唯一标识,所以要想程序成功加载动态库,则需要创建soname同名的指向动态库文件的软连接,或者直接将动态库的名称改为soname(不推荐)
主程序编译时的注意事项:
编译时使用动态库要使用-l指令,要想编译成功,需要建立一个软连接,名称为-l后的字符串加上lib前缀和.so后缀。
下面举例:
shared.c文件生成动态库文件libshared.so.1.0.0,指定soname为libshared.so.1
gcc -shared -fpic -Wall -Wl,-soname,libshared.so.1 -o libshared.so.1.0.0 shared.c
main.c生成exe文件
先创建软连接:
ln -s libshared.so.1.0.0 libshared.so
ln -s libshared.so.1.0.0 libshared.so
再编译,没有名字libshared.so的软连接,编译将出错。
gcc -lshared -L. -o exe main.c
运行exe文件时要先建立另一个软链接,此软链接要和soname必须同名:
ln -s libshared.so.1.0.0 libshared.so.1
另外运行时要将软链接所在路径添加到环境变量LD_LIBRARY_PATH中去,才能正确运行
export LD_LIBRARY_PATH=$(pwd):LD_LIBRARY_PATH
总结:
一个动态库有三种文件名称
1.libxxx.so (一般为软连接),作用是编译程序的时候,用到的动态库必须是这种名字,gcc 中-l选项,例如 -lshared 意思是链接文件名为libshared.so的动态库
2.libxxx.so.x (一般为软连接,只包含主版本号),作用是,编译好的程序,交给用户的去运行的时候,一定要保证用户的电脑上有名称为libxxx.so.x动态库,并且要保证此名称的动态库的唯一标识符也是libxxx.so.x,例如用户电脑上存在 libshared.so.1的软连接,指向真实文件libshared.so.1.0.0,我们要保证真实文件的唯一标识符为 libshared.so.1,即编译生成真实文件libshared.so.1.0.0的时候,gcc的-Wl,-soname选项后面指定的参数也要是libshared.so.1,否则,即使程序运行时加载动态库时找到了此名称的动态库,也不能成功加载
3.libxxx.so.x.xx(真实的动态库文件,包含主版本号和次版本号),开发动态库的公司生成的动态库的文件名称
4. 从上述可以知道:
a.动态库的主版本号 不仅体现在动态库的文件名里面,而且使用 gcc的-Wl,-soname选项,以二进制数据的形式,保存在动态库中,使用动态库的可执行程序运行的时候,先用文件名libxxx.so.x找到动态库,然后检查动态库的唯一标识符是否是libxxx.so.x,是的话才能成功加载(待验证)
b.动态库的次版本号 只体现在动态库的文件名中,所以主版本号相同,次版本号不同的动态库,可以相互兼容。
关于上述总结中第2条,第4条验证结果:
有点意外,如果主版本号不同的两个库,只要保证两个库的名称相同,两个库中的接口相同,即两个库中二进制数据保存的主版本号不同,但是两个库的文件名改成一样的,两个库中函数接口相同,程序运行的时候仍然可以成功加载动态库,即上述总结中的这一点是错误地。验证程序如下:
验证程序的目录结构:
sallenkey@ubuntu:~/workspace$ tree ./
./
├── hello
│ ├── hello.c
│ └── Makefile
├── main.c
└── Makefile
其中 ./hello目录存放编译动态库libhello.so的源文件, ./目录存放调用libhello.so的源文件
./hello/hello.c:
#include <stdio.h>
void hello(void){
printf("hello world\r\n");
}
./hello/Makefile
NO_VER_LINK_OUTPUT := libhello.so
LINK_OUTPUT = $(addsuffix $(VER), $(addsuffix ., $(NO_VER_LINK_OUTPUT)))
OBJS_DIR = ./objs
CCFLAGS = -shared -Wall -fPIC -Wl,-soname,$(LINK_OUTPUT)
HELLO_SRCS = hello.c
HELLO_OBJS = $(addprefix ${OBJS_DIR}/, $(HELLO_SRCS:.c=.o))
SECOND_VER_NU = .0.0
OUTPUT = $(addsuffix ${SECOND_VER_NU}, $(LINK_OUTPUT))
$(OBJS_DIR)/%.o:%.c
gcc $(CCFLAGS) -I./ -c $< -o $@
.PHONY:
all:$(OUTPUT)
$(OUTPUT):$(OBJS_DIR) $(HELLO_OBJS)
gcc $(CCFLAGS) -o $@ $(HELLO_OBJS)
#ln -sf $@ $(LINK_OUTPUT)
ln -sf $@ libhello.so.1
ln -sf $@ $(NO_VER_LINK_OUTPUT)
$(OBJS_DIR):
mkdir -p $@
clean:
rm -rf $(OBJS_DIR) $(OUTPUT) $(NO_VER_LINK_OUTPUT) libhello.so.1 #$(LINK_OUTPUT)
./main.c
extern void hello();
#include <stdio.h>
int main(){
hello();
return 0;
}
./Makefile
TEST_SRCS:=$(wildcard *.c)
OBJS_DIRS:=./objs
LDFLAGS:=-lhello -L./hello
TEST_OBJS:=$(addprefix $(OBJS_DIRS)/, $(TEST_SRCS:.c=.o))
OUTPUT:=$(basename $(TEST_SRCS))
all:$(OBJS_DIRS) $(OUTPUT)
$(OUTPUT):$(TEST_OBJS)
gcc -o $@ $^ $(LDFLAGS) -Wl,-rpath=./hello
$(OBJS_DIRS)/%.o:%.c
gcc -o $@ -c $^
$(OBJS_DIRS):
mkdir -p $@
clean:
rm -rf $(OBJS_DIRS) $(OUTPUT)
验证方式:
1.切换到./hello目录,执行命令: make VER=1,生成主版本为1的libhello.so
2.切换到./目录,执行命令: make,生成可执行文件main, 运行./main,发现成功输出hello world
3.切换到./hello目录,执行命令 make clean VER=1,清除主版本号为1的libhello.so
4.执行命令 make VER=2,生成主版本号为2的libhello.so
5.切换到./目录,运行./main,发现仍然可以成功输出hello world