操作系统学习笔记:内存管理
内存是现代计算机运行的中心(居然不是CPU!)。内存有很大的一组字或字节组成,每个字或字节都有地址。
CPU根据程序计数器从内存中提取指令。CPU所能直接访问的存储器只有处理器内的寄存器和内存。通常,程序存储在磁盘上,执行的时候,程序被调入内存。那么CPU怎么找到它们呢?
通常,将指令与数据绑定到内存地址有以下几种情况:
1)编译时,进程在内存中的驻留地址就已经确定,生成的是绝对代码
2)加载时,编译器生成的是重定位代码,绑定延迟到加载时才进行
3)执行时,绝大多数操作系统采用
一、背景
1、逻辑地址与物理地址
CPU所生成的地址称为逻辑地址,而内存单元对应的地址(即加载到内存地址寄存器的地址)称为物理地址。从逻辑地址到物理地址的映射由硬件设备(内存管理单元,MMU)完成。用户只生成逻辑地址,这些地址在使用前必须映射到物理地址。用户程序决不会看到真正的物理地址。
2、动态加载及DLL
如果一个进程的整个程序和数据必须处于物理内存中,那么进程的大小会受物理内存大小所限制。可以使用动态加载,子程序只在调用时才被加载。动态加载无须操作系统特别支持,这是程序员的工作。
除此而外,可以使用DLL。DLL与动态加载不同,通常需要操作系统的帮助,因为DLL可以被多个进程共用,只有操作系统可以检查其他进程的内存空间。
DLL有版本的区别,不同版本的库都可以装入内存。
二、交换
进程需要在内存中以便执行,但进程可以暂时从内存中换出(swap)到备份存储上,当需要再次执行时再回到内存中。
交换有一些策略,比如基于优先级。
三、连续内存分配
内存通常分为两个区域:驻留操作系统 + 用户进程。
通常需要将多个进程同时放在内存中,因此需要考虑如何为输入队列中的进程分配内存空间。采用连续内存分配时,每个进程位于一个连续的内存区域。
最简单的内存分配方法之一就是将内存分为多个固定大小的分区,每个分区只能容纳一个进程。这种方法现在已不再使用。
有一种固定分区的升级版是可变分区,操作系统有一个表记录内存的使用情况。一开始,所有内存都可用于用户进程,作为一大块可用内存,称为“孔”。当有新进程需要内存时,就为它寻找一块足够大的孔。如果找到,从该孔中进行分配,孔剩余部分可以下次使用。
寻找孔的算法有
1)首次适应。找到第一块足够大的孔就停止。
2)最佳适应,全表扫描,找到最小且合适的一块。
3)最差适应,全表扫描,找到最大且合适的一块。
三种算法中,最差适应最差,首次适应比最佳适应快一点点。但是,后二者产生的外部碎片问题(分区方法产生外部碎片,分页产生内部碎片)也比最差适应要严重。解决之道是紧缩(靠,SQL SERVER也有碎片问题,也是靠索引重建或收缩来解决)。所谓的紧缩就是移动内存内容,以便将空闲空间合并成一块。但并非所有情况都适用紧缩。如果内存物理地址重定位是静态的,在汇编或装入时进行,没问题,但如果是在运行时才进行就开销太大。
另一种解决方案是允许物理地址为非连续。这种方案就是以下的:分页和分段。
四、分页
分页允许进程的物理地址空间非连续。
分页避免了将不同大小的内存块匹配到交换空间上这样的麻烦。因为备份存储也有与内存相关的碎片问题,并且访问速度更慢,因此不适宜合并。而分页是将物理内存划分为固定大小的块,可以避免外部碎片。分页由于其优越性为绝大多数操作系统所采用。
分页由硬件支持。不过,最新的趋势是通过硬件和OS相配合,尤其是在64位微处理器上。
1、基本方法
分页方案中,物理内存划分为固定大小的的块,称为帧(frame);与之对应,逻辑内存也分为同样大小的块,称为页。同样,备份存储也有同样大小的块。大小由硬件决定,通常为2的幂,以方便将逻辑地址转换为页号和页偏移。
p:页号
d:页偏移量
f:帧号
由CPU生成的每个地址分为2部分:页号(p)和页偏移(d)。p作为页表的索引,页表包含每页所在物理内存的基地址,基地址 + 页偏移 = 物理地址。如图
采用分页技术不会产生外部碎片,但可能又内部碎片。因为进程所要求的内存不一定是页的整数倍。页究竟取多大的值,这是个问题。
分页的一个特点是用户视角的内存和实际的物理内存分离。从用户角度看,用户程序将内存作为一整块来处理,并且整个内存只有它自己存在。但事实上,上面有各种各样的进程,并且物理地址分布可能是不连续的。
这种差异,通过硬件进行映射转换。而这一切,对用户来说是透明的,但受操作系统控制。用户程序不能越界访问,无法访问其页表规定之外的内存。
由于操作系统管理物理内存,它必须清楚物理内存的分配细节,知道帧的使用情况,可用数,总数,等等。这些信息通常保存在帧表的数据结构中。
2、硬件支持
操纵系统如何维护页表?
绝大多数是为每个进程分配一个页表,页表指针与其他寄存器一起存入进程控制块中。
页表可以用一组专用的寄存器来保存,这是效率最高的方法。但只适用于页表不大的情况。
如果页表非常大,如1百万个条目,就需要存放在内存,用页表基寄存器指向页表。改变基寄存器就可以切换页表,快得很。
不过由此而来的问题是,采用这种方案,访问一个字节现在需要2次内存访问,相对于原来速度减半。这种延迟是无法忍受的。对这个问题的解决方案是采用硬件缓冲:转换表缓冲区,TLB。TLB只维护页表中的一小部分条目,逻辑地址转换物理地址过程中,先在TLB中查找,如果找到,那么物理地址唾手可得;如果TLB中没有,那么使用置换算法,将相关条目置换进TLB,然后再得到物理地址。
3、保护
分页环境下,内存保护通过每个帧的关联保护位来实现。通常,这些位于页表中。
这个位,定义一个页是可读写还是只读。
还可以定义一个有效-无效位。当该位为有效时,表示该页在进程的逻辑地址空间内,是合法的页,否则属非法地址。一个进程很少会使用其所有的地址空间,而只使用一小部分。
4、共享页
分页的另一个好处是可以共享公共代码。
五、页表结构
1、层次页表
对于一个进程,一个页表已经可以对应不少的条目,比如说,100万条,那就是100万个内存地址。如果还不够,那么页表可以再分层级,将页表再分页。原先的逻辑地址是
页码 + 页偏移
,现在页表分层后,变为 页码 + 页偏移 + 页偏移
2、哈希页表
处理超过32位地址空间的常用方法是使用哈希页表。
哈希页表的条目包括一个链表的元素,每个元素有3个域:1)虚拟页码 2)物理帧号 3)指向链表中下一个元素的指针。其中虚拟页码作为哈希值。(所谓虚拟页码,应该就是页码、页号)
算法主要是凭页码得到哈希值,在哈希表中获得对应条目,找到物理帧号。
3、反向页表
为避免页表条目是否必须,都罗列在页表中,从而造成页表臃肿庞大的毛病,也可以采用反向页表。真正使用的帧才在反向页表中有一个条目。整个系统只有一张页表,对每个物理内存帧只有一个条目,条目包含对应逻辑内存页的地址,及进程号等。主要的问题是内存共享有点麻烦。
六、分段
分页的问题是用户视角的内存和实际物理内存分离,逻辑内存需要映射到物理内存。
但对于我们程序员来说,内存是一个集合,里面有各种变量、对象,通过名字、指针来调用,而不关心它们到底位于什么位置。
分段(segmentation)就是一种支持这种用户视角的内存管理方案。逻辑地址空间由一组段组成。与分页比较,其最大的不同是不固定大小。其余根据段号 + 偏移来获得物理地址,与分页似乎并无大的不同。
进程的地址空间被划分为若干个段,每个段定义了一组逻辑信息。例程序段、数据段等。每个段都从0开始编址,并采用一段连续的地址空间。因此,分段方案中,逻辑地址是二维的。
七、分页与分段的主要区别
分页和分段有许多相似之处,比如两者都不要求作业连续存放.但在概念上两者完全不同,主要表现在以下几个方面:
(1)页是信息的物理单位,分页是为了实现非连续分配,以便解决内存碎片问题,或者说分页是由于系统管理的需要.段是信息的逻辑单位,它含有一组意义相对完整的信息,分段的目的是为了更好地实现共享,满足用户的需要.
(2)页的大小固定,由系统确定,将逻辑地址划分为页号和页内地址是由机器硬件实现的.而段的长度却不固定,决定于用户所编写的程序,通常由编译程序在对源程序进行编译时根据信息的性质来划分.
(3)分页的作业地址空间是一维的.分段的地址空间是二维的.
打个比方,比如说你去听课,带了一个纸质笔记本做笔记。笔记本有100张纸,课程有语文、数学、英语三门,对于这个笔记本的使用,为了便于以后复习方便,你可以有两种选择。
第一种是,你从本子的第一张纸开始用,并且事先在本子上做划分:第2张到第30张纸记语文笔记,第31到60张纸记数学笔记,第61到100张纸记英语笔记,最后在第一张纸做个列表,记录着三门笔记各自的范围。这就是分段管理,第一张纸叫段表。
第二种是,你从第二张纸开始做笔记,各种课的笔记是连在一起的:第2张纸是数学,第3张是语文,第4张英语……最后呢,你在第一张纸做了一个目录,记录着语文笔记在第3、7、14、15张纸……,数学笔记在第2、6、8、9、11……,英语笔记在第4、5、12……。这就是分页管理,第一张纸叫页表。你要复习哪一门课,就到页表里查寻相关的纸的编号,然后翻到那一页去复习
四.段页式存储管理
1.基本思想:
分页系统能有效地提高内存的利用率,而分段系统能反映程序的逻辑结构,便于段的共享与保护,将分页与分段两种存储方式结合起来,就形成了段页式存储管理方式。
在段页式存储管理系统中,作业的地址空间首先被分成若干个逻辑分段,每段都有自己的段号,然后再将每段分成若干个大小相等的页。对于主存空间也分成大小相等的页,主存的分配以页为单位。
参考文章:
http://blog.sina.com.cn/s/blog_4692ea0a0101j4ss.html
版权声明:本文为博主原屙文章,喜欢你就担走。