装载的简单介绍

Posted taocr

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了装载的简单介绍相关的知识,希望对你有一定的参考价值。

一、什么是装载?

程序执行时所需要的指令与数据必须都在内存中才能正常运行,这些需要的数据装入的过程即为装载

装载一般分为两种:
1、静态装入

将所需要的指令和数据全部装入内存中,这时最简单的方法

2、动态装入

利用程序运行时的局部性原理,将程序最常用的部分留在内存中,不太常用的数据放于磁盘中

对于上面的第一种情况,如果可执行文件太大,程序所需要的内存数量大于物理内存的数量时,第一种装载的方式明显不行,于是第二种动态装入的方法才是最好的选择。

对于动态装入的方式,也存在两种:
覆盖装入:

程序员将程序分割成若干块,编写一个辅助代码来管理这些模块。

页映射:

将内存和所有磁盘中的数据和指令按照页为单位划分,之后装载和操作的单位就是页。

其中覆盖装入的方式已经被淘汰,是没有发明虚拟存储前的动态装载方式。其存在着一些缺陷,存在某些模块间的地址冲突,从而导致无法同时使用冲突的模块。

可以看到,图中A、B两个模块之间的地址重合,于是使用A模块后再使用B模块的话,必然会将A模块的部分覆盖掉,这只是一个比较简单的例子,真正的程序中会存在许多模块,会更加复杂,同时需要程序员来编写头部的Overlay Manager(覆盖管理器)来管理模块的驻留或是替换,因此存在缺陷。

页映射的装入方式是虚拟存储机制的一部分,随着虚拟存储的发明而诞生。

如图中所示,将ELF文件按页划分,于是装入的时候只需要按页为单位装入,即可实现程序的执行。
不过这种方法也有个很明显的问题,如果程序需要访问P4,那么装载管理器必须做出抉择,必须放弃正在使用的4个内存页中的一个来装载P4,于是选择哪个页就成了问题,有着很多的算法,包括FIFO(先进先出算法)、LUR(最少使用算法)等等。其实这里的装载管理器就是现代的操作系统的存储管理器。

二、从操作系统角度看可执行文件装载

上面简单介绍了装载的概念,接下来站在操作系统的角度来看一个可执行文件的装载和执行过程。

从操作系统来看,每个进程最关键的特征就是其有着独立的虚拟地址空间。
通常,一个程序被执行的同时伴随着一个新的进程被创建,即创建一个进程,然后装载相应可执行文件,此种情况下总共需要完成三件事:
1、创建一个独立的虚拟地址空间;

一个虚拟空间由一组页映射函数将虚拟空间的各个页映射到相应的物理空间,于是创建一个虚拟空间实际上就是创建映射函数所需要的相应数据结构,实际上就是建立一个页目录,甚至页映射关系都会等到后面发现页错误的时候再进行设置

2、读取可执行文件头,建立虚拟空间与可执行文件的映射关系;

当程序执行发生页错误时,操作系统从屋里内存中分配一个物理页,然后将所缺的那一页从磁盘中读入内存中,在设置却也的虚拟页与物理页的映射关系,然后程序得以执行。
但是存在一个很明显的问题,发生页错误后,系统如何知道该读入的那一页在哪里?或者说在可执行文件中的哪个位置?因此这一步即让操作系统发生页错误后能够知晓缺少的那一页在可执行文件的哪个位置,于是才能将其读入内存中,继而建立虚拟页与物理页的映射关系,从而执行程序。

3、将CPU指令寄存器设置为可执行文件的入口地址,启动运行。

让CPU下一步从可执行文件的入口开始执行可执行文件,程序成功运行。但是这一步中会牵扯到内核堆栈、用户堆栈等的切换,所以并不像看上去这么简单。

刚刚的三步过程后CPU开始从程序的入口地址开始执行,发现第一个页是空页,于是发生了页错误,操作系统通过缺页、请页机制将所需的页从磁盘中装入内存中,于是不断发生页错误,不断将页装入内存中,程序得以执行。当出现进程需要的内存超过可用内存数时,操作系统进行组织和分配物理内存,将分配配给进程的物理内存暂时回收并再次进行分配。

三、Section与Segment的区别

我们都知道一个可执行文件中往往包含了很多段,包括代码段(.text)、数据段(.data)等,实际上这些段我们一般称为Section。

上面就是一个简单程序的Seciton的分布,并没有显示完全,不过总计有31个Section,代码段、数据段以及一些我们不知道用处的段等等都在其中

ELF文件被映射时,是以系统的页长度作为单位的,每个Section在映射时长度都应该是系统页长度的整数倍,如果不是的话,那么向上补足,即不到一页也分配一页。
想象一下,上面那个ELF文件中有着31个Section,我们无法保证每个Section的长度都刚好是系统页长度的整数倍,那么31个Section的映射就意味着要浪费很多内存,为了节省内存,于是产生了Segment这个概念。

上面是相同ELF文件的Segment信息,其中Segment只有6个,并且类型为LOAD即需要装载到内存中的Segment只有两个,这样相比于映射31个不同的Section来说节省了许多内存

那么Segment与Section的区别是什么?
其实从上图中能够看到答案,Segment即多个Section的集合。那么多的Section,如果按照权限来归类也只有三类,即:
1、权限为可读可执行,以代码段为代表;
2、权限为可读可写,以数据段及BSS段为代表;
3、权限为只读的段,以只读数据段为代表。
既然分类了,那么就简单了,将相同权限的Section合并为一个Segment,这样就能够节省大量的内存。从上图中可以看到,对于LOAD类型的两个Segment,第一个是由.init、.text等Section合并而成,权限为RE(可读可执行),第二个是由.data、.bss等Section合并而成,权限为RW(可读可写)。

以上是关于装载的简单介绍的主要内容,如果未能解决你的问题,请参考以下文章

贪心算法 | 最优装载问题——加勒比海盗船

读书笔记|《程序员的自我修养》- 04 可执行文件的装载与进程

读书笔记|《程序员的自我修养》- 04 可执行文件的装载与进程

最优装载

读书笔记|《程序员的自我修养》- 04 可执行文件的装载与进程

java中类变量、实例变量和局部变量有何区别?