linux(9) 库
前言:
库这个东西,我们在以前就经常用,只是以前在使用库的时候,有一些问题是无法在学习语言的时候解决的,所以我们今天站在系统的层面来学习一下库;
首先,我们先聊聊什么是库;
然后,我们就要尝试自己编写一个库;
之后,我们来讲一讲如何使用我们的库;
在此之后,我们会引出ELF文件,从而对虚拟地址有一个全新的理解;
最后,我们来讲讲动态链接和静态链接;
那么,做好咯,我们现在发车~
正文:
静态库是什么:
我们先来一个例子:
比如今天有两个人,一个叫梅(我),一个叫张三,我们的老师布置了一个作业,让我们实现一个.......的功能,要自己来写函数;
我比张三厉害一些,所以我很快就写完了这个作业:
[tsx@VM-0-10-centos mei]$ tree . |-- func.c |-- func.h `-- main.c内部如下:
[tsx@VM-0-10-centos mei]$ cat main.c #include "func.h" int main() { print(); return 0; } [tsx@VM-0-10-centos mei]$ cat func.c #include "func.h" void print() { printf("今天是九月十七日\n"); } [tsx@VM-0-10-centos mei]$ cat func.h #include<stdio.h> void print();运行结果如下:
[tsx@VM-0-10-centos mei]$ gcc -o main main.c func.c [tsx@VM-0-10-centos mei]$ ./main 今天是九月十七日所以呀,我很轻松的就完成了这个作业,这时候呢;
张三有点急了,这小子跑过来对我说,“那个,小梅啊,我这作业不会写,你发一份给我呗”
我想了想,直接发过去的话......这老师检查作业非常严格,要是发现我俩写的一模一样,那最后说是谁抄谁的?容易得罪人.......因此我根本不想把源文件给他,这时候,我就想着:
咦?我们不是还有一种编译链接的方式吗?就是把所有.c文件都编译成.o文件,再进行链接
[tsx@VM-0-10-centos mei]$ gcc -c *.c [tsx@VM-0-10-centos mei]$ tree . |-- func.c |-- func.h |-- func.o |-- main |-- main.c |-- main.o `-- makefile就像这样,我们就得到了两个.o文件,那么我们是不是可以只把.o文件发给张三,然后让他去链接啊,而且.o里面存的是二进制机械码(ELF文件,我们稍后讲),这老师应该也看不懂,所以啊,我对张三说:
“张三啊,我把这个.o文件发给你,你自己链接吧!”
张三挠了挠头:
“也行吧!”
于是呢,我做了如下操作:
[tsx@VM-0-10-centos 917]$ tree . |-- mei | |-- func.c | |-- func.h | |-- func.o | |-- main | |-- main.c | |-- main.o | `-- makefile `-- zhangsan 2 directories, 7 files [tsx@VM-0-10-centos 917]$ mv mei/func.o zhangsan [tsx@VM-0-10-centos 917]$ tree . |-- mei | |-- func.c | |-- func.h | |-- main | |-- main.c | |-- main.o | `-- makefile `-- zhangsan `-- func.o这时候啊,张三就得到了我的func.o,但是张三这时候挠了挠头,这二进制机械码......张三看不懂啊?!所以张三就来问我:
“你这写的啥啊,我都看不懂你写了什么函数”
这时候我拍了拍脑袋:
“哦~~~我该把头文件发给你的,你等等,我把头文件发给你”
[tsx@VM-0-10-centos 917]$ mv mei/func.h zhangsan [tsx@VM-0-10-centos 917]$ tree . |-- mei | |-- func.c | |-- main | |-- main.c | |-- main.o | `-- makefile `-- zhangsan |-- func.h `-- func.o这时候呢,张三有了我的头文件和函数,那他是不是就可以自己编main.c函数,然后链接一下就好了呀?
[tsx@VM-0-10-centos zhangsan]$ tree . |-- func.h `-- func.o 0 directories, 2 files [tsx@VM-0-10-centos zhangsan]$ touch zhang_main.c [tsx@VM-0-10-centos zhangsan]$ vim zhang_main.c [tsx@VM-0-10-centos zhangsan]$ gcc -c zhang_main.c [tsx@VM-0-10-centos zhangsan]$ gcc -o zhang_main *.o [tsx@VM-0-10-centos zhangsan]$ tree . |-- func.h |-- func.o |-- zhang_main |-- zhang_main.c `-- zhang_main.o [tsx@VM-0-10-centos zhangsan]$ ./zhang_main 今天是九月十七日我们发现,事实果然如此啊,只要我把.o文件和头文件给了张三,那我就可以让张三在不看到我源代码的同时,可以进行编译!
那么,后来我反思了一下,我后来发现,这样一个个的把.o文件和头文件给过去,好臃肿,好挫啊,所以我就想了一个办法,把它们捆绑成库:
[tsx@VM-0-10-centos 917]$ tree . |-- mei | |-- func2.c | |-- func2.h | |-- func2.o | |-- func.c | |-- func.h | |-- func.o | |-- main | |-- main.c | |-- main.o | `-- makefile `-- zhangsan我先让一切都回到没有给张三.o文件和头文件的时候,为了方便我们演示,我又多加了一个func2.c,内容如下:
[tsx@VM-0-10-centos mei]$ cat func2.c #include "func2.h" void print2() { printf("今天是农历八月初七\n"); }接下来,我们来进行静态库的打包
我们要用到的命令是ar rc 库名称 库用到的.o文件
[tsx@VM-0-10-centos mei]$ ar rc libmei.a func.o func2.o [tsx@VM-0-10-centos mei]$ tree . |-- func2.c |-- func2.h |-- func2.o |-- func.c |-- func.h |-- func.o |-- libmei.a |-- main |-- main.c |-- main.o `-- makefile这样,我们就形成了我的静态库了;
同时,我们也可以通过 ar -t 来看我的库中包含了哪些.o文件
[tsx@VM-0-10-centos mei]$ ar -t libmei.a func.o func2.o好,现在我们有库了,我们把库和头文件打包一下:
[tsx@VM-0-10-centos mei]$ tree . |-- func2.c |-- func2.h |-- func2.o |-- func.c |-- func.h |-- func.o |-- libmei.a |-- main |-- main.c |-- main.o |-- makefile `-- yanmei |-- ku `-- tou 3 directories, 11 files [tsx@VM-0-10-centos mei]$ mv libmei.a yanmei/ku [tsx@VM-0-10-centos mei]$ mv func.h func2.h yanmei/tou [tsx@VM-0-10-centos mei]$ tree . |-- func2.c |-- func2.o |-- func.c |-- func.o |-- main |-- main.c |-- main.o |-- makefile `-- yanmei |-- ku | `-- libmei.a `-- tou |-- func2.h `-- func.h那yanmei这个目录,就是我们最终要给张三的咯:
[tsx@VM-0-10-centos 917]$ tree . |-- mei | |-- func2.c | |-- func2.o | |-- func.c | |-- func.o | |-- main | |-- main.c | |-- main.o | `-- makefile `-- zhangsan `-- yanmei |-- ku | `-- libmei.a `-- tou |-- func2.h `-- func.h那现在,张三就获得了我精心为他准备的静态库了,那么怎么用呢?
静态库的使用:
静态库的使用有四种,不过嘛,我们今天只讲最常用的两种:
①:用命令指明路径与库名称
[tsx@VM-0-10-centos 917]$ cd zhangsan [tsx@VM-0-10-centos zhangsan]$ touch main.c [tsx@VM-0-10-centos zhangsan]$ vim main.c [tsx@VM-0-10-centos zhangsan]$ gcc -o main main.c -L yanmei/ku -I yanmei/tou -l mei [tsx@VM-0-10-centos zhangsan]$ ./main 今天是九月十七日 今天是农历八月初七 [tsx@VM-0-10-centos zhangsan]$ tree . |-- main |-- main.c `-- yanmei |-- ku | `-- libmei.a `-- tou |-- func2.h `-- func.h这种方法写起来比较长,不过我觉得比较简单,毕竟相对于把文件安装到系统目录下,实在是方便太多了
②.把头文件,静态库安装到系统目录下:
[tsx@VM-0-10-centos zhangsan]$ sudo cp yanmei/tou/func.h /usr/include [tsx@VM-0-10-centos zhangsan]$ sudo cp yanmei/tou/func2.h /usr/include [tsx@VM-0-10-centos zhangsan]$ sudo cp yanmei/ku/libmei.a /usr/lib [tsx@VM-0-10-centos zhangsan]$ rm main [tsx@VM-0-10-centos zhangsan]$ tree . |-- main.c `-- yanmei |-- ku | `-- libmei.a `-- tou |-- func2.h `-- func.h 3 directories, 4 files [tsx@VM-0-10-centos zhangsan]$ gcc -o main main.c -l mei [tsx@VM-0-10-centos zhangsan]$ ./main 今天是九月十七日 今天是农历八月初七核心是:
把头文件放到 /usr/include 目录下
把库放到 /usr/lib 目录下
对库的理解:
那我链接的本质不就是,把一堆.o文件给连接到一起吗?而我的库在经过我们上面的探索之后,我们知道了,库本质不也就是一堆.o文件的集合体吗!!!!
此外,注意一下,库必须以lib开头,以.a或.so 结尾,掐头去尾,即为库的真名
动态库:
如何打包动态库:
结论:
gcc -shared (.o文件) -o (动态库名字)
实际演示:
[tsx@VM-0-10-centos mei]$ tree . |-- func2.c |-- func.c |-- main |-- main.c |-- main.o `-- makefile 0 directories, 6 files [tsx@VM-0-10-centos mei]$ gcc -c -fPIC func.c [tsx@VM-0-10-centos mei]$ gcc -c -fPIC func2.c [tsx@VM-0-10-centos mei]$ tree . |-- func2.c |-- func2.o |-- func.c |-- func.o |-- main |-- main.c |-- main.o `-- makefile这个是必须加上的;
[tsx@VM-0-10-centos mei]$ gcc -shared func.o func2.o -o libqingzhu.so [tsx@VM-0-10-centos mei]$ tree . |-- func2.c |-- func2.h |-- func2.o |-- func.c |-- func.h |-- func.o |-- libqingzhu.so |-- main |-- main.c |-- main.o `-- makefile 0 directories, 11 files [tsx@VM-0-10-centos mei]$ mkdir -p qingzhu/tou [tsx@VM-0-10-centos mei]$ cd qingzhu [tsx@VM-0-10-centos qingzhu]$ mkdir ku [tsx@VM-0-10-centos qingzhu]$ cd .. [tsx@VM-0-10-centos mei]$ mv func.h func2.h qingzhu/tou [tsx@VM-0-10-centos mei]$ mv libqingzhu.so qingzhu/ku [tsx@VM-0-10-centos mei]$ tree . |-- func2.c |-- func2.o |-- func.c |-- func.o |-- main |-- main.c |-- main.o |-- makefile `-- qingzhu |-- ku | `-- libqingzhu.so `-- tou |-- func2.h `-- func.h这样,我们在小梅的目录中,就建好了qingzhu这个目录啦,里面就包含我们的动态库以及头文件;
之后,我们把它移到张三目录中
[tsx@VM-0-10-centos mei]$ mv qingzhu ../zhangsan [tsx@VM-0-10-centos mei]$ cd ../zhangsan [tsx@VM-0-10-centos zhangsan]$ tree . |-- main.c |-- qingzhu | |-- ku | | `-- libqingzhu.so | `-- tou | |-- func2.h | `-- func.h `-- yanmei |-- ku | `-- libmei.a `-- tou |-- func2.h `-- func.h 6 directories, 7 files我们在演示的时候,不是把静态库和头文件都安装到系统目录了嘛,我们现在删掉,防止对我们产生影响;
[tsx@VM-0-10-centos zhangsan]$ sudo rm /usr/include/func.h [sudo] password for tsx: [tsx@VM-0-10-centos zhangsan]$ sudo rm /usr/include/func2.h [tsx@VM-0-10-centos zhangsan]$ sudo rm /usr/lib/libmei.a动态库的使用:
给张三安装好后,我们来使用这个动态库啦,
编译时:
①:把库文件和头文件安装到系统指定路径下:
1. 把头文件复制到系统头文件目录 sudo cp qingzhu/tou/*.h /usr/include 2. 把动态库复制到系统库目录 sudo cp qingzhu/ku/libqingzhu.so /usr/lib②:本地路径指定编译
就和我们在静态库使用做的一样:
[tsx@VM-0-10-centos zhangsan]$ gcc main.c -o main -Iqingzhu/tou -Lqingzhu/ku -lqingzhu [tsx@VM-0-10-centos zhangsan]$ tree . |-- main |-- main.c |-- qingzhu | |-- ku | | `-- libqingzhu.so | `-- tou | |-- func2.h | `-- func.h `-- yanmei |-- ku | `-- libmei.a `-- tou |-- func2.h `-- func.h问题:
咦?那我们是不是就好啦?那动态库真的太简单啦~~当然不是!!!!!
我们这时候如果运行的话:
[tsx@VM-0-10-centos zhangsan]$ ./main ./main: error while loading shared libraries: libqingzhu.so: cannot open shared object file: No such file or directory这什么意思呀?
找不到动态库......
怎么会这样呢?
因为!在编译的时候,我们指明的路径,是给编译器指明了路径,而我们在运行的时候,是谁来干活啊?加载器!现在我又没有指明路径,当然找不到咯
那我们这里也给出两种解决方案:
运行时:
①:把库和头文件安装到系统目录
[tsx@VM-0-10-centos zhangsan]$ sudo cp qingzhu/ku/libqingzhu.so /usr/lib [sudo] password for tsx: [tsx@VM-0-10-centos zhangsan]$ sudo cp qingzhu/tou/*.h /usr/include [tsx@VM-0-10-centos zhangsan]$ ./main ./main: error while loading shared libraries: libqingzhu.so: cannot open shared object file: No such file or directory [tsx@VM-0-10-centos zhangsan]$ sudo rm /usr/lib/libqingzhu.so [tsx@VM-0-10-centos zhangsan]$ sudo cp qingzhu/ku/libqingzhu.so /usr/lib64 [tsx@VM-0-10-centos zhangsan]$ ./main 今天是九月十七日 今天是农历八月初七注意哦,这里把头文件安装到 /usr/lib 是不可以的;
正如我上面写的,就报错了,原因是:
64 位 CentOS,系统库目录不是/usr/lib,64 位动态库要放到/usr/lib64/usr/lib是给 32 位库用的,你把 64 位 so 放进去,动态链接器不会去这里找,所以依然报错。
那就有人要问了,那我静态库怎么就可以安装到 /usr/lib 目录下呢?
静态库只在编译链接阶段使用,不区分/usr/lib和/usr/lib64的运行时查找规则;动态库是程序运行时由动态链接器加载,会严格区分 64 位库目录。
②:设置临时变量:
[tsx@VM-0-10-centos zhangsan]$ sudo rm /usr/lib64/libqingzhu.so [tsx@VM-0-10-centos zhangsan]$ sudo rm /usr/include/func.h /usr/include/func2.h [tsx@VM-0-10-centos zhangsan]$ export LD_LIBRARY_PATH=qingzhu/ku [tsx@VM-0-10-centos zhangsan]$ ./main 今天是九月十七日 今天是农历八月初七和我们在环境变量改PATH这个环境变量的时候差不多;
就是:PATH=$PATH:(路径)
总结一下:
这里依旧给一头表格来总结:
| 对比项 | 静态库(.a) | 动态库(.so) |
|---|---|---|
| 文件后缀 | .a | .so |
| 链接时机 | 编译链接阶段,把库代码拷贝进可执行程序 | 程序运行阶段,程序启动后才加载库 |
| 生成方式 | ar打包.o目标文件,编译目标文件不需要-fPIC | gcc 加-fPIC生成位置无关代码,-shared生成 so |
| 程序体积 | 可执行文件大,库代码被复制到程序内部 | 可执行文件更小,库单独存放,多个程序可共享同一份 so |
| 运行依赖 | 编译完成后,不再依赖原静态库文件,删掉.a 照样运行 | 运行时必须存在对应的.so,找不到直接报错 |
| 内存占用 | 多程序同时运行,各自保存一份库代码,内存冗余 | 内存中只加载一份库,多个进程共享,节省内存 |
| 系统目录差异 | 64 位 CentOS,.a放/usr/lib链接器也能找到;仅链接阶段生效 | 64 位 CentOS,64 位 so 必须放/usr/lib64;动态链接器运行时严格区分目录 |
| 更新方式 | 库更新,必须重新编译整个程序才能生效 | 替换新的 so 文件,程序不用重新编译,重启程序即可生效 |
| 链接参数 | -lxxx | -lxxx |
| 优点 | 运行不依赖外部库,移植简单,运行速度略快 | 程序体积小、节省内存,库升级方便 |
| 缺点 | 可执行文件大,库更新需要重编译,内存浪费 | 运行依赖 so 文件,部署时必须附带动态库,容易出现找不到库的问题 |
ELF文件:
是啥:
ELF(Executable and Linkable Format),Linux 下目标文件、可执行程序、静态库、动态库统一使用的文件格式。 四种文件都是 ELF:
①.o目标文件(编译后,链接前)
②.a静态库:多个.o打包而成的 ELF 集合
③.so动态库:共享对象 ELF
④. 可执行文件(./a.out,我们直接运行的程序)
结构:
一个ELF文件的组成部分:
长啥样:
我这里给大家画了一下;
如何读:
# 查看ELF头部信息 readelf -h main # 查看程序头表PHT readelf -l main # 查看节头表SHT readelf -S main使用演示:
[tsx@VM-0-10-centos zhangsan]$ readelf -h main ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x400560 Start of program headers: 64 (bytes into file) Start of section headers: 6464 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 9 Size of section headers: 64 (bytes) Number of section headers: 30 Section header string table index: 29 [tsx@VM-0-10-centos zhangsan]$ readelf -l main Elf file type is EXEC (Executable file) Entry point 0x400560 There are 9 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040 0x00000000000001f8 0x00000000000001f8 R E 8 INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238 0x000000000000001c 0x000000000000001c R 1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x000000000000082c 0x000000000000082c R E 200000 LOAD 0x0000000000000e00 0x0000000000600e00 0x0000000000600e00 0x000000000000023c 0x0000000000000240 RW 200000 DYNAMIC 0x0000000000000e18 0x0000000000600e18 0x0000000000600e18 0x00000000000001e0 0x00000000000001e0 RW 8 NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254 0x0000000000000044 0x0000000000000044 R 4 GNU_EH_FRAME 0x0000000000000700 0x0000000000400700 0x0000000000400700 0x0000000000000034 0x0000000000000034 R 4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 10 GNU_RELRO 0x0000000000000e00 0x0000000000600e00 0x0000000000600e00 0x0000000000000200 0x0000000000000200 R 1 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 04 .dynamic 05 .note.ABI-tag .note.gnu.build-id 06 .eh_frame_hdr 07 08 .init_array .fini_array .jcr .dynamic .got [tsx@VM-0-10-centos zhangsan]$ readelf -S main There are 30 section headers, starting at offset 0x1940: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .interp PROGBITS 0000000000400238 00000238 000000000000001c 0000000000000000 A 0 0 1 [ 2] .note.ABI-tag NOTE 0000000000400254 00000254 0000000000000020 0000000000000000 A 0 0 4 [ 3] .note.gnu.build-i NOTE 0000000000400274 00000274 0000000000000024 0000000000000000 A 0 0 4 [ 4] .gnu.hash GNU_HASH 0000000000400298 00000298 0000000000000038 0000000000000000 A 5 0 8 [ 5] .dynsym DYNSYM 00000000004002d0 000002d0 00000000000000f0 0000000000000018 A 6 1 8 [ 6] .dynstr STRTAB 00000000004003c0 000003c0 0000000000000077 0000000000000000 A 0 0 1 [ 7] .gnu.version VERSYM 0000000000400438 00000438 0000000000000014 0000000000000002 A 5 0 2 [ 8] .gnu.version_r VERNEED 0000000000400450 00000450 0000000000000020 0000000000000000 A 6 1 8 [ 9] .rela.dyn RELA 0000000000400470 00000470 0000000000000018 0000000000000018 A 5 0 8 [10] .rela.plt RELA 0000000000400488 00000488 0000000000000060 0000000000000018 AI 5 23 8 [11] .init PROGBITS 00000000004004e8 000004e8 000000000000001a 0000000000000000 AX 0 0 4 [12] .plt PROGBITS 0000000000400510 00000510 0000000000000050 0000000000000010 AX 0 0 16 [13] .text PROGBITS 0000000000400560 00000560 0000000000000182 0000000000000000 AX 0 0 16 [14] .fini PROGBITS 00000000004006e4 000006e4 0000000000000009 0000000000000000 AX 0 0 4 [15] .rodata PROGBITS 00000000004006f0 000006f0 0000000000000010 0000000000000000 A 0 0 8 [16] .eh_frame_hdr PROGBITS 0000000000400700 00000700 0000000000000034 0000000000000000 A 0 0 4 [17] .eh_frame PROGBITS 0000000000400738 00000738 00000000000000f4 0000000000000000 A 0 0 8 [18] .init_array INIT_ARRAY 0000000000600e00 00000e00 0000000000000008 0000000000000008 WA 0 0 8 [19] .fini_array FINI_ARRAY 0000000000600e08 00000e08 0000000000000008 0000000000000008 WA 0 0 8 [20] .jcr PROGBITS 0000000000600e10 00000e10 0000000000000008 0000000000000000 WA 0 0 8 [21] .dynamic DYNAMIC 0000000000600e18 00000e18 00000000000001e0 0000000000000010 WA 6 0 8 [22] .got PROGBITS 0000000000600ff8 00000ff8 0000000000000008 0000000000000008 WA 0 0 8 [23] .got.plt PROGBITS 0000000000601000 00001000 0000000000000038 0000000000000008 WA 0 0 8 [24] .data PROGBITS 0000000000601038 00001038 0000000000000004 0000000000000000 WA 0 0 1 [25] .bss NOBITS 000000000060103c 0000103c 0000000000000004 0000000000000000 WA 0 0 1 [26] .comment PROGBITS 0000000000000000 0000103c 000000000000002d 0000000000000001 MS 0 0 1 [27] .symtab SYMTAB 0000000000000000 00001070 0000000000000600 0000000000000018 28 46 8 [28] .strtab STRTAB 0000000000000000 00001670 00000000000001c4 0000000000000000 0 0 1 [29] .shstrtab STRTAB 0000000000000000 00001834 0000000000000108 0000000000000000 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), I (info), L (link order), O (extra OS processing required), G (group), T (TLS), C (compressed), x (unknown), o (OS specific), E (exclude), l (large), p (processor specific)我们截一张图看看,哇,好多节,有三十个吧
我们再看这个,叫段,有九个
那么段和节什么关系?
我如果啊,要把一个文件加载到内存里,是按照内存页加载的,如果一个节呢,没有占满一个内存页,也占一整个内存页,就会造成很大的内存浪费;所以呀,我们把相同权限的节,放到一个段里,然后进行加载,这样的话,就可以给我们省下来内存;故,段是在加载过程中的概念,实际上在文件中是不存在的
内存页是啥?内存中的内存页,就相当于磁盘里的块,都是读取最小单位
ELF与虚拟地址:
首先呢,先问大家一个问题,我们的ELF文件在编好以后,,有没有“地址”呀?
答案是有的,只不过不是物理地址,而是逻辑地址(可以理解为虚拟地址),我们会记录下每一行代码的偏移量,来作为我们的逻辑地址。
我们的计算机在进行工作的时候,要求采用“平坦模式”进行工作,要求ELF文件对自己的代码和数据进行统一编址
也就是说,其实虚拟地址在我们的程序还没有加载到内存的时候,就已经把可执行程序进⾏统⼀编址了
有什么用呢?那我再问大家一个问题:
所以:虚拟地址机制,不仅os支持,编译器也支持!!!
所以,我们的流程是,我的可执行程序中,本来就有编好的虚拟地址,我要执行程序时,我用我ELF文件中的虚拟地址来给mm_struct赋值,将虚拟地址写入页表,然后在我分配到物理内存的时候,将物理地址填页表。
这一块与动态链接关系比较深,那我们就先讲讲动态链接;
动态链接:
那么我们的动态库啊,是出了名的省空间,不需要把动态库复制到每一个可执行文件中,那么我们来看看我们是如何调用动态库的;
首先我们要知道,动态库是ELF文件形式,而这个形式由于“平坦模式”,我们的ELF文件内部,是存着偏移量的,我们只要根据偏移量,加上虚拟起始地址,我们就可以得到它的虚拟地址,再通过页表,我们可以得知它的物理地址。
而动态链接的关键是,让我们在可执行文件中用到的函数,与库中对应的函数建立关系,然后对我们加载到内存中的程序的库函数调⽤进⾏地址修改,在内存中⼆次完成地址设置;
可是,这样我们修改的不就是代码区了嘛......代码区是只读的啊!!!不可修改
所以,我们就引入了GOT表(全局偏移量表)
注意:
静态链接:
静态链接,就是链接器把程序用到的所有.o目标文件、静态库代码,直接复制合并到最终可执行文件里。
过程:
此时我们有了许多ELF文件,我们会在符号表中,给我们的未定义符号,找到唯一的符号定义,然后把我们所需要的多个ELF文件,合并为一个ELF文件,之后再次进行编址,再然后将符号表中的未定义符号前的地址进行修改,改为我们最终编好的逻辑地址
(注意,在此处遍历符号表指的是,会遍历每一个ELF文件的符号表找我需要的,如果一个ELF文件中有我需要的符号,哪怕只有这一个符号我需要,后续我也会把这一个.o文件全部包含)
优点:
运行时不需要依赖外部库,移植方便。
缺点:
多个程序如果都用同一个库,内存里会加载多份库代码,浪费内存;程序体积更大。
总结:
编译链接阶段,库代码直接打包进 exe;运行时不再找库。
尾:
怎么从快八点多写到现在了,好累.......好累.....
中午吃啥?