【Linux系统编程十八】:(基础IO5)--动静态库共享/动静态加载问题(涉及地址空间)

文章详细解释了可执行程序和动态库在Linux系统中的加载过程,涉及逻辑地址、虚拟地址、物理地址、缺页中断和地址空间管理,以及动态库如何实现多进程间的共享。
摘要由CSDN通过智能技术生成

一.可执行程序如何被加载的

1.加载之前

在这里插入图片描述

编译器在编译形成可执行的程序的时候。编译器是要考虑加载的问题的。
它不是说啊我随便编一下就完了啊,不是的,它呢是把可执行程序分段给我们编译好的。

那么分段编译好之后呢,相关的字段都会有它对应的地址。
这个地址呢我们可以理解成叫做每一个函数它的入口,每一个变量它的定义,相对于零号地址数对应的一个偏移量。
这个地址在我们的程序当中已经存在了。
好,那么我们把这种在可执行程序当中的地址呢,把它叫做逻辑地址,其实它是一种断地址间偏移的一种表述方式。但其实说白了它已经是虚拟地址了啊,或者叫做线性地址。

所以可执行程序在没有被加载到内存前,内部已经有地址了。
在这里插入图片描述程序内的每一个指令都有它的地址,这些将来都是由c p u去执行的。那么我们c p u它怎么能够帮我去执行这些指令呢?

c p u呢它其实呢已经在自己被制作的时候,就已经内置了很多能够认识这些基础指令的工作。只不过这些指令都是用二进制制作的,所以呢我们把这种二进制,可以提前在c p u内部设置好的,称之为叫做指令集的东西。 也就是在cpu里面呢,你向它对应的一些就是寄存器当中写入这种指令集,它是会被被当做指令或者控制指令来看待的,它不会把它当数据处理的。
所以呢你的c p u它就能认识你喂给它的各种各样的这样的汇编。那汇编呢连起来一长啊多一多,它就变成了一种具体的动作。

c p u它要能执行指令,它得先识别指令。并且c p u它是能区分清楚哪些是指令,哪些是数据的。

2.加载之后

可执行程序在加载到内存的时候,它的所有的代码的指令,包括函数调用,变量等已经在我们那么可执行程序里面已经按照e l f这样的可执行程序的格式已经给我们编好了。它内部的函数跳转时或者是变量寻找时都有它对应的地址。

我们知道每个进程还有自己的PCB对象,叫task_struct。每一个进程还有自己所对应的地址空间和页表。
未来呢CPU要执行程序中对应的代码和数据,它是如何执行的呢?我们进程数结构也也创建好了,它们之间和CPU是如何关联起来的呢?我们该如何理解呢?

①如何执行第一条命令

在这里插入图片描述
CPU执行程序中指令的时候,如何执行第一条指令呢?

我们对应的可行程序形成的时候,它里面包含了一个entry,叫做入口地址。所以文件在编译形成可执行程序的时候,他已经在自己的可行程序里,头部呢提前把自己整个程序的入口地址已经写好了。入口地址是逻辑地址啊,但在内存里它就叫做虚拟地址。

当这个可行程序被加载到内存的时候,一方面我们把程序加到内存,当然你也可以不加载啊,因为曾经讲过,一定是先形成内核这部分的,也就是内存储结构先要形成。
然后呢,我们的可行程序要运行的时候,CPU其实首先要做的就是把我们这个表当中,我们叫做入口地址拿过来。

在这里插入图片描述

所以呢CPU在读取这个可行程序时,会把对应的入口地址直接load到CPU寄存器当中。
当录到CPU的寄存器里面的时候,因为它这个地址已经是在编译时候形成,就是一个我们对应的虚拟地址。对应着程序的入口。
所以从CPU就开始执行了。
当它开始执行的时候,它就需要从经过我们对应的页表中读取虚拟地址,而读第到的第一条指令,就是entry这个虚拟地址。
CPU去执行的时候,发现这个页表当中有没有建立对应的内存,就会触发缺页中断。缺页中断后,我们的程序数据就被加载进来了。

②缺页中断/与地址空间建立联系

我们要理解,程序加载到内存的时候,它要不要占据我们对应的物理内存当中的空间呢?可执行程序加入到内存的时候,要不要占据那个内存的空间啊?它是需要的啊。
既然它要占物理内存,所以整个代码或者数据呢或者是各种区域呢,每一条呢它都要有自己对应的物理地址。当你把对应的可执行程序加载到我们对应的内存之后呢。那么每条指令天然的就具备了物理地址。
你要访问的是你程序内部的地址啊,但是你自己这条指令它有自己的物理地址。
那么所以当我们的程序加载到内存的时候,它每一行指令,它都会有两套地址。一个叫做我们自己内部所采用的,曾经加载内存之前的叫做逻辑地址啊,那么另一个就是加载物理内存时所具备的物理地址。
加粗样式

好,这个道理呢就好比。你在学校里面。每一个人都有自己的学号。这是在全校范围内给我们编好的。那么可是呢当我们坐在一个具体的教室里面的时候,我可能坐在桌子每一张桌子,比如说五十张桌子编号一到五十。我坐在其中的三号桌子上,那么当我坐在那个桌子上的时候,我就叫做从教室外加载到教室内。那么此时我自己内部用的地址,我自己和我的同学啊,我们之间聊的话,我们用的是学号。可是当我们坐在教室里的时候,我们每一个人桌子上也有对应的地址,我们叫做物理地址。

所以程序一旦加载进来,你的程序天然就具备了物理地址。好,加载的具体地址我就知道了。那么这个天然的物理地址和可执行程序内部的逻辑逻辑地址(也就是虚拟地址)就直接建立映射关系了。

对应的页表里就可以立马填上左侧的虚拟地址和右侧的页表的物理地址。初始化页表
因为加载之后的程序是有两套地址的。这时页表映射关系瞬间建立。
我们在程序内部,在你的程序内部已经编好的这个虚拟地址,我已经给你搞好了,拿过来用就行了。
因为页表中虚拟地址先填进去,所以我们的物理列表一填,此时映射关系就建立好了。

如果CPU发现指令它是一个函数调用,显示的是一串地址,这是什么地址呢?CPU可能要进行我们对应的寻址了,而这个函数地址是什么呢?
没错,这个地址它就是逻辑地址即虚拟地址。
所以直接调用,先在虚拟地址空间里找到我们的新地址,再转成物理地址。
最后再从物理地址转成我们所对应的这个我们对应的这个指令处开始执行。

CPU从读取程序当中的地址。到内部分析处理,到我们最后再重新二次继续访问它。整个过程我们凡是读到的这个指令里面的地址都是虚拟地址。
但CPU真正的要找到你,你再怎么给我虚拟,也找不到,真正的数据在物理地址里,所以这也就必须将虚拟地址转换成物理地址了。

可执行程序,其实它有两个地址,编译器编译的时候就已经考虑虚拟地址空间了。进程在设计的时候就已经考虑了你对应的地址空间了。这就是我们编译器和操作系统互相协同的最重要的表现之一。

二.动态库如何加载的

动态库就是一个可执行文件,它的加载与可执行文件的加载是一样的。
关键问题在于我需要把这个动态库映射到地址空间的共享区里,我们要理解这个可执行程序里面那么任何地址都是已经编译好对应的线性地址。
好,你今天要调的这个地址,那在我看来他就是序列地址。如果按照序列地址的话,共享库加载在序列地址里必须被加载到这个位置。也就是我在把我们对应的磁盘当中,把对应共享库加载到对应的内存里,加到对应的内存映射的时候,必须得把它映射到固定位置处。那这就有点坑爹了。
一个进程在运行时,你的库先加载后加载完完全全料不定的。因为你的程进程可能要有十几个库,我怎么保证每一个库它都必须得加载到固定位置呢?
那么被加载到我们对应的叫做固定地址空间。固定地址我给占了怎么办?你怎么保证你每一次加的时候,还要让这个库固定的映射到一个位置,那这个成本太高了。

库呢设计者得想办法让他在共享区任意位置加载都可以。我们是采用相对以逻辑地址加偏移量的方式。
我们只以偏移量来标准,我们不要采用绝对地址来编制。只要每个函数在库中的偏移偏移量即可。也就是说呢,我自己动态库,我自己在编的时候,我不要太多。你把每一个库它在地址空间里的起始地址,你给我记下来就可以。在加载时,最后我们来确定加偏量就能访问到它在库当中的绝对地址了。
在这里插入图片描述
这也是为什么在制作动态库时,需要一个东西叫做FPIC与位置无关码了。
因为动态库在加载时,不需要真正的地址,最后我们来确定加偏量就能访问到它在库当中的绝对地址了。

三.动态库如何实现多进程间共享的

在这里插入图片描述
动态库首先先加载到内存里,然后通过页表映射到进程地址空间的共享区里,只需要第一次将动态库加载到内存里,后面要是有进程需要使用动态库就直接将内存里的共享库通过页表映射到共享区里,就可以使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小陶来咯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值