【动态库的加载】【进程地点空间(三)】

[复制链接]
发表于 2026-1-20 02:07:03 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
再谈进程地点空间时,【Linux】动静态库 我们先讲一个关于动态库是怎样加载的话题,再引入进程地点空间,再次批评该话题。
1. 宏观对待动态库的加载

当一个可实行步调被运行时,操纵体系要为其创建内核数据布局 PCB、进程地点空间 和 页表,然后再把该步调的代码和数据从外设加载到内存中。可实行步调也是一个文件,只要是文件,就有路径和 inode,那么就可以通过路径找到该文件地点分区,再根据 inode 编号找到地点分组。而在加载可实行步调之前,须要先找到该文件,就可以通过其路径和 inode 找到其文件属性 + 文件内容,然后把它们加载到内存。
换言之,动态库也是一个文件,也有路径和 inode。当我们的步调须要访问动态库的代码时,那么这个动态库也须要被加载到内存。但是进程之间是相互独立的,一个进程是无法直接访问另一个进程的代码和数据的!以是动态库颠末页表映射到进程地点空间中的共享区,而我们本身的进程的代码是位于正文代码区的,当我们的代码想调用动态库的方法时,只须要调解到共享区,实办法态库的代码,实行完后原路返回即可。
也可以明白为,创建映射后,我们实行的任何代码,都是在我们的进程地点空间中实行的!
接着,我们须要创建一个共识,一个进程是大概加载多个动态库的,反之,一个动态库也大概跟多个进程产生关联。以是在体系运行中肯定会存在多个动态库,体系中有许多进程、文件,因此它们都须要被管理起来,同理,动态库也须要被操纵体系所管理起来,换言之,操纵体系对全部动态库的加载情况好坏常清晰的。以是假如后续有另一个进程想要访问类似的动态库,操纵体系可以或许分辨该库是否已经被加载,假如已经被加载到内存了,只须要在该进程的页表中做一下映射关系,该进程就可以或许跳转实行该库的代码。


  • C 标准库 libc.so 中有一个全局变量 errno,假如有多个进程共同访问这个库,而且本身的步调运行时都出标题了,即 errno 被写入了。它们访问的是同一个库,那么多进程之间会相互影响吗??
    共享区是位于堆栈之间的一段空间,属于用户空间。以是只要是对共享数据做写入操纵,都会导致进程发生写时拷贝!我们之前讲用户缓冲区时就有过一个案例,在调用各种 fprint、fwrite 之后 fork() 创建子进程,C 式接口的输出信息输出了两次,由于缓冲区革新的本质也是写入,以是进程对缓冲区做了写时拷贝,而缓冲区也是位于共享区中的!

2. 进程地点空间第二讲

2.1 步调没有加载前的地点



  • 步调编译完成之后,运行之前,步调内部有地点的概念吗?
    步调内部有地点。在 vs 编译调试时,假如你转到汇编上,你就可以看到编译之后的每条代码,都是有地点的,包罗函数名、变量名,在编译之后都是以地点的情势举行地点,调用一个函数,会变化为 call 一个函数的地点。再诸如 C++ 的继承多态中的虚函数表,在编译时就已经为虚函数表分配地点了,包罗表内有各种派生类的方法的地点。
    而且,在编译时为各段代码分配的地点,该地点是有分段的,诸如进程地点空间中分成了代码区、未初始化、已初始化等等各种地区,这是为了方便后续加载代码计划的,也就是说编译器,也是要思量操纵体系的(编译器编译的步调是要被操纵体系加载的,以是须要照顾该步调加载到内存的标题,即进程地点空间的分段)。而在编译时给步调分配的地点,是捏造地点!只不外假如该步调还没有被加载,我们更多的称为 逻辑地点。
    1. objdump -S a.out
    复制代码

2.2 步调加载后的地点

步调加载后,就变为进程,以是探究步调加载后的地点,实在就是探究进程的地点。
当一个步调加载到内存,它的代码和数据在内存中也肯定要占据内存空间(不管是从语言代码的角度对待,照旧汇编指令的角度),这个步调的每一条指令 / 代码都有相应的 物理地点 ,但是这与上述说的,步调在编译完后,还没被加载的时间就已经有地点这件事不辩说,加载到内存后,代码存储在物理内存上须要空间,因此它有对应的物理地点,但是这个地点不是代码数据内部的地点,只是它存储在物理内存的位置而已,该步调内部各条指令 / 代码照旧接纳的捏造地点(就比如每个弟子都有本身的学号,比及了科场上,每个考生都有一个座位号,但不影响你有本身的学号这件事,座位号本身纪录你的位置,学号才是你这个人的代表)。


  • 当进程的 PCB、进程地点空间、页表等布局都创建完成之后,该进程怎样实行第一条指令呢
    当一个步调被编译完成之后,步调内部的表头就已经存储了一个 entry 步调入口地点的信息,这个入口地点也是逻辑地点(由于编译后就有这个地点了,还没加载到内存,以是不大概是物理地点)。而 CPU 为了知道下一次实行哪一条指令,在 CPU 内部维护了一个 PC 寄存器,用于存储下一条实行的指令地点。而当一个步调被加载到内存时,其入口地点就已经被加载到 CPU 内部的 PC 寄存器中了。而由于在编译后,入口地点就是捏造地点了,因此 CPU 可以直接访问这个捏造地点,然后开始实行该步调。
    接着,顺着这个捏造地点到页表中寻址映射的物理地点,假云云时发现该步调还没有创建物理地点的映射(即该步调还没有被加载到内存中),那么就发生缺页停止,等步调的代码和数据加载到内存中了,每条指令天然的就有了物理地点,加上步调内部本身的捏造地点,就可以或许完成对页表的捏造地点到物理地点的映射!
    顺着步调中的代码实行下去,当下一条指令遇到函数调用处,其代码会被表明为 call 一个地点,假设 call 400450,那么 CPU 读取到的指令就是地点,这个地点是代码编译完后形成的地点!因此 CPU 读取到的地点也是捏造地点!然后 CPU 再顺着该捏造地点到进程地点空间中寻址,接着通过页表映射到物理地点,再次访问物理地点。后续大概另有调用函数,CPU 照旧读取到捏造地点,然后再顺着捏造地点映射到物理地点举行访存,这不就是一个环吗! 实行该步调一套操纵下来,你应该要知道,CPU 读取到的地点,全部都是捏造地点!
以是如今也就可以或许进一步明白,编译器再计划的时间,就已经思量到进程地点空间了,编译后形成步调内部的地点,即捏造地点,等步调变为进程时,内部的地点直接套用即可,这也是编译器与操纵体系相互协同最紧张的体现之一。

3. 动态库的地点



  • 绝对地点:进程地点空间中规定的 0x0000 0000 ~ 0xFFFF FFFF 如许的地点,称为绝对地点
  • 相对地点:比如 int a 变量在捏造地点中的地点为 0x11223344,int b 变量在间隔 a 变量地点之后的 4 字节,即0x11223348,如许的形貌称为相对地点。
现假设可实行步调 a.out 中调用了 libc.so 库的 printf 方法,以是当可实行步调运行起来,加载到内存之后,实行到调用处,在捏造地点映射物理地点时,发现动态库还没有被加载到内存中,于是缺页停止,等动态库加载完成而且在页表创建中创建了映射关系之后,进而根据 cpu 读取到的 printf 的地点(捏造地点 0x11223344)跳转到共享区实行库的方法。


  • 如今的标题是:动态库要映射到共享区的哪个地方呢??
    当我们的步调编译完成后,假如给动态库 libc.so 分配了 0x11223344 如许的地点,即地点已经被硬编码到步调内部了,以是当步调调用 printf 时,只能映射到捏造地点中的 0x11223344,换言之,在捏造地点中,动态库就必须被加载到 0x11223344 的位置处,假如不加载到这个位置处,将来步调跳转时,就找不到动态库。但是当体系加载了多个库时,凭什么包管 0x11223344 处肯定加载的是 libc.so 这个动态库,大概另有别的库也同时被加载到内存呢??换言之,操纵体系可无法包管哪个库肯定被加载到固定的地方。以是在捏造内存中,库的加载是恣意位置的。
    以是在编译时,会让库函数不接纳绝对编址,只接纳偏移量体现每个函数在库中的偏移位置,进而找到对应的库函数。以是当动态库被恣意的加载到捏造地点的某一处时,操纵体系只须要记取这个库的起始地点 start 即可, 当步调实行时调用库函数,步调即可根据 start + 编译时库函数形成的偏移量,即可跳转到对应库的对应方法处!
    讲 【Linux】动静态库 时,对怎样编译形成动态库,其时有一个选项:-fPIC 产生与位置无关码,产生与位置无关码的意思就是告诉编译器,不要再接纳绝对体例了,直接用偏移量对库函数举行体例!
  • 静态库为什么不谈加载,不谈与位置无关这些概念?
    如今我们就对动静态库的区别的明白更进一步了,为什么只有动态库有加载的概念,由于静态库是拷贝计谋,本身的步调调用了库方法,用什么我拷什么即可,须要的库方法已经成为我的可实行步调的一部分了,以是没有静态库须要加载的说法。而当我们的步调被加载到内存时,在步调内部,任何指令但是都已经硬编址好的了,方法都已经拷贝到我本身的步调内部了,哪一条指令加载到捏造地点的哪一地点处,都是清清晰楚的,还须要用什么偏移量?!还须要怕找不到库的标题吗?!(库方法就在你步调内里)

假如感觉该篇文章给你带来了劳绩,可以 点赞👍 + 收藏⭐️ + 关注➕ 支持一下!
感谢各位观看!
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表