认识动态库静态库
我们有没有使用过库呢?-- 用过c、c++的标准库
c的各种函数,c++的各种STL容器,我们使用他们内部必须得有具体实现。
Linux: .so(动态库) .a(静态库) Windows: .dll(动态库) .lib(静态库)
库是拿来给别人使用的,所以库里面必然没有main函数,库名是去掉前缀lib和后缀.so。
静态库
我们知道若干的.o文件能够链接成一个可执行文件。
静态库一般是这些若干目标二进制文件(.o)打包而成。我们一般用gcc -c选项生成.o文件。
一个完整的库需要静态库或动态库加上头文件。
头文件是一个手册,提供函数的声明,告诉用户怎么用。
.o提供实现,我们只需要补上一个main.c,调用头文件提供的方法,然后和.o进行链接,就能形成可执行文件。
我们一般用ar -rc libmyc.a *.o来打包.o文件形成静态库。
我们把.o文件可以删除了。
直接执行后是找不到定义的,也就是说gcc/g++是不认识这个库的,gcc/g++默认是认识C/C++库的,但libmyc.a是别人写(第三方提供的),gcc//g++不认识!!!
使用gcc main.c libmyc.a
我们以后使用库文件肯定是放在某一个目录中去,头文件和静态库分别放到对应目录文件中去。
我们把头文件放到./mylib/include中,libmyc.a放到./mylib/lib中去。
然后执行gcc main.c libmyc.a
把静态库写入自己指定的文件里。
gcc main.c -I ./mylib/include/ -L ./mylib/lib/ -l myc
其中-I选项: 指定用户自定义头文件路径
-L选项:指定用户自定义库文件路径
-l选项:指行确定的第三方库名称(去掉前缀.so和后缀.a)
动态库
动态库的制作与静态库略有区别,首先产生.o文件的选项就不同,增加了选项-fPIC(与位置无关码)
然后打包.0文件执行就不是静态库那样执行ar命令,而是执行gcc带选项-shared
动态库制作成功,把动态库移动我们的库文件中去。
执行main.c
为什么会这样?因为 你告诉给了gcc/g++编译器,但你没告诉给操作系统!!!
动态库,因为程序在运行的时候要找到动态库加载并运行!
静态库为什么没这个问题?编译期间,已经将库中的代码,拷贝到我们的可执行程序内部了,加载就和库没有关系了!!!
怎么解决?五种方法来解决!!!
1.安装到系统
可以把.so文件拷贝到系统目录下/lib64
先切换到root用户下,因为是系统文件,子用户没有权限。
2.建立软链接
在系统路径下建立软链接!!!
3.命令行导入环境变量
系统会搜索环境变量的路径!!!
由于这里导入的环境变量是内存级的,是无法永久生效的,重启系统操作系统会加载新的环境变量的内容,而这些使用命令临时导入的环境变量就不存在了!!!
4.修改.bashrc配置文件,让环境变量永久生效
修改配置文件
重启操作系统依然可以正常链接
5./etc/ld.so.conf.d 新增动态库搜索的配置文件,ldconfig
切换root创建我们的配置文件。
写入动态库链接路径即可
需要使用ldconfig用于配置共享库缓存,以便运行时正确的加载动态库。
因为gcc在不使用static选项时默认使用动态库。
此时默认连接的是动态库,如果你没有使用-static,并且只提供.a,只能静态链接当前的.a库,其他库正常动态链接。
-static的意义是什么?必须强制的使用-static将我们的程序进行静态链接,这就要求我们链接的任何库,都必须提供对应的静态库版本。
动态库加载 -- 可执行程序和地址空间
因为静态库是把库中的代码拷贝到可执行程序中了,所以就无需加载!!!
所以不需要考虑静态库。
我们知道一个进程创建要同时创建task_struct, 地址空间,页表来与内存进行关系映射,在地址空间中有一块共享区,我们就需要在动态库加载之后,要映射到当前进程的堆栈之间的共享区,所以动态库也叫共享库。
我们知道c语言标准库就一直在内存中保存,因为我们在编译很多c语言程序时,甚至我们的指令都是用c语言写的,但都链接的是同一个库,这是怎么做到他们可能被映射到共享区的任意区域,仍然能够很好的找到对应的函数执行!!!
我们的可执行程序,编译成功,没有加载运行,二进制代码中有“地址”吗?
有地址!!!
ELF格式的可执行程序,二进制是有自己的固定格式的,elf可执行程序的头部,可执行程序的属性
可执行程序编译之后,会变成很多行汇编语句,每条汇编语句都有他的地址。
那如何编址?
从000...000 --- ffffff...ffff这种顺序编址方式,我们叫为平坦模式,
上方就是这种模式,其实是一种虚拟地址,又叫逻辑地址!!!
ELF + 加载器可以知道各个区域的起始和结束地址,main函数的入口地址!!!
我们知道进程 = 内核数据结构 + 代码和数据,那么操作系统应该是先创建内核数据结构,然后再加载代码和数据,还是反过来呢?当然是第一种方式!!!
在我们CPU中有很多寄存器,其中有一个类似于pc指针的寄存器,
这个pc指针里面存放的是正在执行指令的下一跳指令的地址,pc指向哪里,CPU就执行哪里的代码!!!
mm_struct是结构体对象,成员变量呢?
像code_start,code_end...global_start初始值从哪来呢?
从可执行程序来!!!
所以结论:虚拟地址空间概念,不是OS独有的,而是要有OS,编译器,加载器
1.进程创建阶段,初始化地址空间,让CPU知道main函数入口地址
2.加载每一行代码和数据,就都有了物理地址,自己的虚拟地址自己也知道,然后就可以构建映射了!!!
库被映射到虚拟地址空间的什么位置重要吗?
由于可执行程序汇编代码是平坦模式,所以从0开始,那么每一行代码对应的地址就可以作为偏移量了!!!
库中代码被加载到内存中会有对应物理地址对应,而虚拟地址代表偏移量,那么,在地址空间与内存通过页表映射时就拿到了该行代码在地址空间的虚拟地址,而代码自带的地址可以是一种偏移量,那如何找到我们要执行的代码块,只需要让虚拟地址空间的初始位置 + 偏移量(虚拟地址)即可求得我们要执行代码的那一行。
我们可能不知道虚拟地址空间的共享区初始位置,但偏移量是确定的,所以无论初始位置在哪,总能找到我们想要的代码行!!!
所以虚拟地址空间的位置不重要!!!
即与地址无关
库函数调用其实,也是在我的地址空间返回内来回跳转!
如果库没有加载???OS可以让进程知道!!!
库也要先描述,在组织,被OS管理!