再识动静态库
动静态库
- 1 手动制作静态库
- 2 手动调用静态库
- 方式一:(安装到系统)
- 方式二:(和源文件一起)
- 方式三:(使用带路径的库)
- 3 动态库制作与使用
- 方式一:拷贝到系统
- 方式二:(和源文件一起)
- 方式三:(使用带路径的库)
- 方式4
- 4 动态库的理解(动态库的加载,进程地址空间)
- 4.1 原理上理解动态库
- 5 可执行程序格式
- 6 重谈地址空间
- 7 动态库的理解
1 手动制作静态库
预备工作
上图是我们的准备的阶段(一些最基本的方法)
这个是为下面做准备的main函数,其中需要注意的是,如果我们将库就保存在当前路径的话,就使用""来引用库,如果是将库保存在系统默认路径下,就还是使用<>
2 手动调用静态库
首先制作库根据前面的理解我们知道,库其实只提供了.o与.h文件:
1、形成 .o
2、将所有 .o 打包
方式一:(安装到系统)
其中 -rc ,rc是如果-o存在就不管,如果不存在就创建
静态库标准就是库名前+lib,库名后+.a
3、将 .h 与 .a 拷贝在系统默认位置(所以安装本质上就是拷贝)
4、gcc、g++认识c/c++的头文件,我们所写的库都是第三方库,如果不说明库名,编译器是找不到的
需要注意的是库名是去掉前缀和后缀的
-l引入指定名称的第三方库
方式二:(和源文件一起)
别人直接给我们提供库和头文件:
gcc查库,默认不会在当前路径
-L. 选项:让gcc在当前路径下查找库,但必须跟上库名
方式三:(使用带路径的库)
在这里插入图片描述
首先我们写一个makefile,output代表着发布库
当我们要给别人使用时,我们可以直接将stdc压缩打包,然后发送给别人,那别人如何使用?
-I:在指定目录下查找
3 动态库制作与使用
首先动态库的后缀为.so,其次编译动态库还是使用gcc命令。
-FPIC:
动态库的使用:
方式一:拷贝到系统
方式二:(和源文件一起)
方式三:(使用带路径的库)
但是是无法使用的:
为什么会出现这种情况呢?
这是因为gcc在编译时知道在什么路径下去链接库(因为我们指定了路径),但是运行时跟gcc没有关系,它还是默认在系统路径下查找动态库,现在的问题变成了,我们如何解决动态库查找的问题?
1、将库拷贝到系统路径
2、建立软链接
3、OS存在环境变量,我们可以增加环境变量
方式4
4 动态库的理解(动态库的加载,进程地址空间)
4.1 原理上理解动态库
1、可执行程序本质上也是磁盘上的文件,当我们运行程序时,它会被加载到内存,它加载后有自己的代码段和数据段,然后页表会建立mm_struct中虚拟地址与内存地址之间的映射关系
2、如果程序中是用了库的方法,所以动态库也需要加载到内存,但是该进程该如何看到加载的动态库?
页表也会将动态库在内存中的地址映射到进程地址空间的堆栈之间的区域(共享区)。
3、进程使用库就是执行代码时,遇到库方法,程序就跳转到共享区,执行库方法的代码(在自己的地址空间中跳转运行)
4、多个进程依赖同一个库时,动态库只会加载一次,其他进程运行时,也是直接建立映射关系到自己的进程地址空间,(所以我们称动态库为共享库)
5、共享库加载到内存的位置有规定吗?动态库映射到进程地址空间的区域有规定吗?
5 可执行程序格式
以前我们只知道有可执行程序,现在就来了解一下可执行
1、首先可执行程序是有自己的格式的,它的格式我们称为ELF,上面这张就是ELF的具体组成
2、数据段,代码段等在ELF中被称为节
3、不仅仅可执行文件,包括库,.o 文件都符合ELF
4、所有的 .c 文件链接形成 .o ,本质上所有的.c文件和.o文件都具有代码段,数据段等各种节,所谓的链接就是将所有相同属性的节合并
5、header:
6、我们该如何知道每一个节的开始和结束?(对于任何文件,文本内容就是一维数组,表示文件任何一个区域,偏移量+大小方式)
7、section table: 是一个表(数组),每一个数组元素对应一个特定的数据结构,下标0,1,2.。。代表有多少节,存放每一个节的开始与结束
一个可执行程序可以被加载到内存的前提是,os要认识你的可执行程序
6 重谈地址空间
一个可执行程序可以被加载到内存的前提是os要认识你的可执行程序
1、mm_struct 是一个结构体,那这个结构体刚开始是由谁初始化的?
正文部分的代码的各种数据区,未初始化区,初始化区这些字段是从哪里来的?从可执行程序,读取可执行程序,加载可执行程序的各种数据节的具体地址而来的
2、可执行程序中的内容有没有地址(未被加载)?有
3、linux系统的编址方式称为平坦模式(逻辑地址=起始地址+偏移量)(000…0000----fffff…fffff)
main函数并不是第一个执行的函数,所以它的地址并不是0,其次指令它是二进制,也有自己的长度,两个地址之间的差值就是一个指令的长度,逻辑上只需要找到起始地址,就可以运行该程序。
虚拟地址也是全0-全f,虚拟地址在编译器编译时就已经形成了,在现在逻辑地址在数字上等于虚拟地址
4、程序内部使用的地址就是虚拟地址
5、虚拟地址的开始(程序入口)地址是告诉我们的
所以操作系统直接将这个地址告诉PC寄存器,接下来就是计组的部分了
5、cpu角度使用的地址是什么地址?虚拟地址
6、程序最终在磁盘上存储的其实是二进制,将文件内容加载到内存中时,其实是加载到内存的物理地址,文件内容也就是一句句指令,它是不是也要有自己的物理地址,所以一个程序被加载到内存中时,不光它内部有互相调用的虚拟地址,还有被加载到内存的物理地址
7、所以现在就可以完善页表了,页表映射的就是虚拟地址和物理地址,同时cpu内部还有一个寄存器叫做CR3(保存页表的起始地址–物理地址)
8、MMU(硬件)+CR3:查找虚拟地址与物理地址
9、EIP:指令寄存器
9、虚拟地址空间是操作系统、cpu、编译器协同的产物
10、 为什么要有虚拟地址和虚拟地址空间:
编译器在编译代码时只需要考虑虚拟地址,也就是操作系统和编译器进行了解耦。
11、真实的内存划分
操作系统会对每一个区域进行vm_area_struct(堆,栈,数据区等等)
12、可执行程序是按节为单位加载到内存的,这就是4G的内存可以运行更多内存的程序,甚至一个程序的加载只需要起始地址