大家好,又见面了,我是你们的朋友全栈君。 总结一:动态库
前言
我们知道程序编译链接经常使用动态,同时我们可能还知道,动态库时程序运行时加载的。但是动态库到底有什么作用,如何生成、如何加载等,我们却很少关注。接下来,我给大家做一个简单的介绍。
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("%sn", __FUNCTION__);
}
void printExtern()
{
printInter();
printf("%sn", __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 secondsn");
sleep(5);
printf("%sn", __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("%sn", __FUNCTION__);
}
int main()
{
printf("waite 5 secondsn");
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