程序的装入和链接

程序的装入和链接:

  1. 地址空间的概念
  2. 单道程序执行和多道程序执行
  3. 程序的链接
  4. 程序的装入

1:地址空间的概念

      地址空间分为名空间地址,逻辑地址,线性地址,物理地址

1.1名空间。这个没啥好解释的了,就是我们程序的命名:就java程序来说吧

  Class  A{}  这个类A的a就是一个名空间

1.2 逻辑地址:  源程序经过汇编或编译后,形成目标代码,每个目标代码都是以0为基址顺序进行编址的,原来用符号名访问的单元用具体的数据——单元号取代。这样生成的目标程序占据一定的地址空间,称为作业的逻辑地址空间,简称逻辑空间。

我们看一段汇编程序:就可以看出start开始的指令的逻辑地址是从0000开始的。逻辑地址的组成是

段基址+ 偏移地址

assume cs:code,ss:stack,ds:data ; 段说明, 自己定义的code对应代码段, stack对应堆栈段, data对应数据段

stack segment stack ; 堆栈段的定义

byte 64 dup(0)

stack ends

data segment ; 数据段的定义

msg byte 'hello world!$'

data ends

code segment ; 代码段的定义

start:      //这里的地址是0000X,然后后面每一个指令和数据占据对应的地址位置。 

            //start是名空间,然后编译,链接是对应的逻辑空间,逻辑空间开始的内存位置是0000H,装入之后运行对应的是物理空间。64位cpu有个内存映射寄存器

mov ax,data    //为什么这里要把数据段的地址给ax ? 因为要把地址段的地址给ds

mov ds,ax      //把地址段的地址给ds,给地址段赋值,所以说汇编给内存赋值是需要自己手动去做的,不像java是jvm自动去进行内存管理,不管是堆栈段还是代码段还是数据段,使用前都要获取段的值

mov dx,offset msg   //获取dx寄存器的位置,

mov ah,9

int 21h

mov ax,4c00h

int 21h

1.3 线性地址:某种程度上来说线性地址和逻辑地址差不多,但是80386之后因为内存的扩展和操作系统已经不是16位的了,所以线性地址已经是逻辑地址里面的段选择符+偏移地址组成的了,其实就是段选择符里面的GDT或者LDT里面的段基址+偏移地址构成了线性段地址。这是因为操作系统在单道程序和多道程序设计时候对内存管理的不同,最开始的系统是没有内存管理这个概念的。

 

逻辑地址转换成线性地址

3.物理地址: 物理地址这个更没啥说多了,就是内存硬件。那么计算机怎么找到内存硬件里面的阵列呢、就是通过半导体或者以前就是继电器实现的逻辑门电路,类似2-1选择器,8-5选择器之类的电路。所以指令啊,数据啊都是0和1,而0和1在计算机里面简单的表示就是高低电压,这高低电压和半导体就构成电路,这就构成了计算机。

现在的线性地址转换物理地址是有硬件支持的,这个硬件就是MMU。Cpu把虚拟地址或者说线性地址给到MMU,把虚拟地址转换成物理地址。

CPU执行单元发出的内存地址将被MMU截获,从CPUMMU的地址称为虚拟地址(Virtual Address,以下简称VA,而MMU将这个地址翻译成另一个地址发到CPU芯片的外部地址引脚上,也就是将VA映射成PA

 

2:程序执行

我们编写一个程序都是要经过编译,连接,装入这三个步骤的。我们所编写的程序应该算是半成品,需要windows加工才算是完全品。就像java程序的jar包或者war都算是半成品,需要jvm环境加工然后windows加工才能运行在机器上。

而汇编程序就是编译—链接-执行。

一个汇编程序,我们首先是编写asm文件,然后是编译会生成一个obj文件,然后使用link链接命令会生成一个exe可执行文件,exe或者dll文件结构都是PE文件,有兴趣的话可以去学一下PE系统。PE结构里面标记了文件的各个地址空间。

下图就是一个exe程序的pe结构了,64位和32的为PE差别不大的:

PE的节点里面是记录了程序的入口地址和程序的其他信息:

节名

描述

.text

代码段,里面的数据全都是代码

.data

可读写的数据段,存放全局变量或静态变量

.rdata

只读数据区

 .idata

导入数据区,存放导入表信息

.edata

导出数据区,导出表信息

.rsrc

资源区段,存放程序用到的所有资源,如图表,菜单等

.bss

未初始化数据区

.crt

用于支持C++运行时库所添加的数据

.tls

存储线程局部变量

.reloc

包含重定位信息

.sdata

包含相对于可被全局指针定位的可读写数据

.srdata

包含相对于可被全局指针定位的只读数据

.pdata

包含异常表

.debug$S

包含OBJ文件中的Codeview格式符号

.debug$T

包含OBJ文件中的Codeview格式类型的符号

.debug$P

包含使用预编译头时的一些信息

.drectve

包含编译时的一些链接命令

.didat

包含延迟装入的数据

 

程序运行的步骤:汇编的link就是链接过程,但是装入确是没有对应的工具,装入内存运行的功能是由execve(2)这一系统调用实现的。简单来讲,程序的装入主要包含以下几个步骤:

  • 读入可执行文件的头部信息以确定其文件格式及地址空间的大小;
  • 以段的形式划分地址空间;
  • 将可执行程序读入地址空间中的各个段,建立虚实地址间的映射关系;
  • bbs段清零;
  • 创建堆栈段;
  • 建立程序参数、环境变量等程序运行过程中所需的信息;
  • 启动运行。

 

源程序(source code)→ 预处理器(preprocessor)→ 编译器(compiler)→ 汇编程序(assembler)→ 目标程序(object code)→ 连接器(链接器,Linker)→ 可执行程序(executables)

Obj程序和exe程序的区别

本质上都是2进制代码, 但是 obj 没有经过链接的重定位,不是可执行的代码。

而且一个obj对应一个cpp编译单元,里面的外部符号都没解析出来。

比如a.cpp 通过b.h使用了一个 b.cpp里定义的函数 func(); 那么a.cpp生成的obj里面只会有func() 这个函数名的调用而已,也就是一个(对a.obj来说的)外部符号。 只有经过链接成为了.exe, 相应于a.obj里面的func()的调用才会更新成实际的b.obj里面的func()的实际地址。 对全局变量也是同样的道理。

 

3.程序的连接:

 不管是静态连接还是动态连接,连接的本质是把程序从目标目标组装成一个装入模块,静态连接和动态连接只是连接的时间点不一样。链接器主要有两个工作要做――符号解析和重定位。这个可以参考博客 https://www.cnblogs.com/virusolf/p/4946264.html

 

1.静态连接方式:

2.装入时动态链接:

3.运行时动态链接:

 

  1. 程序的装入:

装入是把程序整个放入内存中。

1.绝对装入: 绝对装入是把程序在一开始的时候就装入到整个内存。

2静态地址重装入:

在程序装入对目标代码装入内存的过程中完成,是指在程序开始运行前,程序中指令和数据的各个地址均已完成重定位,即完成虚拟地址到内存地址映射。地址变换通常是在装入时一次完成的,以后不再改变。

3.动态地址重定位装入:因为现在的系统是分页,一个程序不一定要把所有数据一开始就装入到内存中,所以根据运行情况的不同和需要,在运行时候把程序装入到内存。

 

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值