存储管理与linux内存寻址(1)
- 无存储器抽象
最简单的存储器模型就是没有存储器抽象,即每个程序直接访问物理内存(这里程序是通过内存地址访问内存),物理内存被组织成0到某个上限的地址集合,每个地址对应一个内存单元,通常是8个二进制位即1 byte.。每个程序通过这样的地址直接访问和修改内存单元中的内容。
这样带来的问题是一个时刻内存中只能有一个程序,如果内存中有两个程序,程序1在某个内存单元保存了内容,而程序2在随后也在该内存单元保存自己的内容,后来的内容将覆盖先前的内容,那么程序1将因为失去某些运行必要的内容而崩溃。
在这种同一时刻内存中只允许一个程序运行的,无存储器抽象的系统中,有三种方式组织内存
即没有交换和分页的单道程序,a和c因为操作系统在RAM(可读可写)内存单元中,因此用户程序可能误操作修改这里的内容而导致操作系统崩溃。
- 重定位问题:
将内存划分为等大的块(即每块有相同数量的内存单元),位每个内存单元分配一个4位保护键,每个程序在其程序状态字中也存储这样一个4位码,但一个程序访问一个内存块时,先验证这两个4位码,如果不同则通过硬件拒绝操作。这样貌似可以防止用户进程之间的相互干扰,但也带来问题
Can not be sure where program will be loaded in memory.
Use base and limit values
因为使用的是绝对物理地址,所以因为程序2无法操作已属于程序1的内存块,程序2也无法运行。内存中还是只允许同一时刻只能有一个程序。
解决方法是不使用绝对物理地址。每个程序用自己的方法来进行内存寻址,从而保证不会因为相同的物理地址而操作相同的内存块。也就是说即使现在程序1和程序2都想操作地址为29的内存块,但因为采用了不同的寻址方式(相对地址映射),;例:程序1的操作地址为程序1的基地址加上29的内存块,程序2操作地址为程序2的基地址加上29的内存块,于是不会发生冲突。
也就是说29这个地址被不同的程序映射到了不同的内存物理地址。于是就有了地址空间的概念。
3.地址空间:
地址空间是一个进程可用于寻址内存的一套地址集合。每个进程都有自己的地址空间,并独立于其他进程。通俗的讲,就是每个进程里有维护这样一张表,表的每一项保存了进程中的一个地址到真实物理地址的映射。并且,这个表是每个进程不同的。
4.交换技术
处理内存超载有两种方法,一种是交换swapping技术:把一个进程完整调入内存,运行一段时间后把它存回磁盘。因此进程不运行时不占内存。另一种是虚拟内存技术virtual memory,使程序只有一部分调入内存,然后按需要调入需要的块。
如果所示的内存进程的换入换出。
进程的交换会在内存中产生多个空闲去,称为空洞。
一个进程所需内存的大小是不固定的,事实上程序在运行过程中会产生新的变量等,所以可以为进程分配额外的内存,一般进程有两个可增长的段,供变量动态分配和释放的作为堆使用的数据段,以及存放普通局部变量和返回地址的堆栈段
如b图所示,堆栈段向下增长,数据段向上增长,中间是可供他们扩展的空闲内存。
5.空闲内存管理
有两种方式跟踪内存,管理内存分配,基于内存块的组织方式。
1.使用位图的内存管理
将内存划分为相同大小的分配单元(或块),每个分配单元对应位图中的一位,于是内存的使用情况就可以用a图这样一个位图来表示,阴影部分表示未被使用的内存分配单元所占的区域。每个刻度是一个分配单元,所以用0和1来表示该分配单元有无被使用,因此没8个分配单元1byte。检索这样的位图表就可以知道那些空闲的分配单元可以被使用。这个方法的缺点是如果要为一个需要8个分配单元的进程开辟内存,需要从头遍历位图表找到连续的8个0所对应的位置,这是一个比较耗时的操作。
第二个方法如b所示,将使用与未使用的内存块用链表连起来,h代表空闲,p代表使用,后两位数字表示该内存块的起始与结束分配单元号。
使用链表为进程寻找合适的内存块要有效一点,当进程换出时,可能比较麻烦,因为一个进程换出后可能会产生多种情况:
考虑这样的过程,最好使用双向链表。
为进程分配合适的内存需要一定的策略,最简单的就是从链表头开始寻找空闲块,如果空闲块的大小大于进程所需的内存大小,就将此空闲块分给进程,链表加入一个结点,同时改变空闲块大小为剩下的大小。这样做的问题是可能会造成一定的空间浪费,大空闲块分配给进程后留下来较小的空闲块,难以在分配给其他进程。
所以最好是找到跟进程所需内存较合适的空闲内存块。这既是最佳算法,而事实上最佳算法更糟,因为,不可能有正好的空闲内存块大小,所以一般会把空闲块大小和进程所需空间最靠近的空闲块分配个进程,这样剩下的空闲块会很小,那么能被其他进程使用的可能更低了。
最差适配法和最佳相反,每次分配所能找到的最大的空闲块为进程分配内存。使得剩下的空闲块还能被其他进程使用。
6.虚拟内存
起源:当程序大于内存时,解决办法是把程序分解成片段,这些片段称为overlay,程序启动是,先将overlay管理模块装入内存,然后按程序执行流程装入overlay0,执行完后,再加入overlay1覆盖overlay0.
这是大致思想,在实现里可以变得更复杂。首先把这些程序分段和换入换出的工作都交给计算机完成,就产生了虚拟内存技术。
虚拟内存的基本思想是:每个程序有自己的地址空间,这个空间被分为多个块,每一块称作一个页面page,这是一个连续的地址范围。物理内存也按相应大小划分多个单元,称为页框,按前文所述,这样的地址空间地址被映射到真实的物理地址。但并不是地址空间中所有的页面都会在一开始都会被映射到物理地址页框。事实上虚拟内存抽象了一个更大的内存,所以地址空间中多个页面并没未完成映射。进程会根据他虚拟的那个内存来执行程序,当运行到的页面有到物理页框的映射时,就找到物理内存地址,在此地址运行。否则,进程运行的页面没有到物理页框的映射,就会产生缺页中断。由硬件执行必要的映射。
分页
所以程序产生的地址是虚拟地址,对应地址空间中的地址,这个地址空间就构成了一个虚拟内存,在使用虚拟内存的情况下,虚拟地址必须被翻译成正确的物理地址。因此虚拟地址会先发送到内存管理单元memory management unit MMU,由MMU映射为物理地址。
如图是一个给出虚拟地址与物理地址映射的页表,MMU根据此将虚拟地址翻译成物理地址。
如果程序运行到一个没有完成映射的页面时,MMU会发现此页面没有映射,于是使CPU陷入操作系统,在此称为一个陷阱page fault,此时操作系统找到一个很少使用的页框,将其写入磁盘(保存该页框原始数据,如果该页框的数据是脏数据的话),然后将需要访问的页面映射到此页框(讲页面内容读到此页框),并修改映射关系。重启引起陷阱的指令。
这是一个MMU内部结构原理,输入为16位虚拟地址,被分为4为页号和12位偏移量。中间是一个页表page table,4位页号会被翻译页表索引,即4位二进制转成十进制,页表有两列,第一列是对应的页框号,如果没有映射为000,第二列表示该处是否有映射,0表示无映射,1表示有映射。页表项3表示有映射,并映射到物理框000,其他000都是无映射。
如果有映射,根据表得到对应的页框号,将页框号加上12位偏移量,就是物理地址。
页表项结构:
一般页表项有32位,如图
从左到右依次为,高速访问禁止位,访问位,修改位,保护位,是否存在映射,页框号。
保护位指出一个页允许什么类型访问,一般有1位或3位表示,标识读/写,只读,可执行等。修改和访问为记录了页面的使用情况,如果一个页面是已被修改过的,那么如果发生缺页中断,并且操作系统绝对替换此页面的映射时,必须讲此页面的内容写回磁盘,因为此页面的内容发生了改变(所有没有映射的页面都是程序暂时不运行的,所以由磁盘保存)。如果此页面没有被修改过,那么磁盘上应该保留有它的副本,所以不要用写回磁盘,内存可以直接丢弃。访问位可以帮组操作系统在发生缺页中断时选择被淘汰的页面,具体由页面置换算法决定。