动静态库。

软硬连接
建立软连接
ln -s 源文件路径+文件名 目标路径+任意文件名
建立硬连接
ln 源文件路径+文件名 目标路径+任意文件名

取消连接
unlinke 连接文件名

libxxx.a 静态库 静态链接 xxx为库名
libYYY.so 动态库 动态链接 YYY为库名

一、站在库的制作者的角度和使用库的人的角度
制作静态库
把我们提供的方法,给别人用:
1、我把源文件直接给他,我们不想这样做,我好不容易写的为什么直接给原码

2、把我们的源代码打包成库 = 库 + .h头文件
怎么做?
非常简单,把所有库里面的.c 全部编译成.o 后 现在直接把所有.o给你也能形参可执行了但是为了防止使用太麻烦整合成一个所有.o的libxxx.a集合 未来你的main.c变成.o后再和libxxx.a一结合后就形成可执行文件了
动静态库。_第1张图片
在makefile中生成静态库
动静态库。_第2张图片
然后直接编译发现找不到头文件

动静态库。_第3张图片
gcc去
1、默认路径/usr/include里面去找
2、当前目录去找动静态库。_第4张图片
但是mymath.h根本不在这里面,所以就报错了
你可以在.c中 #include “./lib/include/” 指明头文件在哪里,但是不推荐 ,建议在gcc时处理

动静态库。_第5张图片
那我自己gcc生成可执行程序时从来没写过什么-I -L,能不能不写?
可以
1、把第三方的头文件放到系统中的头文件,把第三方库放到系统中的库中(安装)
动静态库。_第6张图片

2、把对应第三方库的头文件和库在系统头文件和库里建立软连接
但是仍然需要指明你要链接哪一个库,也就是-lmymath 这个-l后面跟的是库名字

总结“:
动静态库。_第7张图片
可执行程序中是可以动静态混合链接,既有动态链接,又有静态链接
动静态库。_第8张图片
日后第三方库可能不会让你指明头文件路径和库的路径,但是一定会有-l,因为第三方库也会有安装(拷贝到系统库路径中)

制作动态库
动态库也是先把所有.c文件成所有的.o文件,也就是预处理编译汇编
gcc内置了生成动态库的功能,就像生成可执行一样用-o生成库,但是因为库里不包含main函数,我们加上加上-shared来告诉gcc我今天不生成可执行而是生成动态库!
命令行中直接生成
动静态库。_第9张图片
在makefile中的写法
动静态库。_第10张图片
生成好动态库后,使用一下这个动态库
动静态库。_第11张图片

动静态库。_第12张图片
./a,out发现什么error while loading shared lib,这是 加载器找不到我们的.so动态库,我们在gcc告知编译器-L那是告诉了编译器,反正加载器就说我找不到。
动静态库。_第13张图片

关于
ldd a.out 查看动态库
file a.out 查看文件类型
在这里插入图片描述

那加载器怎么找到这个动态库呢?
解决加载找不到动态库的方法:
1、把库拷贝到系统默认的库路径/lib64 /usr/lib64/,当然就能找到动态库了,lib64和usr lib64都可以但是lin64用的最多
2、在lib64建立动态库libmymethod.so的软连接,在系统默认的库路径/lib64 /usr/lib64/下建立软连接
动静态库。_第14张图片

3、将自己的库所在的路径,添加到系统的环境变量LD_LIBRARY_PATH中
$LD_LTBRARY_PATH:延续之前的环境变量这样就不会覆盖掉原来的环境变量
在这里插入图片描述
动静态库。_第15张图片
以前指令需要带./ ,但是系统指令不用带因为有PATH环境变量
今天动态库加载除了系统默认路径之外,也可以指明LD_LIBRARY_PATH
环境变量就是系统级别的一种全局变量,用来支撑编译器加载器去搜索它所要的源文件库文件头文件动态库
静态库只要在编译时能找到它的任务就完成了,静态库把代码拷贝到源文件时就可以之后加载跟静态库没关系了,将来也不需要被加载,只要你的main.c可执行程序能加载就可以,这也就是为什么静态库没有可执行x,而动态库有x,说明动态库需要被加载

关于环境变量是内存级,配置后重启shell会失效,要想一直存在需要进入家目录中的.bash_profile去添加
动静态库。_第16张图片

4、/etc/ld.so.conf.d建立自己的动态库路径的配置文件,然后重新ldconfig即可
对你没看错就是ld.so.conf.d这个目录名,然后在里面建立一个任意名字的.conf
动静态库。_第17张图片
实际情况比我们看到的要简单
在这里插入图片描述


把两个不相干的可执行程序用的同一个动态库删了会导致两个就都跑不了
这就说明了,动态库在进程运行的时候,是要被加载的(静态库没有)
常见的动态库被所有的可执行程序(动态链接的),都要使用,动态库—共享库
所以,动态库在系统中加载之后,会被所有进程共享!!!
怎么做到的? ?

动静态库。_第18张图片
动态库共享公共代码,所有进程共享一个库,共享库被所有程序不光在硬盘上链接同一个库,加载时在内存中公用一个库,可以让公共代码只有一份,大大节省内存。
静态链接用不着加载,拷贝进你的可执行中之后你把静态库删了可执行照样跑的欢。

二、动态库是怎么被加载的?
当自己程序代码中遇到了动态库中代码,编译期间可执行程序其实知道库中代码地址,此时需要把库文件也是个文件加载到物理内存中,通过页表映射到地址空间共享区,此时cpu执行代码从可执行程序代码跳转到共享区代码运行完成后再跳转回来继续执行

结论:建立映射,从此往后,我们执行的任何代码,都是在我们的进程地址空间中进行执行!!

事实:系统在运行中,一定会存在多个动态库-- os管理起来—先描述在,在组织系统中,所有库的加载情况,OS非常清楚有哪些库被加载哪些库没被加载

由于操作系统对多个动态库进行了管理,对于已经被加载的动态库如果其他进程下次再次调用,那么OS直接让其去找已经加载的库的共享区跳转运行就可以,所以系统中只需要存在一份共享库,就能被所有进程共享使用了,所以动态库也叫共享库

动静态库。_第19张图片

问题:共享库libc.so中提供的全局变量errno,如果A进程出错,B进程也出错,他们对同一个errno进行修改,那岂不是互相干扰了?
答:共享区属于用户空间3G,先对errno全局变量进行检测其引用计数是否被多进程共享,如果要进行写入此时会发生写时拷贝,不会互相干扰

进程地址空间的第二讲
1、什么是虚拟地址?
CPU读到的指令里面用的地址,是什么地址?
2、什么是物理地址??
3、动态库编译形成.o文件时gcc -fPIC --与地址无关码是什么?静态库怎么不用
我们要把整个可执行程序和库,以及编译原理和操作系统和硬件三者之间的关系理顺了,知道程序刚开始是怎么跑起来的,结合动态库。

其实现在

关于地址
1、程序没有加载前的地址(可执行程序)
程序编译好之后,内部有地址的概念吗?
有的,老的可执行程序内部的编址采用段地址加偏移量的方式,现在的编译器编址可执行程序采用平坦模式:严格遵照地址空间的方式来编址,从全0到全1编址。磁盘看到的可执行程序看到的编址顺序和地址空间的规则基本一致。
遵照地址空间编址后可执行程序内部的地址叫逻辑地址,他其实已经是虚拟地址了!
逻辑地址和虚拟地址是一回事,只不过逻辑地址是磁盘中可执行程序形成时所对应的地址
现在已经明白可执行程序已经在磁盘中编址时把虚拟地址编好了,我上来直接往虚拟地址空间和页表里面扔就可以了
原来编译器也要考虑我的虚拟地址的概念,要不然形成可执行程序运行不了。
动静态库。_第20张图片

objdump -S a.out 可执行二进制程序反汇编
1、每行指令都是有自己的长度的,所以地址是42,43,46
动静态库。_第21张图片
2、CPU内置指令集,它会认识基本指令push,mov,相当于给cpu二进制序列,它能认识这是某个指令,而不是数据
这样就能从一大堆二进制中区分指令和数据

2、程序加载后的地址(进程)
先明确一个事实,可执行程序要被加载到内存中,可执行的代码和数据需要占据物理内存!其中每一条指令占用了物理内存,则每一条代码天然都有对应的物理地址!就像你在学校有学号,到了教室考试桌子上的编号一样!
动静态库。_第22张图片

可执行程序内部用的是逻辑地址也就是虚拟地址,但是它被加载到内存时,它的代码
CPU要执行对应代码和数据时,程序已经加载到内存中了,进程数据结构也创建好了,现在的问题就是:cpu执行指令时,如何执行第一条指令呢??
动静态库。_第23张图片

你上面不说了吗在虚拟地址空间中找到正文代码如何如何,你想多了,第一条指令在那我们并不清楚,刚开始的时候。
好在进程程序替换时,可执行程序里面有一个表头,表头里面有个entry:入口地址(类似CRTmain),这个地址不是物理地址,因为现在是可执行程序中编译期间表头就已经形成了,所以entry里面是逻辑地址,内存里就叫虚拟地址!
动静态库。_第24张图片
可执行程序也可不加载到内存中,先有进程相关的内核数据结构就行,有了entry后,直接读取表中入口地址,并且加载到cpu的EIP寄存器中,cpu就能通过这个入口地址(虚拟地址)经过虚拟地址空间-》页表-》,因为没有加载可执行页表中没有建立映射会触发缺页中断后重新申请物理内存空间把物理地址填入页表,程序就被加载进物理内存中,加载后程序天然就具有物理地址,这样就能把物理地址在页表中和虚拟建立映射!可以按页级来初始化页表,物理内存中按页加载的可执行程序中的所有指令的逻辑地址可以填入页表虚拟地址中,天然具有的物理地址也可以填入页表的物理地址中。页表的页级4KB初始化就完成了
动静态库。_第25张图片
然后就按顺序就执行指令呗,从第一条指令执行,执行后,这条指令的长度大小我们知道,让EIP+1,+2,+n让指令顺序向后执行通过页表映射继续访问。

动静态库。_第26张图片

当EIP访问到0x3 call 4这个指令时,它访问的是一个地址
CPU内读取到的指令,内部可能有数据,可能有地址,因为在内存中所以是虚拟地址
当我们要访问4这个地址时,从虚拟地址空间找到虚拟地址转成物理地址,找到物理内存中的指令继续运行(图中红色剪头)
动静态库。_第27张图片
所以我们发现整个的过程从读取程序中的地址,到CPU内部分析处理,到最后重新二次访问加载后指令内部又是虚拟地址,再返回给CPU,整个过程就循环起来了。(页表+mmu硬件帮我们虚拟地址转物理地址)
动静态库。_第28张图片

整个过程凡是CPU读到的指令地址全部是虚拟地址!

3、动态库的地址
1、绝对编址
0x11223344相对于整个范围地址来讲是唯一的,11223344就叫绝对地址
动静态库。_第29张图片
2、相对地址
以树为起始地址来说,人在绝对地址50米的地方相对于树的距离是10米,或者20米,但是是以树作为参照系定义出来的,则相对于树的地址是10,这种地址叫做相对地址/逻辑地址
如果树在0的位置处,此时相对地址这个偏移量就等于上面的绝对地址了
动静态库。_第30张图片

当cpu执行到可执行程序中调用了c库printf的时候,可执行程序中调用函数会被编址成地址0x1122,这时候库就需要被加载到物理内存,加载之后需要把库映射到地址空间的共享区中,但是共享区大了,具体映射到哪里呢?
我们可执行程序中对printf硬编码为0x1122,决定了虚拟地址空间中必须跳转0x1122时它必须是printf,也就是说动态库被加载到固定地址空间中的位置,但这是不可能的!

因为可能有很多个动态库被加载,谁先加载根本不确定,可能之前加载的库把我0x1122这个地址占据了,跳转时就找不到,有人说你可以不占啊提前排好就行,这是很难做到的,成本太高了。
甚至可能程序运行时动态的加载库

我们必须保证库可以在虚拟内存中,任意位置加载!
库在编址的时候,让自己内部函数不要采用绝对编址,只表示每个函数在库中的偏移量即可!!
把库随便放在地址空间共享区任意位置,放好之后建立页表映射,让我找到对应的库,最终只需要记住libc.so:start(库的地址空间起始地址)(操作系统管理了库,它知道),当你调用0x1122是一个库函数调用,需要到共享区跳转了,,要找到这个函数用start(90000)(起始地址)+0x1122(对应的偏移量)就能找到访问库中的绝对地址了,然后经过页表映射找到代码。
动静态库。_第31张图片

fPIC:
gcc在形成目标文件时,产生位置无关码
意思就是说默认形参.o时,采用绝对地址从全0到全F直接编制
现在加了fPIC,直接用偏移量进行对库中函数进行编制!

静态库为什么不谈加载,不谈与位置无关???
静态库会把程序直接拷贝到可执行程序里,根本不谈加载的问题
静态库程序直接拷贝到程序里,相对于库里的方法就是我的方法,编址时我的程序用的是平坦模式按虚拟地址编好,库里的方法也一起编了
把库方法拷贝到可执行程序中,编译时就是按照虚拟地址编址的,库方法在虚拟地址中在什么位置它就是确定的。

你可能感兴趣的:(Linux,linux)