gcc生成动态链接库

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 

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 

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

你可能感兴趣的:(linux)