程序如何运行,编译、链接、装入?

点击蓝字

1865ebcafa23a86cdd5be8a3b7857cc8.png

关注我们

因公众号更改推送规则,请点“在看”并加“星标”第一时间获取精彩技术分享

来源于网络,侵删

一、地址概念和程序如何运行

在多道程序环境下,要使程序运行,必须先为之创建进程。而创建进程的第一件事,便是将程序和数据装入内存。如何将一个用户源程序变为一个可在内存中执行的程序,通常都要经过以下几个步骤:

首先是要编译

由编译程序(Compiler)将用户源代码编译成cpu可执行的目标代码,产生了若干个目标模块(Object  Module)(即若干程序段)。形成的目标代码,每个目标代码都是以0为基址顺序进行编址,原来用符号名访问的单元用具体的数据——单元号取代。这样生成的目标程序占据一定的地址空间,称为作业的逻辑地址空间,简称逻辑空间

在逻辑空间中每条指令的地址和指令中要访问的操作数地址统称为逻辑地址  。很简单,逻辑地址就是你源程序里使用的地址,或者源代码经过编译以后编译器将一些标号,变量转换成的地址。

其次是链接

由链接程序(Linker)将编译后形成的一组目标模块(程序段),以及它们所需要的库函数链接在一起,形成一个完整的装入模块(Load  Module)

最后是装入(地址重定位)

由装入程序(Loader)将装入模块装入物理内存。物理内存是真实存在的插在主板内存槽上的内存条的容量的大小。

物理内存内存是由若干个存储单元组成的,每个存储单元有一个编号,这种编号可唯一标识一个存储单元,称为内存地址(或物理地址)。我们可以把内存看成一个从0字节一直到内存最大容量逐字节编号的存储单元数组,即每个存储单元与内存地址的编号相对应。

装入模块虽然具有统一的地址空间,但它仍是以“0”作为参考地址,即是浮动的。要把它装入内存执行,就要确定装入内存的实际物理地址,并修改程序中与 地址有关的代码,这一过程叫做地址重定位。地址重定位主要是把逻辑地址转换成物理内存绝对地址,这个工作又称为地址映射。

图 4-2 示出了这样的三步过程。

566ef88ae01c3dfb48faf832512955d6.jpeg

图4-2  对用户程序的处理步骤

二. 程序的链接

源程序经过编译后,可得到一组目标模块,再利用链接程序将这组目标模块链接,形成装入模块。根据链接时间的不同,可把链接分成如下三种:

(1) 、 静态链接。在程序运行之前,先将各目标模块及它们所需的库函数,链接成一个完整的装配模块,以后不再拆开。我们把这种事先进行链接的方式称为静态链接方式。

(2)、  装入时动态链接。这是指将用户源程序编译后所得到的一组目标模块,在装入内存时,采用边装入边链接的链接方式。

(3)、  运行时动态链接。这是指对某些目标模块的链接,是在程序执行中需要该(目标)模块时,才对它进行的链接。

1.静态链接方式(Static Linking)

我们通过一个例子来说明在实现静态链接时应解决的一些问题。在图 4-4(a)中示出了经过编译后所得到的三个目标模块A、B、C,它们的长度分别为 L、M和N。在模块A中有一条语句CALL B,用于调用模块B。在模块B中有一条语句CALL C,用于调用模块C。B和C都属于外部调用符号,在将这几个目标模块装配成一个装入模块时,须解决以下两个问题:

(1)  对相对地址进行修改。在由编译程序所产生的所有目标模块中,使用的都是相对地址,其起始地址都为 0,每个模块中的地址都是相对于起始地址计算的。

在链接成一个装入模块后,原模块B和 C在装入模块的起始地址不再是 0,而分别是 L和 L+M,所以此时须修改模块B和C中的相对地址,即把原B中的所有相对地址都加上 L,把原 C中的所有相对地址都加上L+M。

(2)  变换外部调用符号。将每个模块中所用的外部调用符号也都变换为相对地址,如把B 的起始地址变换为 L,把 C 的起始地址变换为 L+M,如图 4-4(b)所示。

这种先进行链接所形成的一个完整的装入模块,又称为可执行文件。通常都不再拆开它,要运行时可直接将它装入内存。这种事先进行链接,以后不再拆开的链接方式,称为静态链接方式。

e28916270acd3b03ba3ec8b0172f731f.jpeg

图 4-4 程序链接示意图

2.装入时动态链接(Load-time Dynamic Linking)

用户源程序经编译后所得的目标模块,是在装入内存时边装入边链接的,即在装入一个目标模块时,若发生一个外部模块调用事件,将引起装入程序去找出相应的外部目标模块,并将它装入内存,还要按照图4-4所示的方式来修改目标模块中的相对地址。装入时动态链接方式有以下优点:

(1) 、 便于修改和更新。对于经静态链接装配在一起的装入模块,如果要修改或更新其中的某个目标模块,则要求重新打开装入模块。这不仅是低效的,而且有时是不可能的。若采用动态链接方式,由于各目标模块是分开存放的,所以要修改或更新各目标模块是件非常容易的事。

(2)、  便于实现对目标模块的共享。在采用静态链接方式时,每个应用模块都必须含有其目标模块的拷贝,无法实现对目标模块的共享。但采用装入时动态链接方式,OS则很容易将一个目标模块链接到几个应用模块上,实现多个应用程序对该模块的共享。

3.运行时动态链接(Run-time Dynamic Linking)

在许多情况下,应用程序在运行时,每次要运行的模块可能是不相同的。但由于事先无法知道本次要运行哪些模块,故只能是将所有可能要运行到的模块都全部装入内存,并在装入时全部链接在一起。显然这是低效的,因为往往会有些目标模块根本就不运行。

比较典型的例子是作为错误处理用的目标模块,如果程序在整个运行过程中都不出现错误,则显然就不会用到该模块。近几年流行起来的运行时动态链接方式,是对上述在装入时链接方式的一种改进。

这种链接方式是将对某些模块的链接推迟到程序执行时才进行链接,亦即,在执行过程中,当发现一个被调用模块尚未装入内存时,立即由OS去找到该模块并将之装入内存,把它链接到调用者模块上。凡在执行过程中未被用到的目标模块,都不会被调入内存和被链接到装入模块上,这样不仅可加快程序的装入过程,而且可节省大量的内存空间。

三. 程序的装入(地址的变换)

为了阐述上的方便,我们先介绍一个无需进行链接的单个目标模块的装入过程。该目标模块也就是装入模块。在将一个装入模块装入内存时,可以有绝对装入方式可重定位装入方式动态运行时装入方式,下面分别简述之。

1.绝对装入方式(Absolute Loading Mode)

在编译时,如果知道程序将驻留在内存的什么位置,那么,编译程序将产生绝对地址的目标代码。即按照物理内存的位置赋予实际的物理地址。例如,事先已知用户程序(进程)驻留在从R处开始的位置,则编译程序所产生的目标模块(即装入模块)便从R处开始向上扩展。

绝对装入程序按照装入模块中的地址,将程序和数据装入内存。装入模块被装入内存后,由于程序中的逻辑地址与实际内存地址完全相同,故不须对程序和数据的地址进行修改。程序中所使用的绝对地址,既可在编译或汇编时给出,也可由程序员直接赋予。

这个方式的优点:是CPU执行目标代码快

缺点:

1)是由于内存大小限制,能装入内存并发执行的进程数大大减少

2)编译程序必须知道内存的当前空闲地址部分和其地址,并且把进程的不同程序段连续地存放起来,编译非常复杂。由于程序
因此,通常是宁可在程序中采用符号地址,然后在编译或汇编时,再将这些符号地址转换为绝对地址。

如何把虚拟内存地址空间变换到内存唯一的一维物理线性空间?涉及到两个问题:

一是虚拟空间的划分问题。

二是把虚拟空间中已经链接和划分好的内容装入内存,并将虚拟空间地址映射内存地址的问题。即地址映射。地址映射就是建立虚拟地址与内存地址的关系。

2.静态地址重定位(可重定位装入方式 Relocation Loading Mode)

绝对装入方式只能将目标模块装入到内存中事先指定的位置。在多道程序环境下,编译程序不可能预知所编译的目标模块应放在内存的何处,因此,绝对装入方式只适用于单道程序环境。

在多道程序环境下,所得到的目标模块的起始地址通常是从 0 开始的,程序中的其它地址也都是相对于起始地址计算的。此时应采用可重定位装入方式,根据内存的当前情况,将装入模块装入到内存的适当位置。

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

值得注意的是, 在采用可重定位装入程序将装入模块装入内存后, 会使装入模块中的所有逻辑地址与实际装入内存的物理地址不同,图4-3示出了这一情况。

053afc318774744d5905d3421750ff18.jpeg

图 4-3 作业装入内存时的情况

例如,在用户程序的 1000 号单元处有一条指令LOAD 1,2500,该指令的功能是将 2500 单元中的整数 365 取至寄存器 1。但若将该用户程序装入到内存的 10000~15000号单元而不进行地址变换, 则在执行11000号单元中的指令时,它将仍从 2500 号单元中把数据取至寄存器1而导致数据错误。

由图4-3 可见,正确的方法应该是将取数指令中的地址 2500 修改成 12500,即把指令中的相对地址 2500 与本程序在内存中的起始地址 10000 相加,才得到正确的物理地址12500。除了数据地址应修改外,指令地址也须做同样的修改,即将指令的相对地址 1000 与起始地址 10000 相加,得到绝对地址 11000。

优点:无需硬件支持

缺点:

1)程序重定位之后就不能在内存中搬动了;

2)要求程序的存储空间是连续的,不能把程序放在若干个不连续的区域中。

3.动态地址重定位(动态运行时装入方式Dynamic Run-time Loading)

可重定位装入方式可将装入模块装入到内存中任何允许的位置,故可用于多道程序环境;但这种方式并不允许程序运行时在内存中移动位置。因为,程序在内存中的移动,意味着它的物理位置发生了变化, 这时必须对程序和数据的地址(是绝对地址)进行修改后方能运行。

然而,实际情况是,在运行过程中它在内存中的位置可能经常要改变,此时就应采用动态运行时装入的方式。

动态地址重定位:不是在程序执行之前而是在程序执行过程中进行地址变换。更确切的说,是把这种地址转换推迟到程序真正要执行时才进行,即在每次访问内存单元前才将要访问的程序或数据地址变换成内存地址。

动态重定位可使装配模块不加任何修改而装入内存。为使地址转换不影响指令的执行速度,这种方式需要一个重定位寄存器的支持,

优点:

1)目标模块装入内存时无需任何修改,因而装入之后再搬迁也不会影响其正确执行,这对于存储器紧缩、解决碎片问题是极其有利的;

关注公众号:C语言中文社区,免费领取300G编程资料

2)一个程序由若干个相对独立的目标模块组成时,每个目标模块各装入一个存储区域,这些存储区域可以不是顺序相邻的,只要各个模块有自己对应的定位寄存器就行。

缺点:需要硬件支持。

四. Windows NT动态链接库

5.1. 构造动态链接库

DLL是包含函数和数据的模块,它的调用模块可为EXE或DLL,它由调用模块在运行时加载;加载时,它被映射到调用进程的地址空间。在VC中有一类工程用于创建DLL。

  • 库程序文件 .C:相当于给出一组函数定义的源代码;

  • 模块定义文件 .DEF:相当于定义链接选项,也可在源代码中定义;如:DLL中函数的引入和引出(dllimport和dllexport)。

  • 编译程序利用 .C文件生成目标模块 .OBJ

  • 库管理程序利用 .DEF文件生成DLL输入库 .LIB和输出文件 .EXP

  • 链接程序利用 .OBJ和 .EXP文件生成动态链接库 .DLL。

5.2. DLL的装入方法

1)装入时动态链接(load-time):

  • 在编程时显式调用某个DLL函数,该DLL函数在可执行文件中称为引入(import)函数。

  • 链接时需利用 .LIB文件。在可执行文件中为引入的每个DLL建立一个IMAGE_IMPORT_DESCRIPTOR结构。

  • 在装入时由系统根据该DLL映射在进程中的地址改写Import Address Table中的各项函数指针。Hint是DLL函数在DLL文件中的序号,当DLL文件修改后,就未必指向原先的DLL函数。在装入时,系统会查找相应DLL,并把它映射到进程地址空间,获得DLL中各函数的入口地址,定位本进程中对这些函数的引用

装入时动态链接过程:

(注:Import Address Table是在装入时依据DLL模块的加载位置确定)。

7f0f47c7c7925bfad94b7698c404f733.png

「DLL函数的调用过程:」

a30f8f4e0ce4bd2a3134cf9010da7dff.png

2)运行时动态链接(run-time):

在编程时通过LoadLibrary(给出DLL名称,返回装入和链接之后该DLL的句柄), FreeLibrary, GetProcAddress(其参数包括函数的符号名称,返回该函数的入口指针)等API来使用DLL函数。这时不再需要引入库(import library)。

  • LoadLibrary或LoadLibraryEx把可执行模块映射到调用进程的地址空间,返回模块句柄;

  • GetProcAddress获得DLL中特定函数的指针,返回函数指针;

  • FreeLibrary把DLL模块的引用计数减1;当引用计数为0时,拆除DLL模块到进程地址空间的映射;

运行时动态链接的例子:

HINSTANCE hInstLibrary;//模块句柄定义
DWORD (WINAPI *InstallStatusMIF)(char*, char*, char*, char*, char*, char*, char*, BOOL);//函数指针定义
if (hInstLibrary = LoadLibrary("ismif32.dll"))//映射
{InstallStatusMIF = (DWORD (WINAPI *)(char*,char*,char*, char*, char*, char*, char*, BOOL))      GetProcAddress(hInstLibrary, "InstallStatusMIF");//获得函数指针if (InstallStatusMIF){if (InstallStatusMIF(“office97”, “Microsoft”, “Office 97”, “999.999”, “ENU”, “1234”, ”Completed successfully”, TRUE) !=0)//调用DLL模块中的函数{}}FreeLibrary(hInstLibrary);//拆除映射
}
如果你年满18周岁以上,又觉得学【C语言】太难?想尝试其他编程语言,那么我推荐你学Python,现有价值499元Python零基础课程限时免费领取,限10个名额!
▲扫描二维码-免费领取

戳“阅读原文”我们一起进步

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

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

相关文章

python举两种字符串引号的例子_python里的单引号和双引号的有什么作用

在Python当中表达字符串既可以使用单引号,也可以使用双引号,那两者有什么区别吗?python单引号和双引号的区别简单来说,在Python中使用单引号或双引号是没有区别的,都可以用来表示一个字符串。但是这两种通用的表达方式…

枚举对象注释_如何以及何时使用枚举和注释

枚举对象注释本文是我们名为“ 高级Java ”的学院课程的一部分。 本课程旨在帮助您最有效地使用Java。 它讨论了高级主题,包括对象创建,并发,序列化,反射等。 它将指导您完成Java掌握的旅程! 在这里查看 !…

background 互联网图片_cssbackground-image和layer-background-image的区别

layer-background-image语法:layer-background-image : none | url (url)参数:none :  无背景图url :  使用绝对或相对地址指定背景图像说明:设置或检索对象整个区域的背景图像。示例:code {position: absolute;top: 100px; lef…

纪事本 乱码_纪事日记–可自定义的数据存储

纪事本 乱码总览 使任何数据结构或算法尽可能快的方法是使代码完全执行您想要的操作,而无需执行其他操作。 建立一个可以做任何人想做的每件事的数据存储的问题是,它做得特别不好。 自定义数据存储在性能方面可以实现什么? 您可以支持&#…

datavideo切换台说明书_【新品发布】datavideo SE-650 高清四通道切换台

还在为音乐演唱会的拍摄而烦恼吗?或者为体育比赛的多机位发愁?或者为微课、优课、慕课制作而焦头烂额?大部分用户对多机位的恐惧来源于相关产品令人发指的复杂和专业性,面对满眼的键盘会有无从下手之感,很多学校和工作…

NSA:建议从 C/C++ 切换到内存安全语言

点击蓝字关注我们因公众号更改推送规则,请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络,侵删美国国家安全局(NSA)发布了一份指南,旨在帮助软件开发商和运营商预防和缓解软件内存安全问题。其鼓励组织将编…

探索cqrs和事件源_编写基于事件的CQRS读取模型

探索cqrs和事件源关于事件源和CQRS的讨论似乎通常集中在CQRS上下文中的整体系统架构或领域驱动设计的各种形式。 但是,尽管也有一些有趣的考虑,但读取模型经常被忽略。 在本文中,我们将介绍通过使用事件流来填充视图模型的示例实现。 总览 …

shmmax单位_kernel.shmmax ,kernel.shmmni 和kernel.shmall

kernel.shmmax 2147483648//该参数定义了共享内存段的最大尺寸(以字节为单位)。其值应>sag_max_size初始化参数的大小,否则SAG由多个内存段构成,效率降低,还要不小于物理内存的一半,默认情况下在32位x86系统中,OracleSGA最大不能超过1.7GB.缺省为32M,对于oracle…

C# 11正式发布

点击蓝字关注我们因公众号更改推送规则,请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络,侵删C# 11 现已发布。公告称,“随着每个版本的发布,社区的参与度越来越高,贡献了从建议、见解和错误报告到整个功…

java 自定义运算符_Java中集合的自定义运算符

java 自定义运算符总览 操作员重载有多种语言可用。 Java对String类型的运算符的支持对运算符的重载非常有限。 我们可以利用其他语言支持运算符的不同方式,但是我们可以在Java中实现一个使用Java已经使用的约定的实现。 获取,设置和放置操作 集合的运…

marker主题 ros_(五)ROS主题理解

参考网址:1,小海龟例子(1) 在新的终端打开roscore$ roscore---如果出错,请确定关闭所有ROS命令或者路径,重试。(2) 在新的终端打开运行小海龟界面$ rosrun turtlesimturtlesim_node得到结果&…

分享一个通用的嵌入式驱动层

点击蓝字关注我们因公众号更改推送规则,请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络,侵删C 语言面向对象编程的最佳实践一、前言以STM32为例,打开网络上下载的例程或者是购买开发板自带的例程,都会发现应用层中会…

cks32和stm32_cks子,间谍,局部Mo子和短管

cks32和stm32本文是我们名为“ 用Mockito测试 ”的学院课程的一部分。 在本课程中,您将深入了解Mockito的魔力。 您将了解有关“模拟”,“间谍”和“部分模拟”的信息,以及它们相应的存根行为。 您还将看到使用测试双打和对象匹配器进行验证…

CSON+CJSON,解析json数据更优雅?

点击蓝字关注我们因公众号更改推送规则,请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络,侵删前言json是目前最为流行的文本数据传输格式,特别是在网络通信上广泛应用,随着物联网的兴起,在嵌入式设备上&a…

服务总线yali测试_服务器的压力测试方法与流程

本发明涉及服务器的测试技术领域,特别是涉及一种包括超路径互联总线的服务器的压力测试方法。技术背景QPI(Quick Path Interconnect,快速通道互联)总线技术是一种取代前端总线(FSB)的CPU(处理器)间点对点连接技术。英特尔公司在2017年发起UPI(Ultra Path…

deprecated_@Deprecated新外观可能是什么?

deprecatedJDK增强建议 (JEP)277(“ 增强的弃用 ”)建议“重新定义弃用注释,并提供加强功能生命周期尾端的工具。” 当前 java.lang.Deprecated的一些限制使我困扰了一段时间。 我特别希望能够使用Deprecated提供文本&…

轨道运营管理专业自荐书_轨道运营管理专业自荐信x

轨道运营管理专业自荐信尊敬的领导:您好!非常感谢您们在百忙之中审阅我的求职资料。这对一个即将步入社 会的学子来说是莫大的鼓舞。我是西安联合学院城市轨道运营管理专业的一名学生, 2011年 7 月即将步 入社会,心情是那么的激动…

让C语言源码可知自身函数的实际地址与大小

点击蓝字关注我们因公众号更改推送规则,请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络,侵删事情的起因大概是这样……在很久很久以前,我最早用的是MASM(Win32ASM)写程序,从平台兼容性、开发…

java设计模式迭代器模式_迭代器设计模式示例

java设计模式迭代器模式本文是我们名为“ Java设计模式 ”的学院课程的一部分。 在本课程中,您将深入研究大量的设计模式,并了解如何在Java中实现和利用它们。 您将了解模式如此重要的原因,并了解何时以及如何应用模式中的每一个。 在这里查…

平衡二叉树 C语言代码实现

点击蓝字关注我们因公众号更改推送规则,请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络,侵删1.什么是平衡二叉树平衡二叉树,我们也称【二叉平衡搜索树/AVL】,树中任何节点的两个子树的高度最大差别为1,巴拉巴拉。。…