文章目录
5. 内存
5.1 什么是内存
在第一课的学习中已经有过对内存的介绍,我们在这里不再赘述,同时也知道内存是计算机中非常重要的一部分,计算机运行时的数据指令等等都需要在内存中存储。
5.2 存储器体系结构
计算机的存储体系结构具体到不同的具体的计算机设计当中又是各不相同的,这里给出一个大体的层次结构,缓存有效的原因是源自于程序执行的局部性原理。
5.3 存储管理
这里聊的存储管理主要是指没有基于虚拟内存的存储管理。
5.3.1 内存管理的内容
想一下存储管理主要要解决问题,存储管理主要还是管理各个进程在运行时的程序和数据的访问。
当要启动一个应用程序的时候,首先需要将该进程的可执行程序加载到内存当中,创建一个进程,这个进程有自己特定的内存区域,存放刚才加载进来的代码指令,数据块,以及一些进程的描述信息,控制信息等。
同时,在进程运行的过程中可能还要访问文件等外部存储,这个时候还需要将这些东西加载到内存当中才行。内存管理就是为了解决这些问题而存在的。
下面的图也显示了一个进程在内存中的需求:
5.3.2 内存管理的需求
- 重定位
为了内存使用效率的提升,内存被换出内存后再换进内存的时候不一定能够换到原来的位置,所以换到新的位置也要能够支持,称为重定位技术。 - 保护
每个进程的内部内存都要受到保护,保护必须由处理器来实现,而不是操作系统来满足,因为操作系统不能预测程序可能产生的所有内存访问。 - 共享
要支持对部分共享区域的受控访问,比如一些程序共享库文件等 - 逻辑组织
就是对一个进程使用的内存如何划分,分段是比较符合实际程序情况的,数据段,代码段等等这样的。 - 物理组织
计算机存储器至少要组织成两级,即内存和外存。内存提供快速的访问,成本也相对较高。此外,内存是易失性的,即它不能提供永久性存储。外存比内存慢而且便宜,且通常是非易失性的。因此,大容量的外存可用于长期存储程序和数据,而较小的内存则用于保存当前使用的程序和数据。在这种两级方案中,系统主要关注的是内存和外存之间信息流的组织交换等工作,比如在内存不够用的时候将一部分内存换到外存当中的操作。
5.3.3 内存管理的术语
- 页框:物理内存中的固定长度块
- 页: 固定长度的数据块,存储在二级存储中(如磁盘),数据页可以临时复制到页框当中。
- 段:变长数据块,存储在二级存储中(如磁盘),数据页可以临时复制到内存中的一个可用区域中(分段),或者可以将一个段分为很多页,然后将每页单独复制到内存中(分段与分页结合式)
- 逻辑地址:指与当前数据在物理内存中分配的地址没有关系的访问地址
- 相对地址:相对地址是逻辑地址的一个特例,他是相对于某些已知点(通常是程序的开始处)存储单元。
- 物理地址:或者成为绝对地址,是指物理内存中的一个存储单元。
5.3.4 内存管理的发展
技术 | 简要说明 | 优点 | 缺点 |
---|---|---|---|
固定分区 | 主存被分为很多大小固定的分区,进程可以装载到大于等于自身大小的分区。 | 实现简单 | 1.有内部碎片,2.活动进程的数目是固定的 |
动态分区 | 分区是被动态创建的,进程可以装载到正好等于自身大小的分区。 | 没有内部碎片,内存使用更完全 | ,有外部碎片,需要压缩外部碎片 |
简单分页 | 主存被分为很多大小相同的帧,进程被分为很多与帧大小相同的页。要装入一个进程,需要将进程所有的页装入主存,可以是不连续的帧中。 | 没有外部碎片有很少的内部碎片(仅出现在进程的最后一页) | |
简单分段 | 进程被分为很多的段,要装入一个进程,需要将进程所有的段装入主存中不一定连续的动态分区。 | 没有内部碎片,比较与动态分区,内存利用率更高,开销小 | 有外部碎片,需要压缩外部碎片 |
虚拟内存分页 | 与简单分页相比,不需要将进程的所有页装入主存 | 没有外部碎片巨大的虚拟内存空间 更高程度的多到程序设计 | 复杂的内存管理开销 |
虚拟内存分段 | 与简单分段相比,不需要将进程所有的段都装入主存 | 具有虚拟内存分页的三个优点,并且支持保护和共享 | 复杂的内存管理开销 |
5.3.5 简单分页的内存管理
内存管理的发展经历了很多迭代,从上面也可以看出来。分区技术曾经用在过许多过时的系统当中。简单分页和简单分段并没有在实际中使用过,只是为了增强对物理内存的管理理解,有助于后面对虚拟内存的分页,分段管理增强理解。
在简单分页中,主存被分为很多大小相同的帧(页框),进程被分为很多与帧大小相同的页。要装入一个进程,需要将进程所有的页装入主存,可以是不连续的帧中。每个进程维护一个页表,页表项有页号和对应的物理页框。
简单分页要求进程运行时当前进程的代码段数据段等都要加载进内存中,内存才能够运行。
下面这幅图展示了ABCD 4个进程的载入和换出过程。主存储中的每个格子代表了一个页框。
同时下面的第二幅图显示了每个进程需要维护一个页表来保存他使用了主存储中的哪些页框。
介绍一种逻辑地址和物理地址的转换方案
为了使分页方案更加方便,规定页和页框的大小都必须是2的幂。以便于容易地标识出相对地址。
相对地址一般由程序的起点和逻辑地址定义,可以用页号和偏移量表示。
比如这里使用16位的地址,页的大小是1kb,2的10次方,寻址范围是64kb,就可以用地址的高6位标识页号,低10位标识页内偏移量。
00000101 11011110
对应的页号就是1,偏移量就是1502
提取完页号以后,以页号为索引在进程页表中找到这个页号对应的页框
把页框地址和上一步中的偏移量叠加起来就构成了物理地址。
5.3 什么是虚拟内存
5.3.1 虚拟内存术语
- 虚拟内存:
在存储分配机制中,尽管备用内存是主存的一部分,但它也可被寻址。程序引用内存使用的地址与内存系统用于识别物理存储站点的地址是不同的,程序生成的地址会自动转换为机器地址。虚拟存储的大小受计算机系统寻址机制和可用的备用内存量的限制,而不受主存储位置实际数量的限制 - 虚拟地址: 在虚拟内存中分配给某一位置的地址,它使得该位置可被访问,就好像是主内的一部分那样
- 虚拟地址空间:配给进程的虚拟存储
- 地址空间:用于某进程的内存地址范围
- 实地址: 物理内存中存储位置的地址
通过上面的介绍也可以看到,早期计算机使用物理寻址方式,但是到了现在的多任务计算机时代,普遍使用的是虚拟寻址(virtual addressing);虚拟内存,就是对于每个进程来说,他能够访问的地址空间都是整个cpu能够访问的地址空间的最大值。
举例,对于x86系统,32位的虚拟内存大小是4g,也就是每个进程的虚拟地址空间都有4G大小,64位系统每个进程的虚拟地址空间都是4g*4g的虚拟地址空间(简直可以认为无限大了)如果每个进程的可访问空间都要装入内存中,那计算机就是有再大的内存也是扛不住的。所以对于虚拟内存来说,只是有一部分会在物理内存当中,很多都是需要的时候才会加载,同时可能在物理内存不够的时候又会被换出去。又是局部性决定了可以使用虚拟内存。
5.3.2 地址翻译
在运行的过程中,CPU 通过一个虚拟地址(virtual address,VA)来访问主存,这个虚拟地址在被送到主存之前会先转换成一个物理地址。将虚拟地址转换成物理地址的任务叫做地址翻译(address translation)。
地址翻译需要 CPU 硬件和操作系统之间的配合。 CPU 芯片上叫做内存管理单元(Menory Management Unit, MMU)的专用硬件,利用存放在主存中的查询表来动态翻译虚拟地址,该表的内容由操作系统管理。
5.3.3 一个进程的虚拟地址空间分布
x86 平台 Linux 进程内存布局
6.链接
链接(linking)是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载(或被拷贝)到存储器并执行。链接可以执行于编译时(compile time),也就是在源代码被翻译成机器代码时;也可以执行于加载时(load time),也就是在程序被加载器(loader)加载到存储器并执行时;甚至执行于运行时(runtime),由应用程序来执行。在早期的计算机系统中,链接是手动执行的。在现代系统中,链接是由叫做链接器(linker)的程序自动执行的。
&emsp链接器在软件开发中扮演着一个关键的角色,因为它们使得分离编译(separate compilation)成为可能。我们不用将一个大型的应用程序组织为一个巨大的源文件,而是可以把它分解为更小、更好管理的模块,可以独立地修改和编译这些模块。当我们改变这些模块中的一个时,只需简单地重新编译它,并重新链接应用,而不必重新编译其他文件。
6.1 程序编译链接的一般过程
c源程序到执行的的一般过程,假如源程序是main.c
- 预处理(preprocessing) :预处理阶段主要处理#include和#define,它把#include包含进来的.h 文件插入到#include所在的位置,把源程序中使用到的用#define定义的宏用实际的字符串代替,这一步生成 main.i 文件
- 编译(compilation) :把代码翻译成汇编语言,该选项只进行编译而不进行汇编,生成汇编代码,生成的文件是main.s。
- 汇编(assembly):汇编阶段把main.s文件翻译成二进制机器指令文件main.o, 该文件被称为可重定位目标文件。
- 链接(linking) :将可执行目标文件经过一系列处理生成可执行目标文件
以上步骤可以得到可执行文件,也就是可运行程序 - 加载(loader)就可以执行了。
可重定位目标文件,可执行目标文件,共享目标文件,都被称为目标文件,因为他们有相似的文件格式,都被称为ELF文件。
这里主要想要学习的就是链接是如何将可重定位目标文件转换为可执行目标文件的。
其实我们在平时生成的目标文件中,具备以下几个特征:
1. 各个段没有具体的起始地址,只有段大小信息;
2. 各个标识符没有实际地址,只有段中的相对地址;
3. 段和标识符的实际地址需要链接器具体确定。
链接就是的主要过程是
- 解析符号
- 重定位生成可执行文件
ELF文件的结构格式
6.2 链接的要求
1、各个段的链接地址必须符合具体平台的规范;
2、链接脚本中能够直接定义标识符并指定存储地址;
3、链接脚本中能够指定源代码中标识符的存储地址;
在 Linux 中,进程代码段(.text)的合法起始地址为[0x08048000, 0x08049000]。
在链接器中默认指定了text的起始地址为0x08048000(可以自己设置),然后链接器会基于这个位置对其他的代码进行虚拟地址的重定位。默认的入口函数是_start函数,这个函数就放在入口地址位置处。
1.32位进程的虚拟地址空间
这里对链接器讲的很详细
参考:
https://juejin.im/post/59f8691b51882534af254317
https://segmentfault.com/a/1190000016433897