《Linux应用编程 —— 动态库与静态库》

1.动态库

1.1 生成一个动态库:

#include <stdlib.h>
#include <stdio.h>

void dynamic_lib_call(void)
{
    printf("dynamic lib call\n");
}

编译:

gcc -Wall -shared dlib.c -o libdlib.so

  其中多了一个 -shared 选项,该选项用于指示 gcc 生成动态库。

1.2 使用动态库

#include <stdlib.h>
#include <stdio.h>
extern void dynamic_lib_call(void);
int main(void)
{
    dynamic_lib_call();
    return 0;
}

编译:

 gcc -Wall main.c-o test_dlib -L ./-ldlib

  其中-l用于指示生成文件依赖的库,本例依赖于 libdlib.so ,因此为 -ldlib。

  -L 与 -I 类似, -L 用于指示 gcc 在哪个目录中查找依赖的库文件。

运行结果:

./test_dlib: error while loading shared libraries: libdlib.so: cannot open shared object file: No such file or directory

  为什么会报告出错,找不到这个 libdlib.so 呢?前面明明已经使用 -L 指定了库文件在当前目录中,并且这个库文件也确实存在于当前目录中啊。

  使用 ldd 来查看 test_lib 的依赖库:

ldd test_dlib
linux-gate.so.1 => (0xb7785000)
libdlib.so => not found
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75ce000)
/lib/ld-linux.so.2 (0xb7786000

  ldd本身不是一个程序,而仅是一个shell脚本:ldd可以列出一个程序所需要得动态链接库(so)。

  原因在于 -L 只是在 gcc 编译的过程中指示库的位置,而在程序运行的时候,动态库的加载路径默认为 /lib 和 /usr/lib 。在 Linux 环境下,还可以通过 /etc/ld.so.conf 配置文件和环境变量LD_LIBRARY_PATH 指示额外的动态库路径。

 

1.3 手工加载动态库

#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
int main()
{
    void *dlib = dlopen("./libdlib.so", RTLD_NOW);
    if (!dlib) {
    printf("dlopen failed\n");
    return -1;
    }

    void (*dfunc) (void) = dlsym(dlib, "dynamic_lib_call");
    if (!dfunc) {
    printf("dlsym failed\n");
    return -1;
   }

   dfunc();
   dlclose(dlib);
   return 0;
} 

编译:

gcc -Wall main_mlib.c -ldl -o test_mlib 

  需要使用 -ldl 选项来指定依赖的动态库 libdl.so。

  libdl.so里面有4个函数:dlopen//打开一个动态库、dlsym//在打开的动态库里找一个函数、dlclose//关闭动态库、dlerror//返回错误。

  (34条消息) dlopen()实现三方库的动态加载_yolo_yyh的博客-CSDN博客_dlopen加载动态库
  使用dlopen加载动态库 - 0xzhang - 博客园 (cnblogs.com)

1.4 系统默认加载动态库和手动加载动态库的优缺点

    系统默认加载,会在程序起来后,将所有链接的动态库全部加载到内存中。而手动加载动态库,就会在用到这个动态库的时候再加载到内存中。 

 

1.5 依赖库

(34条消息) linux 动态库多重依赖,Linux库多重依赖 - osc_xl828kzf的个人空间 - OSCHINA - 中文开源技术交流社区..._黄冠恒的博客-CSDN博客

(34条消息) linux 内核fpic,gcc编译参数-fPIC的一些问题_裴梵海的博客-CSDN博客

 

2.静态库

 

3 优缺点

    1.静态库在链接阶段,会被直接链接进最终的二进制文件中,因此最终生成的二进制文件体积会比较大,但是可以不再依赖于库文件。而动态库并不是被链接到文件中的,只是保存了依赖关系,因此最终生成的二进制文件体积较小,但是在运行阶段需要加载动态库。
 2.其中动态库的一个重要优点就是,可执行程序并不包含动态库中的任何指令,而是在运行时加载动态库并完成调用。这就给我们提供了升级动态库的机会。只要保证接口不变,使用新版本的动态库替换原来的动态库,就完成了动态库的升级。更新完库文件以后启动的可执行程序都会使用新的动态库。

  这样的更新方法只能够影响更新以后启动的程序,对于正在运行的程序则无法产生效果,因为程序在运行时,旧的动态库文件已经加载到内存中了。我们只能更新位于磁盘上的动态库的物理文件,而不能影响已经位于内存中的库了。

    3.如何不重启程序就可以直接升级动态库?

 使用动态加载动态库的方式:

typedef int (*test_func) (void *);

struct dlib_manager 
{
  void *dlib_handle; // 保存动态库的句柄
  test_func service_func;
  test_func service_func2;
} g_dlib_manager;

/* g_dlib_manager 作为动态库接口的全局变量*/
struct dlib_manager *g_dlib_manager;

/* 更新动态库接口 */
struct dlib_manager *new_manager = malloc(sizeof(*new_manager));
new_manager->dlib_handle = dlopen("libupgrade.so", RTLD_LAZY);
new_manager->service_func = dlsym(g_dlib_handle, "service_call");
new_manager->service_func2 = dlsym(g_dlib_handle, "service_call2");


/* 在多核环境下,使用内存屏障,以保证在交换 new_manager 和 g_dlib_manager 时, new_manager 已经完成了赋值 */
wmb();

/* 交换新指针与当前正在使用的接口指针因为目前,无论是新指针还是旧指针都是有效的接口,所以并不会对业务产生影响*/
swap(new_manager, g_dlib_manager);

/* 交换完成以后,新的请求都会交由新接口来处理由于当前旧接口仍然可能正在使用中,所以要使用推迟释放或是等待正在服务的接口完成*/
delay_free(new_manager);

struct dlib_manager *local_dlib_manager = g_dlib_manager;
local_dlib_manager->service_func1(data);
local_dlib_manager->service_func2(data);
  •   使用一个结构体来管理动态库的接口
  •   利用 dlopen 、 dlsym 等来加载动态库,更新接口。重新申请新的内存,来保存新的动态库接口
  •   在调用服务接口时,要利用局部变量保存服务接口(之所以这里使用局部变量来进行接口调用,是为了避免在调用了一部分接口后, g_dlib_manager 才发生更新,从而导致前后的服务接口属于不同的动态库,造成不可预料的问题。通过临时变量来保存服务接口,能确保所有接口的一致性。)
  •   释放旧接口的关键在于,要保证没有旧接口正在被使用。根据自己的业务,找到一个时间点, 在这个时间点上,所有的线程(准确地说是请求流程)都已经服务过一次。这时,新来的请求就会使用新的接口,于是我们也就可以安全地释放旧接口了

扩展:
  在程序运行情况下,如果直接替换动态库。

  (34条消息) so文件动态替换方法及Linux动态库的用法_致守的博客-CSDN博客_替换so文件

 








 


 

posted @ 2022-09-15 11:13  一个不知道干嘛的小萌新  阅读(181)  评论(0编辑  收藏  举报