1、内存
定义:内存是用于存放数据的硬件。
程序执行前需要先放到内存才能被CPU处理。系统由多个程序并发执行就说明会有多个程序数据需要放到内存。
那么如何区分各个程序的数据是放在什么地方呢,就类似于酒店给房间编号一样,内存中也有一个一个的小房间,每个房间就是一个存储单元。
如果计算机按字节编址则每个存储单元大小为1字节即1B,8个二进制位。如果按字编址,则看计算机的字长;若字长为16位,则每个存储单元为1个字,1个字大小为16个二进制位。
补充:假设一台电脑有4GB内存,即说明有4*2的30次方个字节,如果按字节编址,那么就是有2的32次方个小房间。这么多小房间,需要2的32次方个地址来一一标识,那么也就需要32个二进制位来表示。
进程的运行原理–指令
在实际编程中,我们写的代码要翻译成CPU能识别的指令即编译,这些指令会告诉CPU应该去内存的哪个地址存/读取数据,数据要做什么样的处理。其中编译时产生的指令只关心逻辑地址,实际放入内存中再想办法根据起始位置得到绝对地址。
对应内存位置:指令放置于程序段,数据放置于数据段。
2、地址装入(装载)方式
已知:通俗来说,编译就是将高级语言翻译成机器语言,翻译成CPU所能理解的语言。链接就是链接程序代码指令所需的一段内存地址。
补充
链接的三种方式:
1.静态链接:在程序运行之前,先将各目标模块及它们所需的库函数连接成一个完整的可执行文件(装入模块),之后不再拆开。
2.装入时动态链接:将各目标模块装入内存时,边装入边链接的链接方式。
3.运行时动态链接:在程序执行中需要该目标模块时,才对它进行链接。其优点是便于修改和更新,便于实现对目标模块的共享。
三种装入方式:绝对装入、静态重定位、动态重定位
-
绝对装入:在编译时,若是知道程序最终会放置在内存具体的位置,编译程序将产生绝对地址,装入程序按照装入模块的地址,将程序和数据装入内存。
绝对装入只适用于单道程序环境。
-
静态重定位:又称可重定位装入。编译、链接后的装入模块的地址都是从0开始,指令中使用的地址和数据存放的地址都是相对起始地而言的逻辑地址。可根据内存的当前情况,将装入模块装入内存的适当位置。装入时对地址进行重定位,即将逻辑地址变换为物理地址。
特点:作业装入内存时,必须分配其要求的全部内存空间,如果没有足够的内存,就不能够装入;一旦进入内存后,运行期间不可再移动,也不能再申请内存空间。
-
动态重定位:又称动态运行时装入。编译、链接后的装入模块的地址都是从0开始的。装入程序把装入模块装入内存后,并不会立即把逻辑地址转换为物理地址,而是把地址转换推迟到程序运行时才进行。即装入内存后所有的地址依然是逻辑地址。这种方式需要一个重定位寄存器支持。(重定位寄存器会存放装入模块存放的起始位置。物理地址 = 逻辑地址偏移量+起始位置)
采用动态重定位时,运行程序在内存中发生移动。
3、内存分配
3.1 连续分配方式
连续分配:为用户进程分配的必须是一个连续的内存空间。
连续分配方式:
-
固定分区分配:分成固定大小的分区;
缺点:缺乏灵活性,(进程所需资源小于固定大小)会产生大量的内部碎片,内存的利用率低。
-
动态分区分配:分成不定大小的分区;
缺点:剩余空闲分区太小,无法满足进程所需资源,即会产生很多外部碎片,虽然可以用紧凑技术将外部碎片合并,但紧凑的时间代价很高。
动态分配算法
算法 | 算法思想 | 分区排列顺序 | 优点 | 缺点 |
---|---|---|---|---|
首次适应 | 从头到尾找适合的分区 | 空闲分区以地址递增次序排列 | 综合而言性能最好。算法开销小,回收分区后一般不需要对空闲分区队列重新排序 | |
最佳适应 | 优先使用更小的分区,以保留更大的分区 | 空闲分区以容量递增次序排列 | 会有更大的分区保留下来,更能满足大进程需求 | 会产生很多更小、难以利用的碎片;算法开销大,回收分区后可能需要对空闲分区队列重新排序 |
最坏适应 | 优先使用更大的分区,以防产生太小的不可用的碎片 | 空闲分区以容量递减次序排列 | 可以减少难以利用的小碎片 | 大分区容易被用完,不利于满足大进程的需求;算法开销大,原因同上 |
邻近适应 | 由首次适应演变而来,每次从上次查找结束位置开始查找 | 空闲分区以地址递增次序排列(可排列成循环链表) | 不用每次都从低地址的小分区开始搜索。算法开销小,原因与首次适应算法相同 | 会使高地址的大分区也被用完 |
3.2 非连续分配方式
如果允许将一个进程分散地装入到许多不相邻的分区中,便可充分地利用内存,无需再进行“紧凑”,即非连续分配方式。
3.2.1 基础分页存储管理方式
思想:把内存分成一个个相等的小分区,每个分区就是一个页框,每个页框有一个编号,即“页框号”,页框号从0开始。将用户进程的地址空间也分为与页框大小相等的一个个区域,称为“页”或“页面”,每个页面也有一个编号即“页号”,页号也是从0开始。
分页之后如何实现地址的转换
需知信息:
-
逻辑地址对应的页号;
-
该页号对应页面在内存中地起始地址;
-
逻辑地址在页面内的“偏移量”;
物理地址 = 页面起始地址 + 页内偏移量
通过以下例子进行进一步理解:
求逻辑地址为80的内存单元对应的实际物理地址:
已知:由左图可知页面大小长度为50
-
逻辑地址为80,即在1号页
页号 = 逻辑地址 / 页面长度 (取结果的整数部分)
80 / 50 = 1
-
1号页在内存中的起始位置是450
-
该内存单元对于该页的起始地址的偏移量是30(1号页的起始地址是50)
**页内偏移量 = 逻辑地址 % 页面长度 **(即余数)
80 % 50 = 30
-
结果:450 + 30 = 480 ;即该内存单元的物理地址为480。
上一个例子中的逻辑地址空间由十进制表示,接下来的例子以二进制为表示进行理解。
假设用32个二进制位表示逻辑地址,页面大小为4KB,需要12个二进制位表示。
前面的20位表示页号,即该系统中一个进程最多允许有2的20次方个页面。后面的12位可表示页内偏移量。
若逻辑地址为2,对应到二进制数,00000000000000000000000000000010,即页号为0,页内偏移量为2。
基本地址变换机构
基本地址变化机构可以借助进程的页表进行地址转换。
通常会在系统中设置一个页面寄存器,存放页表在内存中的起始地址和页表长度。(进程未执行时,页表的起始地址和页表长度放在进程控制块PCB中,当进程被调度时,操作系统内核会把它们放到页表寄存器中。)
页表项长度、页表长度、页面大小
页表长度是指这个页表中总共有几个页表项,即进程所需存储空间被分成了几页。
页表项长度是指每个页表项占多大的内存空间。
页面大小是指一页占多大的存储空间。
由基本分页存储管理的思想可知内存空间的页框大小与页面大小相同,因此得知内存块号之后,通过计算内存块号*页框大小+页内偏移量即可得出物理地址。
3.2.2 基本分段存储管理方式
进程地址空间:按照程序自身的逻辑关系划分为若干个段,每个段都有一个段名,每段从0开始编址。如编程中,main函数为一个段,某个子函数为一个段。
内存分配规则:以段为单位进行分配,每个段在内存中占据连续内存空间,但各段之间可不相邻。
与分页存储管理类似,也会为每个进程建立一张段表,段表由段号、段长、基址(起始位置)三个部分组成, 记录了该段在内存中的起始位置和段的长度。
分页管理与分段管理的对比
- 分页的主要目的是为了实现离散分配,提高内存利用率。分页仅仅是系统管理上的需要,对用户不可见。
- 分段的主要目的是更好地满足用户需求,一个段通常包含一组属于一个逻辑模块的信息。分段对用户可见,用户编程时需要显式地给出段名。
- 页的大小固定且由系统决定;段的长度却不固定,决定于用户编写的程序。
3.2.3 段页式管理方式
段页式是分页和分段的结合:先按逻辑模块分段,再将各段分页,将内存空间分为大小相同的页框。
每个段对应一个段表项,每个段表项由段号、页表长度、页表存放块号组成。
每个页对应一个页表项,每个页表项由页号、页面存放的内存块号组成。
逻辑地址二进制结构:段号、页号、页内偏移量。