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

news2024/11/16 16:02:03

目录

  • 1. 宏观看待动态库的加载
  • 2. 进程地址空间第二讲
    • 2.1 程序没有加载前的地址
    • 2.2 程序加载后的地址
  • 3. 动态库的地址

再谈进程地址空间时,【Linux】动静态库 我们先讲一个关于动态库是如何加载的话题,再引入进程地址空间,再次谈论该话题。

1. 宏观看待动态库的加载

当一个可执行程序被运行时,操作系统要为其创建内核数据结构 PCB、进程地址空间 和 页表,然后再把该程序的代码和数据从外设加载到内存中。可执行程序也是一个文件,只要是文件,就有路径和 inode,那么就可以通过路径找到该文件所在分区,再根据 inode 编号找到所在分组。而在加载可执行程序之前,需要先找到该文件,就可以通过其路径和 inode 找到其文件属性 + 文件内容,然后把它们加载到内存。

换言之,动态库也是一个文件,也有路径和 inode。当我们的程序需要访问动态库的代码时,那么这个动态库也需要被加载到内存。但是进程之间是相互独立的,一个进程是无法直接访问另一个进程的代码和数据的!所以动态库经过页表映射到进程地址空间中的共享区,而我们自己的进程的代码是位于正文代码区的,当我们的代码想调用动态库的方法时,只需要调整到共享区,执行动态库的代码,执行完后原路返回即可。

也可以理解为,建立映射后,我们执行的任何代码,都是在我们的进程地址空间中执行的!

接着,我们需要建立一个共识,一个进程是可能加载多个动态库的,反之,一个动态库也可能跟多个进程产生关联。所以在系统运行中一定会存在多个动态库,系统中有许多进程、文件,因此它们都需要被管理起来,同理,动态库也需要被操作系统所管理起来,换言之,操作系统对所有动态库的加载情况是非常清楚的。所以如果后续有另一个进程想要访问相同的动态库,操作系统能够分辨该库是否已经被加载,如果已经被加载到内存了,只需要在该进程的页表中做一下映射关系,该进程就能够跳转执行该库的代码。

  • C 标准库 libc.so 中有一个全局变量 errno,如果有多个进程共同访问这个库,并且自己的程序运行时都出问题了,即 errno 被写入了。它们访问的是同一个库,那么多进程之间会互相影响吗??

    共享区是位于堆栈之间的一段空间,属于用户空间。所以只要是对共享数据做写入操作,都会导致进程发生写时拷贝!我们之前讲用户缓冲区时就有过一个案例,在调用各种 fprint、fwrite 之后 fork() 创建子进程,C 式接口的输出信息输出了两次,因为缓冲区刷新的本质也是写入,所以进程对缓冲区做了写时拷贝,而缓冲区也是位于共享区中的!


2. 进程地址空间第二讲

2.1 程序没有加载前的地址

  • 程序编译完成之后,运行之前,程序内部有地址的概念吗?

    程序内部有地址。在 vs 编译调试时,如果你转到汇编上,你就可以看到编译之后的每条代码,都是有地址的,包括函数名、变量名,在编译之后都是以地址的形式进行地址,调用一个函数,会转变为 call 一个函数的地址。再诸如 C++ 的继承多态中的虚函数表,在编译时就已经为虚函数表分配地址了,包括表内有各种派生类的方法的地址。

    并且,在编译时为各段代码分配的地址,该地址是有分段的,诸如进程地址空间中分成了代码区、未初始化、已初始化等等各种区域,这是为了方便后续加载代码设计的,也就是说编译器,也是要考虑操作系统的(编译器编译的程序是要被操作系统加载的,所以需要照顾该程序加载到内存的问题,即进程地址空间的分段)。而在编译时给程序分配的地址,是虚拟地址!只不过如果该程序还没有被加载,我们更多的称为 逻辑地址。

    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 产生与位置无关码,产生与位置无关码的意思就是告诉编译器,不要再采用绝对编制了,直接用偏移量对库函数进行编制!

  • 静态库为什么不谈加载,不谈与位置无关这些概念?

    现在我们就对动静态库的区别的理解更进一步了,为什么只有动态库有加载的概念,因为静态库是拷贝策略,自己的程序调用了库方法,用什么我拷什么即可,需要的库方法已经成为我的可执行程序的一部分了,所以没有静态库需要加载的说法。而当我们的程序被加载到内存时,在程序内部,任何指令可是都已经硬编址好的了,方法都已经拷贝到我自己的程序内部了,哪一条指令加载到虚拟地址的哪一地址处,都是清清楚楚的,还需要用什么偏移量?!还需要怕找不到库的问题吗?!(库方法就在你程序里面)


如果感觉该篇文章给你带来了收获,可以 点赞👍 + 收藏⭐️ + 关注➕ 支持一下!

感谢各位观看!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2155718.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在ComfyUI中,Cross-Attention优化方案应该选哪个?

🐱‍🐉背景 在comfyui中,如果你使用了秋叶的启动器,会在高级选项中看到这样一行选项:Cross-Attention优化方案,右边有个下拉框,可以选择的选项有4个,如下图: 那么&#…

设计模式之观察者模式例题

答案:D 知识点: 观察者模式意图是定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新 状态模式 意图:允许一个对象在其内部状态改变时改变它的行为

【verilog】4. gtkwave的调用

文章目录 前言实验步骤 前言 进行 数电 FPGA 实验 实验步骤 将 GTKwave 的 bin 文件夹路径添加到 “系统环境变量” 的 “Path” 中 启动 debugger wizard, 设置观测信号 编译选择 2进制 文件 点击 start programming connect debugger 选择触发方式 Run 自动打开 gtkwave&a…

[Meachines] [Medium] Querier XLSM宏+MSSQL NTLM哈希窃取(xp_dirtree)+GPP凭据泄露

信息收集 IP AddressOpening Ports10.10.10.125TCP:135, 139, 445, 1433, 5985, 47001, 49664, 49665, 49666, 49667, 49668, 49669, 49670, 49671 $ nmap -p- 10.10.10.125 --min-rate 1000 -sC -sV -Pn PORT STATE SERVICE VERSION 135/tcp open msrp…

【若依RuoYi-Vue | 项目实战】帝可得后台管理系统(二)

文章目录 一、人员管理1、需求说明2、生成基础代码(1)创建目录菜单(2)添加数据字典(3)配置代码生成信息(4)下载代码并导入项目 3、人员列表改造(1)基础页面&a…

机器学习算法那些事 | TPAMI 2024.9 | FeatAug-DETR:通过特征增强丰富DETRs的一对多匹配

本文来源公众号“机器学习算法那些事”,仅用于学术分享,侵权删,干货满满。 原文链接:TPAMI 2024.9 | FeatAug-DETR:通过特征增强丰富DETRs的一对多匹配 论文标题:FeatAug-DETR: Enriching One-to-Many Mat…

智能指针:作用 | 使用 | 原理 | 内存泄漏

🌈个人主页: 南桥几晴秋 🌈C专栏: 南桥谈C 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据…

golang学习笔记4-基本数据类型

注:本人已有C,C,Python基础,只写本人认为的重点。 go的数据类型如下 由于bool和c类似,和go的区别是,bool的值只能取true和false,不能取整数,而且有默认值false。 一、整数型 整数型存放整数&…

设计模式之策略模式例题

答案:A 知识点: 策略模式又叫模板方法模式 它的意图是定义一个操作中的算法骨架。而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重新定义算法的某些特定步骤

3.5.2 __ipipe_init()之完成中断处理程序设置

点击查看系列文章 》 Interrupt Pipeline系列文章大纲-CSDN博客 原创不易,需要大家多多鼓励!您的关注、点赞、收藏就是我的创作动力! 3.5.2 __ipipe_init()之完成中断处理程序设置 __ipipe_init()最核心的就是__ipipe_enable_pipeline()&am…

分享两道算法题

分享两道算法题 王者荣耀分组 题目描述 部门准备举办一场王者荣耀表演赛,有 10 名游戏爱好者参与,分 5 为两队,每队 5 人。 每位参与者都有一个评分,代表着他的游戏水平。 为了表演赛尽可能精彩,我们需要把 10 名参赛…

十七,Spring Boot 整合 MyBatis 的详细步骤(两种方式)

十七,Spring Boot 整合 MyBatis 的详细步骤(两种方式) 文章目录 十七,Spring Boot 整合 MyBatis 的详细步骤(两种方式)1. Spring Boot 配置 MyBatis 的详细步骤2. 最后: MyBatis 的官方文档:https://mybatis.p2hp.com/ 关于 MyBa…

内网渗透-红日1

红日靶场1 渗透测试过程外网打点突破边界内网横向权限维持最后 渗透测试过程 本文章只说明渗透测试思路和技巧,对域靶场搭建不进行赘述 web-ip外网设置为 192.168.119.130,kali和外网ip同网段 外网打点 kali扫描目标ip nmap扫描目标网段   nmap -P…

【记录】大模型|Windows 下 Hugging Face 上的模型的通用极简调用方式之一

这篇文是参考了这篇,然后后来自己试着搭了一下,记录的全部过程:【翻译】Ollama|如何在 Ollama 中运行 Hugging Face 中的模型_ollama 导入 huggingface-CSDN 博客 另外还参考了这篇:无所不谈,百无禁忌,Win11 本地部署无…

【C++初阶】探索STL之——vector

【C初阶】探索STL之——vector 1.什么是vector2.vector的使用2.1 vector的定义2.2 vector iterator(迭代器)的使用2.3 vector空间问题2.4 vector的增删查改2.5 vector迭代器失效的问题2.5.1 vector常见迭代器失效的操作 3 动态二位数组 1.什么是vector vector其实就是一个可以…

iPhone16,超先进摄像头系统?丝滑的相机控制

iPhone 16将于9月20号正式开售,这篇文章我们来看下iPhone 16 在影像方面,有哪些升级和新feature。 芯片:采用第二代 3纳米芯片,A18。 摄像头配置: iPhone 16 前置:索尼 IMX714 ,1200 万像素&am…

SQL 多表联查

目录 1. 内联接(INNER JOIN) 2. 左外联接(LEFT JOIN) 3. 右外联接(RIGHT JOIN) 4. 全外联接(FULL JOIN) 5. 交叉联接(CROSS JOIN) 6. 自联接&#xff0…

简单题101. 对称二叉树 (python)20240922

问题描述: python: # Definition for a binary tree node. # class TreeNode(object): # def __init__(self, val0, leftNone, rightNone): # self.val val # self.left left # self.right rightclass Solution(object):def isSymm…

网络通信——OSI七层模型和TCP/IP模型

OSI模型 一.OSI七层模型 OSI(Open System Interconnect)七层模型是一种将计算机网络通信协议划分为七个不同层次的标准化框架。每一层都负责不同的功能,从物理连接到应用程序的处理。这种模型有助于不同的系统之间进行通信时,更…

pycharm连接远程linux服务器上的docker进行深度学习训练

实习过程中由于GPU都在服务器上,编辑代码很麻烦。并且服务器上配置了docker的环境,所以用pycharm连接远程服务器的docker进行深度学习,这样在本地调用远程服务器的GPU和环境,更方便一点,将这个过程记录下来&#xff0c…