Linux共享库基础及实例

共享库是将库函数打包成一个可执行文件,使得其在运行时可以被多个进程共享。

目标库

回顾下构建程序的一种方式:

将每个源文件编译成目标文件,再通过链接器将这些目标文件链接组成一个可执行程序。

shell 复制代码
gcc -g -c prog.c mod1.c mod2.c
gcc -g -o prog prog.o mod1.o mod2.o

库分为静态的和共享的。

静态库

静态库是一个保存所有被添加到其中的目标文件的副本的文件 。其名称形式libname.a。

可以通过ar命令来创建和维护静态库

shell 复制代码
ar options archive object-files ...
#比如创建静态库
ar r libtest.a test1.o test2.o test3.o
#比如从静态库中删除一个模块
ar d libtest.a test2.o

使用静态库有两种方法

  • gcc -g -o prog prog.o libtest.a

  • gcc -g -o prog prog.o -Lxxx -ltest, 通过-L执行搜索目录和-l指定库名称

创建共享库

静态库有一些缺陷:

  • 多个静态库如果都使用到同一个目标文件,那么存储同一个目标文件的多个副本将会浪费磁盘空间

  • 如果多个程序都使用到了这个同一个目标文件,那么每个程序会在虚拟内存中独立保存一份该目标文件的副本,提高了整体虚拟内存使用量

  • 如果这同一个目标文件修改了, 那么使用到这个目标文件的多个静态库都要重新链接

所以,需要设计出共享库机制。

共享库的目标思想是目标文件的单个副本由所有需要使用它的程序共享。

由第一个需要使用该目标文件的程序启动时,将该目标文件的副本运行加载进内存,后面的程序如果也需要使用该目标文件,直接使用已经被加载进内存的副本即可。

虽然共享库的代码是共享的,但其中的变量不是共享的,每个使用库的程序会拥有自己在库中定义的全局和静态变量的副本。

创建一个共享库

shell 复制代码
gcc -g -c -fPIC -Wall mod1.c mod2.c mod3.c
gcc -g -shared -o libfoo.so mod1.o mod2.o mod3.o

共享库的前缀是lib,后缀是.so。

-fPIC选项:编译器应该生成位置独立的代码,这样共享库代码可以放到任意一个虚拟地址处。

也可以使用一行命令来生成共享库

gcc -g -fPIC -Wall mod1.c mod2.c mod3.c -shared -o libfoo.so

使用共享库也有两种方法

  • gcc -g -o prog prog.o libfoo.so

  • gcc -g -o prog prog.o -Lxxx -lfoo, 通过-L执行搜索目录和-l指定库名称

程序启动时可以通过LD_LIBRARY_PATH来指定库的位置。

共享库别名soname

如果一个共享库有别名soname,则静态链接时会将soname嵌入到可执行文件中,而不使用真实名字。

shell 复制代码
gcc -g -c -fPIC -Wall mod1.c mod2.c mod3.c
gcc -g -shared -Wl,-soname,libbar.so -o libfoo.so mod1.o mod2.o mod3.o

通过**-Wl,-soname**参数设置共享库libfoo.so的别名为libbar.so,这样程序在链接共享库libfoo.so的时候嵌入的就是libbar.so名字,所以还需要一步,创建软连接:

shell 复制代码
ln -s libfoo.so libbar.so

soname的目的是为了提供一层间接层,使得可执行程序能够在运行时使用与链接时使用的库不同的(但兼容的)共享库。

版本和命名

真实名字命名规则

libname.so.major-id.minor-id,比如libdemo.so.1.0.1,第一个数字是主版本号 ,第二个数字是次版本号 ,第三个数字是该次版本中的修订号或补订号。

soname命名规则

libname.so.major-id,比如libdemo.so.1,只需要包含主版本号。

shell 复制代码
libname.so.1 --> libdemo.so.1.0.1

通常还会创建一个链接器名称,比如libdemo.so,没有版本号。链接器铭名称可以链接到soname也可以链接到真实名字,一般链接到soname。

shell 复制代码
libname.so --> libname.so.1
libname.so.1 --> libname.so.1.0.1

动态加载库

在linux中可以通过dlopen API组来打开使用共享库。

构建程序时必须使用-ldl选项链接libdl库

主要的函数有dlopen(), dlsym(), dlclose(), dlerror()等:

c 复制代码
#include <dlfcn.h>
void *dlopen(const char *filename, int flags); //打开共享库
void *dlsym(void *handle, const char *symbol); //查找符号
int dlclose(void *handle);    //关闭共享库
char *dlerror(void);        //错误诊断

控制符号可见性

如果共享库中的某个函数不想被导出symbol给外部访问,可以怎么做?

  • C程序中可以使用static关键词使得函数符号私有

  • gcc编译器提供了一个声明特性,与static效果类似

    c 复制代码
    void __attribute__((visibility("hidden"))) fun(void) { }

LD_PRELOAD

LD_PRELOAD环境变量的设置可以使得程序预加载指定的库,或者通过文件**/etc/ld.so.preload**来控制预加载库也是一样的。

LD_DEBUG

LD_DEBUG环境变量可以帮助监控动态链接器到底在搜索那些库,比如

LD_DEBUG=libs xxx可以监控程序xxx执行时搜索的库的路径。

代码实例

源码参考share_lib文件夹

testfun.c

c 复制代码
#include <stdio.h>
void testfun(void) {
    printf("this is testfun\n");
}

dyload.c

c 复制代码
#include <dlfcn.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    void *libHandle;
    void (*funcp)(void);
    const char *err;

    if (argc != 3 || strcmp(argv[1], "--help") == 0) {
        printf("usage: %s <lib-path> <func-name>\n", argv[0]);
        return 0;
    }

    libHandle = dlopen(argv[1], RTLD_LAZY);
    if (libHandle == NULL) {
        printf("dlopen: %s", dlerror());
        return -1;
    }

    (void) dlerror();
    *(void **)&funcp = dlsym(libHandle, argv[2]);
    err = dlerror();
    if (err) {
        printf("dlsym: %s\n", err); 
        return -1;
    }

    if (!funcp) {
        printf("%s is NULL\n", argv[2]);
        return -1;
    }else {
        printf("%s addr is: %p\n", argv[2], funcp);
    }

    (*funcp)();

    dlclose(libHandle);

    return 0;
}

Makefile

shell 复制代码
src1:=dyload.c
obj1:=dyload
src2:=testfun.c
obj2:=libtestfun.so

all:${src1} ${src2}
	gcc -g -fPIC -Wall ${src2} -shared -o ${obj2}
	gcc -g -o ${obj1} ${src1}
	./${obj1} ./${obj2} testfun
	
clean:
	@rm ${obj1} ${obj2}

执行效果,dyload执行时打印出libtestfun.so中testfun函数的地址,并执行该函数。

shell 复制代码
root@pc:share_lib# make
gcc -g -fPIC -Wall testfun.c -shared -o libtestfun.so
gcc -g -o dyload dyload.c
./dyload ./libtestfun.so testfun
testfun addr is: 0x7f694b0f8119
this is testfun

参考文献

《linux programming interface》part42-43

相关推荐
陈希1233 分钟前
Linux 防火墙配置实战:firewalld、iptables 与云安全组
linux·运维·服务器
女神下凡37 分钟前
芯参谋(95):嵌入式文件系统_开发指南
linux·服务器·arm开发·驱动开发·设计规范
且从容.4 小时前
Ubuntu安装flume
linux·ubuntu·flume
承渊政道5 小时前
Linux网络学习【Reactor反应堆模式】
linux·网络·学习·ubuntu·reactor
且从容.7 小时前
ubuntu安装flink
linux·ubuntu·flink
爱和冰阔落9 小时前
【Linux网络】TCP 服务器如何并发处理多个客户端?从 fork、僵尸进程到双重 fork
linux·运维·服务器·网络
程序员沉梦听雨9 小时前
WSL2的安装与使用
linux·wsl
微小的鱼9 小时前
Linux的渲染与显示系统链路
android·java·linux
lisanmengmeng10 小时前
alertmanger 标签 rules 打上分组标签
linux·运维·服务器
Android系统攻城狮10 小时前
Linux FFmpeg深度解析之av_write_frame调用流程与实战(三十三)
linux·运维·服务器·ffmpeg·音视频进阶·ffmpeg音视频