前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >动态库学习[通俗易懂]

动态库学习[通俗易懂]

作者头像
全栈程序员站长
发布2022-11-19 12:49:18
8370
发布2022-11-19 12:49:18
举报
文章被收录于专栏:全栈程序员必看

大家好,又见面了,我是你们的朋友全栈君。 总结一:动态库

前言

我们知道程序编译链接经常使用动态,同时我们可能还知道,动态库时程序运行时加载的。但是动态库到底有什么作用,如何生成、如何加载等,我们却很少关注。接下来,我给大家做一个简单的介绍。

1.1 动态库和静态库的区别

静态库特点(linux):

  • 命名上是以 *.o 结尾
  • 静态库在链接阶段直接就加入到可执行的文件中了,在执行过程中无需该静态库
  • 相对于动态库生成的文件,使用静态库生成的文件连接生成的可执行文件较大

动态库的特点(linux)

  • 命名上是以 *.so
  • 目标文件在链接阶段只是指明链接的那个动态库,动态库与目标文件保持独立。在执行过程中需要该动态库
  • 使用动态库生成的目标文件较小

对于工程中比较共通的源码文件,比如多个进程使用同一个模块的源码,我们最好将其制作成动态库,以节省系统空间。同时如果动态库出现bug,只需要重新生成一个动态库并将以前的替换即可。不需要重新编译其他模块。

1.2 内存中的动态库

在讲到动态库的装载时我们需要懂一定的背景知识,首先虚拟内存和物理内存,其次还有地址映射,这些知识就不在本文多加讲解,网上资料很多。我们动态库在整个内存空间是有一份,而每个进程都有自己的虚拟空间,虚拟空间会使用匿名映射(mmap使用MAP_PRIVATE方式进行映射),使自己的进程与动态库进行关联。本进程只会保留访问动态库时的一些数据。好了,打的方向就说这么多,这几句话随便抽出一个词来都够将好久。我们只需要对大方向有认识即可。以下就是映射表

在这里插入图片描述
在这里插入图片描述

此外我们说一些额外的小知识,在linux系统中 /proc 目录下有很多进程文件。在执行的进程都会创建一个文件。随便进入一个文件 /etc/1892。查看maps文件(sudo cat maps),这里面对应了动态库对应虚拟空间的位置,值得注意的是,这个文件最后一列有多少个[stack]就有多少个线程

代码语言:javascript
复制
00008000-005ba000 r-xp 00000000 b3:01 11822      /usr/local/bin/ecTelematicsApp
005c1000-005df000 rw-p 005b1000 b3:01 11822      /usr/local/bin/ecTelematicsApp
005df000-00d7f000 rw-p 00000000 00:00 0          [heap]
a8c00000-a8cff000 rw-p 00000000 00:00 0 
a8cff000-a8d00000 ---p 00000000 00:00 0 
a8e00000-a8e01000 ---p 00000000 00:00 0 
a8e01000-a9600000 rw-p 00000000 00:00 0          [stack:1369]
a9600000-a9700000 rw-p 00000000 00:00 0 
a9700000-a9721000 rw-p 00000000 00:00 0 
a9721000-a9800000 ---p 00000000 00:00 0 
.......
b5c5d000-b645e000 rw-p 00000000 00:00 0          [stack:1961]
b645e000-b6470000 r-xp 00000000 b3:01 709        /lib/libresolv-2.19.so
b6470000-b6477000 ---p 00012000 b3:01 709        /lib/libresolv-2.19.so
b6477000-b6478000 r--p 00011000 b3:01 709        /lib/libresolv-2.19.so
b6478000-b6479000 rw-p 00012000 b3:01 709        /lib/libresolv-2.19.so
b6479000-b647b000 rw-p 00000000 00:00 0 
.......
bea5d000-bea7e000 rw-p 00000000 00:00 0          [stack]
ffff0000-ffff1000 r-xp 00000000 00:00 0          [vectors]

同时我们也可以直接使用readelf -m [bin文件名],来读取该bin文件的链接信息。

1.3 动态库的加载

关于动态库我当初的直接认识是,程序运行到调用该动态库的接口时,会产生缺页,从而去磁盘加载动态库到内存,然后再执行。但事实并非如此。动态库也分为隐式链接和显示链接,不同的方式其载入内存的时间也是大相径庭。

显示链接

隐式链接

语法

不需要申明动态库先关的头文件,在调用时需要加载动态库的名称

只需要添加相应的头文件即可

加载

执行到相应代码段时加载动态库(可以控制库的加载和卸载)

由系统控制加载时间,一般在程序启动时就加载

由以上两点我们可以看出显示链接如果控制得当,对内存的消耗将下降许多,大型项目应该使用显示链接。但是缺点也有,就是如果库不存在,隐式链接可以再一开始就发现库不存在,而显示链接会被偶然触发。

1.4 动态库的制作

首先我们准备一个源文件 print.c

代码语言:javascript
复制
#include<stdio.h>

void printInter()
{
    printf("%s\n", __FUNCTION__);
}

void printExtern()
{
    printInter();
    printf("%s\n", __FUNCTION__);
}

输入指令

代码语言:javascript
复制
gcc -fPIC -c print.c -o print.o
gcc -shared print.o -o libprint.so -lstdc++

由此我们生成了 libprint.so动态库。

然后我们再创建libcurl.so 的接口头文件print.h

代码语言:javascript
复制
#ifndef __PRINTT_H_
#define __PRINT_H__

void printExtern();

#endif

1.5 动态库的隐式链接

我们创建main.c 去使用库

代码语言:javascript
复制
#include<stdio.h>
#include<unistd.h>
#include "print.h"

int main()
{
	printf("waite 5 seconds\n");
	sleep(5);
	printf("%s\n", __FUNCTION__);
	printExtern();

	return 0;
}

输入指令

代码语言:javascript
复制
gcc -fPIC -c main.c -o main.o
gcc -o target main.c -L./ -lprint -I./ -lstdc++

生成target

执行 ./target, 输出如下

代码语言:javascript
复制
waite 5 seconds
main
printInter
printExtern

我们可以通过 readelf -d target,可以查看target链接了libprint.so

代码语言:javascript
复制
root@user:/home/cjj/jianxiongs/so2# readelf -d target 

Dynamic section at offset 0xf0c contains 25 entries:
  标记        类型                         名称/值
 0x00000001 (NEEDED)                     共享库:[libprint.so]
 0x00000001 (NEEDED)                     共享库:[libc.so.6]
 0x0000000c (INIT)                       0x80483fc
 0x0000000d (FINI)                       0x8048604
 0x00000019 (INIT_ARRAY)                 0x8049f00
 0x0000001b (INIT_ARRAYSZ)               4 (bytes)
 0x0000001a (FINI_ARRAY)                 0x8049f04
 0x0000001c (FINI_ARRAYSZ)               4 (bytes)
 0x6ffffef5 (GNU_HASH)                   0x80481ac
 0x00000005 (STRTAB)                     0x80482c8
 0x00000006 (SYMTAB)                     0x80481e8
 0x0000000a (STRSZ)                      208 (bytes)
 0x0000000b (SYMENT)                     16 (bytes)
 0x00000015 (DEBUG)                      0x0
 0x00000003 (PLTGOT)                     0x804a000
 0x00000002 (PLTRELSZ)                   32 (bytes)
 0x00000014 (PLTREL)                     REL
 0x00000017 (JMPREL)                     0x80483dc
 0x00000011 (REL)                        0x80483d4
 0x00000012 (RELSZ)                      8 (bytes)
 0x00000013 (RELENT)                     8 (bytes)
 0x6ffffffe (VERNEED)                    0x80483b4
 0x6fffffff (VERNEEDNUM)                 1
 0x6ffffff0 (VERSYM)                     0x8048398
 0x00000000 (NULL)                       0x0

接下来我们删除 libprint.so。 然后再运行target,这时我们发现出现如下错误

代码语言:javascript
复制
./target: error while loading shared libraries: libprint.so: cannot open shared object file: No such file or directory

这说明程序在一开始就要加载libprint.so的库,这就是动态库的隐式链接

1.6 动态库的显式链接

这时我们需要更改main.c的内容,修改最终如下

代码语言:javascript
复制
#include<stdio.h>
#include<unistd.h>
#include<dlfcn.h>
#include<stdlib.h>
//#include "print.h"  删除引用头文件

void test()
{
    char *libname = NULL;
    char *err = NULL;

    //open the lib
    void *handle = dlopen("./libprint.so", RTLD_NOW);
    if(!handle)
    {
        err = dlerror();
        printf("Load libprint.so failed : %s \n", err);
        exit(1);
    }
    //clear error info
    dlerror();

    typedef void (*pf_t)(void);
    pf_t print = (pf_t)dlsym(handle, "printExtern");

    err = dlerror();

    if(err)
    {
        printf("fail to find function : %s \n", err);
        exit(1);
    }

    print(); //使用函数指针的方式引用,不能直接引用

    //close the lib
    dlclose(handle);
    if(dlerror())
    {
        printf("close libprint.so failed : %s \n", dlerror());
        exit(1);
    }

    printf("%s\n", __FUNCTION__);

}


int main()
{
    printf("waite 5 seconds\n");
    sleep(5);

    test();
    return 0;
}

同时我们gcc 中还需要加上 libdl.so库

代码语言:javascript
复制
gcc -fPIC -c main.c -o main.o
gcc -o target main.c -L./ -lprint -I./ -lstdc++ -ldl

此时我们删除 libprint.so。然后再执行target 文件。发现程序运行了一段时间,到使用库时才报错

代码语言:javascript
复制
Load libprint.so failed : ./libprint.so: cannot open shared object file: No such file or directory 

由此我们可以看出动态库的显示连接才能真正实现使用时才去调用。

附录 : makefile

在这里我给出这个小工程的makefile

代码语言:javascript
复制
CXX = g++
CC = gcc
FLAGS = -fPIC
TARGET = target
CSOURCE = $(wildcard ./*.c)
COBJS = $(CSOURCE:.c=.o)

target: $(COBJS) libprint 
	$(CC) $(FLAGS) -o $(TARGET) main.o -L./ -lprint -I./ -ldl

%.c:%.o
	$(CC) $(FLAGS) -c $< -o $@

libprint:
	$(CC) $(FLAGS) -shared -o libprint.so print.o

rm:
	rm print.o

clean:
	-rm -rf $(COBJS) $(TARGET) libprint.so

test:
	@echo $(CSOURCE)
	@echo $(COBJS)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/187491.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年9月30日 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 1.1 动态库和静态库的区别
  • 1.2 内存中的动态库
  • 1.3 动态库的加载
  • 1.4 动态库的制作
  • 1.5 动态库的隐式链接
  • 1.6 动态库的显式链接
  • 附录 : makefile
相关产品与服务
轻量应用服务器
轻量应用服务器(TencentCloud Lighthouse)是新一代开箱即用、面向轻量应用场景的云服务器产品,助力中小企业和开发者便捷高效的在云端构建网站、Web应用、小程序/小游戏、游戏服、电商应用、云盘/图床和开发测试环境,相比普通云服务器更加简单易用且更贴近应用,以套餐形式整体售卖云资源并提供高带宽流量包,将热门软件打包实现一键构建应用,提供极简上云体验。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档