linux基础IO——动静态库——进程编址、进程执行、动态库加载

news2024/11/14 15:42:20

        前言:本节内容为基础IO部分的最后一节, 主要是为了讲一下动静态库里面的动态库如何加载到内存, 动态库的地址等等。 但是,这些内容牵扯到了程序的编址, 程序的加载, 进程的执行等等知识点, 所以,我们会从程序的编址讲起, 一直到进程的执行, 以及动态库加载结束。

        ps:本节内容涉及到了进程地址空间, 磁盘的内容, 建议友友们了解相关知识后再来观看。

目录

程序的编址

cpu为什么可以执行指令

程序加载后的地址

理解物理地址和逻辑地址(也叫做虚拟地址)

cpu执行进程命令流程

动态库的加载


程序的编址

        在谈程序的编址之前, 我们要谈一个事情, 就是程序里面有没有地址呢?

        答案是有的, 当我们编译形成了程序, 比如1.exe, 2.exe, 这个时候, 程序内部其实已经生成了地址了

  •         就比如我们调试的时候能够进入反汇编, 进入反汇编之后, 我们就会发现, 我们的每一行代码都是有地址的, 比如调用一个函数, call后面就会跟一个函数名。 而实际上呢? 我们编译之后, 无论是我们的函数名, 还是我们的变量名, 实际上都没有了, 都会被转化为地址。 所以, 我们对应的一个可执行程序在编译完成之后有没有地址呢? 答案是有的!!!
  •         又比如我们我们回忆一下c++里面的多态, 还记不记得多态里面有一张虚函数表, 程序呢在编译的时候就已经形成了这张虚函数表, 里面填充的是每一个子类具体的方法实现。

        什么意思呢? 就比如我们看下图, 就比如我们有下图的一串伪代码。 然后呢, 当我们编译的时候, 编译到main函数里面的func(), 也就是0x5地址的时候, 就将我们的func()编译成了0x1(因为func()在0x1处)——这就是当我们的代码在编译的时候就已经编好址了, 有了地址之后就不要函数名了。

        当我们程序还没有加载的时候, 我们的程序是有地址的, 在很早之前,我们的地址是采用短地址 + 偏移量的方式。 现在我们的程序内部的编制方式已成变成了平坦模式——就是从0 ~ 4GB访问方式而且这个平坦模式就是按照我们的进程地址空间的规则, 把我们的代码进行编址, 也就是说,这个平坦模式编制规则下生成的程序的内部的编址, 其实就是加载到内存后, 进程建立好映射关系后的虚拟地址!!!

那么, 既然磁盘中程序内部的编址和我们在虚拟地址空间的一样, 所以我们下面的地址(右边的才是真正的内部的地址,也可以叫做逻辑地址!! 左边的是伪汇编), 其实就已经可以叫做虚拟地址了!!!


        

现在我们打开文件, 在文件中写入下面的代码:

我们生成可执行程序a.out:

然后将我们的程序反汇编出来,如同下图:

        我们可以看到, 左边的这个红框框是什么呢? 这个东西, 其实就是每一行所对应的地址

        我们同样可以看到, 这些地址, 每两个之间的偏移量大小是不同的。 有可能偏移一个, 有可能偏移两个, 这个是为什么呢?

        那么这个主要是因为这些指令每个占用的二进制大小不同, 有的占用大一些, 有的占用小一些。       

        未来呢, 我们的这些左边的地址可以不用存在, 但是指令是有长度的, 而且像是函数调用的汇编代码还会有地址在指令中。 

        就比如这串指令, 也就是说, 我们的左边的地址可以不要。 只需要有我们的地址, 有第一条指令, 就可以依次向下执行了

cpu为什么可以执行指令

那么cpu是怎么知道要去执行这些指令呢? ——这就比如一个刚出生的婴儿, 变成了一个小孩, 这个小孩一开始可能不会说长句子, 只认识爸爸妈妈爷爷奶奶这样的词语, 这个小孩呢, 只需要知道一些简单的词语。 它的老爹呢, 就会和他说, 玩具拿起来,这个小孩听到“玩具“、”拿起来”两个单词, 就会执行相应的动作。——对应的呢, 这里面的每一个汇编语句都是一个操作, 一个小孩能知道老爹老妈让自己去干什么, 从根本上是老爸教过自己“玩具” “拿起来”这些词语。 而这里的小孩就是cpu, 这些指令就是“玩具“”拿起来”这样的单词。 cpu能认识这些指令, 就是因为cpu提前内置了能够认识这些指令的东西。 ——只不过可能不是指令本身, 可能是二进制, 比如0001, 00002, 0003等等cpu呢, 只认识一些基本指令, 经过组合这些指令, 就能组合成各种各样的短语汇编, 短语汇编一多, 一长, 就能组合成各种计算的任务!!!

程序加载后的地址

理解物理地址和逻辑地址(也叫做虚拟地址)

        看上面这个图片, 我们知道, 磁盘程序内部是有地址的程序加载到物理内存是一个块一个块的加载的。 而这些块加载到内存又是有自己的物理地址的。 ——也就是说, 这些指令, 要不要占据内存空间呢?——答案是的, 因为加载到内存的就是这些指令的二进制!(而我们知道, 这些指令在整个的程序的内部也是有属于自己的逻辑地址。)

        什么意思呢?就比如一个学生, 我们在学校里有自己对应的学号, 比如说3号。 而到了教室呢, 教室里有我们对应的桌子, 比如说是第一排第二列的位置。 那么这个学生, 他是不是就天然有了两个地址, 一个是自己在学校的地址, 这个地址是3号, 另一个是在教室的地址, 这个地址是第一排第二列。 那么对应的, 我们编译好的指令呢?就比如上面的call指令, 这个call指令在磁盘里的时候有自己的一个地址, 也就是自己的逻辑地址。 就对应着我们的学生的学号!!!这个call指令加载到内存的时候有自己的一个另一个地址, 也就是自己的物理地址。 就对应着我们学生在教室的位置!!

        那么友友们就可能会疑惑, 怎么一会儿这个地址, 一会儿那个地址? ——是的, 就是一会儿这个地址, 一会儿那个地址。 还是那个学生, 当那个学生在教室外边的时候, 他是处在学校中的, 它拥有的是自己的学号。 是因为这个学号, 让他成为了学校的医院, 当这个学生去了教室上课, 做到了第一排第二列的桌子上的时候, 这个桌子就成了他的编号。 老师可能不认识他, 管他叫张三还是李四, 但是老师一定知道他的桌子在哪个位置。而张三从教室外做到教室内的这个过程, 就是加载的过程!!!对应的就是我们指令从磁盘加载到物理内存的过程!!!

cpu执行进程命令流程

现在看下面这张图:

这张图中有cpu, 有进程, 有地址空间, 有页表, 有物理内存, 有磁盘。 我们知道磁盘程序要映射到内存, 然后进程地址空间要和物理空间进行映射。整个的连接后的图我们也知道,但是我们不知道的是这里面的细节, 就比如是先程序加载到内存中呢?还是先地址空间映射到页表呢?还是先物理内存映射到页表呢?——而现在我们就要说一下这个这流程了:

        ps:图中文字可以看一下, 但是顺序可能会有问题, 所以友友们可以先看下面的文字, 再回过头看一下图中的文字, 是一模一样的。

  1.         其实我们的可执行程序在编译的时候, 除了形成本身代码的各种指令, 还会生成一个入口地址entry, 并且这个可执行程序的入口地址所在的位置是这个可执行程序的表头。 
  2.         那么这个entry的入口地址, 是不是物理地址呢? 答案是不是的, 很显然不是!!!因为这个entry一开始是在可执行程序里内部的,是磁盘里面的!!!而可执行程序根本没有物理地址的概念!!!我们的物理地址是什么? 物理地址就是加载程序的时候的一个随机的地址, 磁盘在加载内存之前根本看不到物理内存, 它怎么保存一个物理地址呢?
  3.         那么, 我们的entry不是物理地址之后, 那么很显然, entry是逻辑地址!又因为我们的cpu中有一个寄存器, 叫做EIP/PC指针,这个寄存器保存的就是我们下一步的地址, 有了这个, 我们的cpu就能知道他下一步要做什么。 
  4.         而要让cpu将进程执行起来, 那么第一步就是将entry加载到我们的EIP寄存器中, 然后找到对应的虚拟内存。        
  5.         然后, 虚拟内存又通过页表找到物理内存, 那么问题来了, 我们知道, 我们把程序的入口喂给了cpu,cpu就去执行指令了,然后虚拟地址映射到了页表, 也就是现在这个时刻。 而此时页表并没有映射物理内存啊, 那么是不是就势必会发生缺页中断?那么发生了缺页中断后, 我们的程序就可以加载到物理内存中了!!而一旦加载到了物理内存中, 那么程序天然就具备了物理地址!!所以, 加载进来后, 我们的页表就有了左侧的虚拟地址, 以及右侧的物理地址。 
  6.          而且我们又知道, 程序加载进入内存一定会以4kb的块为一个单位, 那么这4kb里面就有这许多的代码指令, 其实每个代码都有自己的相对于entry的偏移量——其实就有逻辑地址, 而这个逻辑地址在程序内部已经准备好了, cpu可以直接拿来使用。并且我们的块一旦加载到物理内存, 就天然的具备了物理地址。 而这两个虚拟, 物理地址一填。 此时, 页表的映射关系就会创建好了!!!

以上, 就是cpu执行进程的整个流程!!! 

        当我们cpu执行指令的时候, 执行到了函数指令。 ——cpu内部读指令, 内部可能有数据, 也可能有地址。 ——这个地址是什么呢?——虚拟地址(本质上就是逻辑地址, 但是叫法变了, 因为程序加载到内存后, 就没有逻辑地址的概念了, 这个逻辑地址是磁盘里面的概念, 进程里叫虚拟地址)

        那么此时我们说执行到了这个函数指令, 怎么办呢?——其实没有关系, 只要去执行这个地址处的指令即可, 也就是说, 如果call了一个0x1111地址, 那么就在地址空间去找到0x1111处的地址, 再通过页表映射我们的物理地址去执行就可以了。——如果没有映射关系怎么办呢?——那就发生缺页中断。 

        那么这里我们这里就可以下一个结论——就是从我们程序打开开始执行, 到一步步执行指令, 一直到这个程序执行完毕。 中间的过程, 用到的指令, 除了页表映射的块地址, 其他的全部为虚拟地址!!!

        我们的cpu在这个过程中, 只接收到我们块中的虚拟地址, cpu拿到虚拟地址, 但是cpu想要真正的指令, 这个虚拟地址再怎么厉害也只是指令之间的虚拟地址, cpu想要找到这些指令还是需要物理地址, 然后cpu就会转化虚拟地址为物理地址——通过页表和mmu这样的硬件。——然后就找到了我们对应物理地址处的正确的指令, 然后cpu再找到虚拟地址, 执行下一跳指令, 就完成了一个循环的过程!!!

动态库的加载

        我们再来画一下上面的图, 不同的是, 这一次要新添一个新的文件——libc.so, 这个libc.so是一个动态库文件。 

        假如这个代码段里有一个函数 ——printf, 这个printf我们知道在编译的时候,编译出来的是一个地址。 就比如printf->0x1234, 而这个0x1234就能找到我们的动态库的位置。 

        libc.so放到物理内存后, 就会发生缺页中断, 然后将libc.so映射到虚拟地址空间的共享区里面。

        但是,这里的主要问题是, 共享区很大, libc.so具体的映射到共享区哪里呢?那么还记不记得, 我们的程序代码中保存的libc.so的地址是0x1234, 那么, 未来, 我们的代码在去共享区寻找共享库的时候, 就只能去我们的0x1234去寻找了。 

        因为我们说过, 我们程序的内部的地址, 都是编译好的线性地址。 也就是说, 我们的库, 一定会在地址空间的固定地址处。 但是, 我们要知道, 我们的库, 一定会在地址空间的固定地址处, 但是我们要知道, 我们的库的数量是不一定的, 可是我们的库有十几个二十几个, 所以, 我们怎么知道,这些库的地址不会发生冲突呢?动态库被加载到固定的地址空间是不可能的!——那么也就是说, 我们必须把我们的库设计成可以在虚拟内存中, 任意位置加载的库!!!——怎么形成呢?——就是让库在形成的时候, 让自己内部函数不要采用绝对编制, 而是采用相对以逻辑地址加偏移量的方式, 也可以说成是只表示函数在库中的偏移量即可!!!

        什么意思呢? 就是说, 我们的0x1234不是这个库在虚拟空间内的绝对地址, 而是内部的函数, 比如printf在库内部相对于库的起始位置的偏移量是多少。 

        所以呢, 现在的规则就是, 当我们的动态库加载到虚拟地址空间里, 就可以随便放, 形成了我们所谓的库, 只需要最终帮我们记住, 我们的libc.so在虚拟地址当中的起始地址即可。 因为操作系统要对库做管理, 所以, 这个库被加载到了地址空间的什么位置, 也是要被知道的。 那么未来我们libc.so:start如果是90000, 那么我们就可以找到对应的printf方法所在的地址是91234, 那么我们就能去这里寻找printf方法

        那么gcc在生产目标文件时使用的与位置无关码FPIC是什么意思呢?——就是直接用偏移量对库中函数进行编址!!!并且我们在编址的时候可以在虚拟内存中的任意位置加载。——这里有一个问题, 就是静态库为什么不谈加载不谈与位置无关??因为我们静态库的方法是直接拷贝到程序里面的, 就相当于库中的方法就是这个程序的方法了。 就相当于我们在编址的时候将我们的拷贝来的方法一起编进来了

 ————以上就是本篇的全部内容, 本篇内容到此就结束啦, 感谢友友的阅读, 下面是本节的笔记, 和正文几乎一样的, 觉得本节内容有用的话可以保存方便查阅哦

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

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

相关文章

代码随想录算法训练营第二十天| 39. 组合总和、40. 组合总和Ⅱ、131. 分割回文串

今日内容 leetcode. 39 组合总和leetcode. 40 组合总和Ⅱleetcode. 131 分割回文串 Leetcode. 39 组合总和 文章链接:代码随想录 (programmercarl.com) 题目链接:39. 组合总和 - 力扣(LeetCode) 本题不太一样的是可以对同一个数…

自己实现一个分布式锁

我的博客大纲 我的后端学习大纲 1.setnx命令: 2.基于Redis实现分布式锁: 2.1.基本实现: 借助于redis中的命令setnx(key, value),key不存在就新增,存在就什么都不做。同时有多个客户端发送setnx命令,只有一…

20240902-VSCode-1.19.1-部署vcpkg-win10-22h2

20240902-VSCode-1.19.1-部署vcpkg-win10-22h2 软件环境 标签:C++ VSCode mingw gcc13 vcpkg cmake分栏:C++操作系统:Windows10 x64 22h2一、安装VScode-1.19.1 请参考另一篇文章《20240717-VSCode-1.91.1-部署gcc13-C++23-win10-22h2》。 二、安装cmake 本文流程需要安…

【微处理器系统原理与应用设计第八讲】程序设计的开发框架包括编程语言、程序的基本要素、汇编程序结构、集成开发环境

一、编程语言 从处理器的角度看:一个指令只是一个操作,那么执行多条指令构成的程序就是完成一个完整功能的操作。 从程序执行的角度看:处理器读取指令后译码执行,完成所有操作。 从程序设计的角度看:用指令来描述所…

尽快更新!Zyxel 路由器曝出 OS 命令注入漏洞,影响多个版本

近日,Zyxel 发布安全更新,以解决影响其多款商用路由器的关键漏洞,该漏洞可能允许未经认证的攻击者执行操作系统命令注入。 该漏洞被追踪为 CVE-2024-7261,CVSS v3 得分为 9.8,是一个输入验证故障,由用户提…

“榆”您相约|遨游矿用煤安防爆手机助力煤矿作业安全增效

金秋九月结硕果,丹桂飘香迎盛会。2024年9月13日至15日,第十八届榆林国际煤炭暨高端能源化工产业博览会(以下简称“榆林国际煤博会”)即将在榆林会展中心盛大启幕。本次博览会以“能源新时代,低碳新榆林”为主题&#x…

SAP学习笔记 - 开发03 - CDSView开发环境搭建,Eclipse中连接SAP,CDSView创建

上一章讲了BTP的账号创建,环境搭建等内容。 SAP学习笔记 - 开发02 - BTP实操流程(账号注册,BTP控制台,BTP集成开发环境搭建)-CSDN博客 本章继续讲SAP开发。 - CDSView 的开发环境(Eclipse)搭建…

世界公认十大护眼灯数据出炉!一文看懂孩子用的台灯哪个牌子好

近年来,随着科技的迅猛发展,诸如智能手机、电脑等电子设备在工作、学习及娱乐中的应用日益广泛,人们对这些设备的依赖程度也随之加深。然而,长时间面对屏幕不可避免地给眼睛带来伤害,如眼疲劳、干燥甚至近视等问题。因…

线程池概念介绍

一、初始化线程的四种方式 1.继承Thread 2.实现Runnable 3.实现Callable接口FutureTask 4.线程池:两种主要初始化方式Executors.newFixedThreadPool()或new ThreadPoolExecutor() 方式1和方式2:主进程无法获取线程的运算结果。 方式3:主进程可以获取运算结果,但是…

MMO地图传送

本篇由以下四个点讲解: 创建传送点 传送点配置 编辑器扩展:传送点数据生成 传送协议与实现 创建传送点 建碰撞器触发 //位置归零 建一个传送门cube放到要传送的位置(这个teleporter1是传出的区域 这是从另一张地图传入时的传送门 创建一…

mysql笔记—基础

1.SQL语句 DDL(数据库对象操作)、DML(增删改)、DQL(查询)、DCL(用户和权限操作) 2.DDL: 1.数据库操作: show databases; create database []; use []; sele…

网络工程师学习笔记——无线通信网

移动通信 从1G到3G都是针对语音通话设计的,只有4G才可以与Internet衔接 1978年美国贝尔实验室开发了高级移动电话系统(AMPS),可以随时随地的进行通信,采用蜂窝技术解决了公用通信系统所面临的大容量要求和…

初识Linux · 进度条

目录 前言: 1 缓冲区和回车换行 2 进度条 前言: 我们目前学习了些许知识,已经足够支持我们写一个非常非常小的项目了,即进度条,相信大家都有过下载游戏,等待游戏更新完成的时候,那么此时就有…

电器维修系统小程序的设计

管理员账户功能包括:系统首页,个人中心,管理员管理,客服聊天管理,基础数据管理,公告管理,新闻信息管理 微信端账号功能包括:系统首页,新闻信息,我的 开发系…

站长工具 API 接口,助力网站管理新高度

站长工具是一款非常实用的网站管理工具,通过其提供的API接口,可以轻松实现多种功能,如域名反查、域名备案查询、IPV6归属地查询等。这些功能可以帮助网站管理员更好地管理和优化自己的网站。以下是简单的代码示例,展示了如何使用站…

康姿百德公司官网柔压磁性枕豪华款高科技邂逅温柔夜活力满满!

康姿百德豪华柔压磁性枕慢回弹拥抱每寸肌肤,科技面料让呼吸自由夜夜好眠不是梦! 现代家居设计不仅注重美观,更强调功能性和舒适性。康姿百德柔压磁性枕(豪华款)通过将高科技材料与创新设计结合,为我们的家…

Java反序列化漏洞-TemplatesImpl利用链分析

文章目录 一、前言二、正文1. 寻找利用链2. 构造POC2.1 生成字节码2.2 加载字节码1)getTransletInstance2)defineTransletClasses 2.3 创建实例 3. 完整POC 三、参考文章 一、前言 java.lang.ClassLoader#defineClass defineClass可以加载字节码&…

最大N个数与最小N个数的和

题目描述 给定一个数组,编写一个函数来计算它的最大N个数与最小N个数的和。你需要对数组进行去重 说明: 数组中数字范围[0,1000]最大N个数与最小N个数不能有重叠,如有重,输入非法返回-1输入非法返回-1 输入描述 第一行输入M&a…

uboot:配置编译

了解BSP 在嵌入式系统中,BSP(Board Support Package)被称为板级支持包或板级支持软件。它是一组针对特定硬件平台的软件支持包,为开发人员提供了一个统一的接口层,简化了硬件和软件之间的交互。BSP的主要功能和特点如…

Python数据分析-绘制图表

示例1: from pyecharts.charts import Bar # 柱状图 from pyecharts import options as optsfrom pyecharts.render import make_snapshotbar Bar() bar.add_xaxis([一月, 二月, 三月, 四月, 五月]) bar.add_yaxis("销售额", [10, 20, 15, 25, 30])# 配…