1 内存管理的需求
1.1 重定位
我们事先并不知道程序会存放在哪个区域:
- 内存空间通常被多个进程共享,运行前并不知道进程会被分配到哪里;
- 为了充分利用 CPU,进程经常被换入换出内存,下次换入时的位置很难做到与之前相同,即进程占用的内存地址可能变化。
程序代码访存的地址必然是相对于程序本身的,操作系统需要把这种地址重定向为实际的物理地址。
重定位有三种方式:
- 编译时重定位:程序放入内存的位置能提前确定,就可以在编译的时候把放入后的地址都计算好
- 载入时重定位:在程序放入内存的时候,计算出地址,但这要求放入后程序在内存的地址就不再改变
- 运行时重定位:运行时再根据基地址和相对地址计算出物理地址,这种方式最可行
使用运行时重定向,基地址会保存在进程控制块中,基地址改变的时候,会更新进程控制块中的对应数据,运行时会从进程控制块中读出基地址放入寄存器中。
1.2 保护
一个进程的内存不能在未经授权的情况下被其它进程访问,计算机底层需要具有对应的能力来保证这点,而实际上这是处理器硬件必须实现的功能。
1.3 共享
保护的同时,还要支持多进程共享内存
1.4 逻辑组织
大多数程序是按模块进行组织的,某些模块不能修改(如程序段),某些模块包含可以修改的数据(如数据段),计算机底层要有效的利用这些特点。
1.5 物理组织
计算机底层要处理好数据在内存和外存之间的换入换出。
2 内存分区法
2.1 固定分区(过时)
把内存划分成固定的块,块的大小可以相等,也可以不相等。
- 大小相等的固定分区法:
- 程序可能太大,放不进分区中;
- 内部碎片多
- 大小不等的固定分区法:
- 可以稍微缓解大小固定存在的问题
- 有两种放置策略:每个分区一个队列 VS 所有分区一个队列
2.2 动态分区(过时)
进程装入内存时,系统为其动态分配一块与其所需容量完全相等的内存空间。
- 缺点:会产生大量的外部碎片
- 解决办法:需要经常进行碎片整理,使进程占用的空间连续。
- 放置策略:最佳适配 VS 首次适配 VS 下次适配
2.3 伙伴系统(用于并行系统)
TODO: 等待补充……
2.4 简单分页
把内存划分成大小固定、相等且较小的块,每个进程也分成同样大小的小块,那么进程中称为页(page)的块可以分配到内存中称为页框(frame)的可用块。
碎片情况:仅是进程最后一页可能形成很小的内部碎片,没有任何外部碎片。
操作系统需要为每个进程维护一个页表,页表记录了该线程每页所对应页框的位置。为了方便,一般规定页和页框的大小为2的幂,这样程序中的逻辑地址就可以分成 [页号 : 偏移量]
两部分。比如逻辑地址为 16 位,页大小为 1K,这样逻辑地址就为 [页号(6位) : 偏移量(10位)]
。
访存时,会根据页号在页表中查找页框的地址,然后用页框地址替换页号,就得到了物理地址。
当内存不够用时,系统根据置换算法把一些进程出内存,让需要内存的进程有使用空间。
2.5 简单分段
在程序员看来,为了方便组织程序和数据,达到模块化设计等目的,我们的程序一般可以分成几个部分,每个部分都有自己的特点,如主程序代码不可写,但可读可执行,而变量集是可写的。编译器一般也会把程序和数据划分到不同的部分。分段就是支持这种程序员视图的内存管理方案。
分页对程序员来说是透明的,而分段通常是可见的。
分段是按照进程的实际内容来进行划分的,每个段的大小不一样,所以没有内部碎片,但是存在外部碎片。
程序载入内存时,是将各个段分别放入的,每个段都有一个段的基地址,寻址时要先用段的基地址加上相对地址才能得到物理地址。而段的基地址是存放在**段表(LDT)**中的,LDT 又放在进程的 PCB 中。也分页不同的是,段表必须记录段的长度。
分段访存的过程和分页类似,也是把逻辑地址分为 (段号 : 偏移量)
,访存的时候根据段号在段表中查找该段的实际地址。需要注意的是,分段中还需要判断偏移量是不是超过了段的长度。