本文内容参考自以下博客与书籍:html
http://blog.csdn.net/windowseight/article/details/8279863前端
http://bbs.chinaunix.net/thread-2083672-1-1.html程序员
http://blog.csdn.net/erazy0/article/details/6457626#comments编程
http://blog.csdn.net/drshenlei/article/details/4261909windows
http://duartes.org/gustavo/blog/post/memory-translation-and-segmentation数组
《操做系统概念》第六版中文版,高等教育出版社,第九章,郑扣根译。数据结构
《Operating System Concepts》7th Edition, 高等教育出版社。app
文中不少处是根据本身理解所写,如有错误,欢迎指出和探讨。ide
1. 物理地址和逻辑地址函数
物理地址:加载到内存地址寄存器中的地址,内存单元的真正地址。在前端总线上传输的内存地址都是物理内存地址,编号从0开始一直到可用物理内存的最高端。这些数字被北桥(Nortbridge chip)映射到实际的内存条上。物理地址是明确的、最终用在总线上的编号,没必要转换,没必要分页,也没有特权级检查(no translation, no paging, no privilege checks)。
逻辑地址:CPU所生成的地址。逻辑地址是内部和编程使用的、并不惟一。例如,你在进行C语言指针编程中,能够读取指针变量自己值(&操做),实际上这个值就是逻辑地址,它是相对于你当前进程数据段的地址(偏移地址),不和绝对物理地址相干。
为何会有这两种地址?
我的觉的缘由在于逻辑地址分配更加灵活,能够容许不惟一,看起来也较为直观,例如,一段代码中分配数组,逻辑地址上是连续的,然而在物理地址上,这个数组所占用的页可能分散开来,物理地址上就是不连续的,这样对程序的可理解性上有影响。另外,有了逻辑地址这个概念,才能使用虚拟内存技术。
2. Paging,分页内存管理方案
(1) 分页的最大做用就在于:使得进程的物理地址空间能够是非连续的。
物理内存被划分为一小块一小块,每块被称为帧(Frame)。分配内存时,帧是分配时的最小单位,最少也要给一帧。在逻辑内存中,与帧对应的概念就是页(Page)。
逻辑地址的表示方式是:前部分是页码后部分是页偏移。
例如,已知逻辑空间地址为2^m个字节(也就是说逻辑地址的长度是m位),已知页大小是2^n字节。那么一共能够有2^(m-n)个页。所以页码部分会占m-n位,以后的n位,用来存储页偏移。
举个例子, 页大小为4B,而逻辑内存为32B(8页),逻辑地址0的页号为0,页号0对应帧5,所以逻辑地址映射为物理地址5*4+0=20。逻辑地址3映射物理地址5*4+3=23。逻辑地址13(4*3+1,页号为3,偏移为1,所以帧号为2),映射到物理地址9。
采用分页技术不会产生外部碎片(内存都被划分为帧),但可能产生内部碎片(帧已是最小单元,所以帧内部可能有空间没有用到)。
按几率计算下来,每一个进程平都可有半个帧大小的内部碎片。
(2) 页表的硬件实现
上一小节中写到页表是逻辑地址转化到物理地址的关键所在。那么页表如何存储?
每一个操做系统都有本身的方法来保存页表。绝大多数都会为每一个进程分配一个页表。如今因为页表都比较大,因此放在内存中(以往是放在一组专用寄存器里),其指针存在进程控制块(PCB)里,当进程被调度程序选中投入运行时,系统将其页表指针从进程控制块中取出并送入用户寄存器中。随后能够根据此首地址访问页表。
页表的存储方式是TBL(Translation look-aside buffer, 翻译后备缓冲器)+内存。TBL其实是一组硬件缓冲所关联的快速内存。若没有TBL,操做系统须要两次内存访问来完成逻辑地址到物理地址的转换,访问页表算一次,在页表中查找算一次。TBL中存储页表中的一小部分条目,条目以键值对方式存储。
(3) 页表的数据结构
a.
今年是2013年,现有的笔记本电脑,内存地址空间通常为2^32字节以上。对于具备32位逻辑地址空间的计算机系统,若是系统的页大小为4KB(2^12B),那么页表能够拥有2^(32-12)个,也就是一百多万个条目,假设每一个条目占有4B,那每一个进程都须要4MB的物理地址空间来存放页表自己。并且,页表自己须要分配在连续内存中。
为此,Hierarchical Paging(层次化分页)被提出,实际上就是将页号分为两部分,第一部分做为索引,第二部分做为页号的偏移。
以一个4kb页大小的32位系统为例。一个逻辑地址被分为20位的页码和12位的页偏移。由于要对页表进行再分页,因此该页号可分为10位的页码和10位的页偏移。这样一个逻辑地址就表示以下形式:
地址转换过程以下:
地址由外向内转换,所以此方法也被称为forward-mapped page table(向前映射表)。
b. Hashed Page Tables 哈希页表
处理超过32位地址空间的经常使用方法是使用hashed page table(哈希页表),并以虚拟页码做为哈希值。哈希页表的每一条目都包括一个链表的元素,这些元素哈希成同一位置。每一个元素有三个域:虚拟页码,所映射的帧号,指向链表中下一个元素的指针。
我的看来,哈希页表的地址转换方式,其实是Chaining(连接)方式,也就是一种哈希函数的溢出处理方式(另外一种溢出处理方式叫作Open Addressing,开放寻址),具体过程以下:
逻辑地址须要大于32bit的地址空间来表示,可是操做系统仍只有32bit来表示地址。此时人们便想到虚拟页地址,虚拟地址能够在32bit表示范围以内,而后利用哈希函数完成逻辑地址到虚拟地址的映射,因为虚拟地址更少,哈希函数会出现溢出,这里使用Chaining来解决溢出。
逻辑地址中的页号(下图中的p)通过哈希函数的计算,算出虚拟地址中的页号,根据虚拟页号能够在哈希表中以O(1)方式寻址,用p与链表中的每个元素的第一个域相比较。若是匹配,那么相应的帧号就用来造成物理地址。若是不匹配,就对链表中的下一个节点进行比较,以寻找一个匹配的页号。
c. Inverted page table 反向页表
时间关系,这段暂时略过。
3. Segmentation,分段内存管理方案
采用分页内存管理有一个不可避免的问题:用户视角的内存和实际内存的分离。设想一段main函数代码,里面包含Sqrt函数的调用。按照编写者的理解,这段代码运行时,操做系统应该分配内存给:符号表(编译时使用),栈(存放局部变量与函数参数值),Sqrt代码段,主函数代码段等。这样,编写者就能够方便地指出:"函数sqrt内存模块的第五条指令",来定位一个元素。而实际上,因为采用Paging的管理方式,全部的一切都只是散落在物理内存中的各个帧上,并非以编写者的理解来划分模块。
Segmentation的内存管理方式能够支持这种思路。逻辑地址空间由一组段组成。每一个段都有名字和长度。地址指定了段名称和段内偏移。所以用户经过两个量来指定地址:段名称和偏移。段是编号的,经过段号而非段名称来引用。所以逻辑地址由有序对构成:
<segment-number,offset>(<段号s, 段内偏移d>)
段偏移d因该在0和段界限之间,若是合法,那么就与基地址相加而获得所需字节在物理内存中的地址。所以段表是一组基地址和界限寄存器对。
例以下图,有5个段,编号0~4,例如段2为400B开始于位置4300,对段2第53字节的引用映射成位置4300+53=4353。而段0字节1222的引用则会触发地址错误,由于该段的仅为1000B长(界限为1000)。
4. 合并分段和分页的管理方案
在现有的Intel兼容计算机(x86)上,采用的内存管理方案是分段和分页合并的管理方案。
在这个方案中,逻辑地址,如前一节中所说,是由一个段标识符加上一个指定段内相对地址的偏移量,表示为 [段标识符:段内偏移量]。
这样的逻辑地址转换的过程是怎样呢?以下图所示:
当CPU要执行一条引用了内存地址的指令时,转换过程就开始了。第一步是把逻辑地址转换成线性地址。可是,为何不跳过这一步,而让软件直接使用线性地址(或物理地址呢?)缘由主要是由于:
(1) Intel的更新是渐进式而非革命式,新的处理器须要兼容和保留过往的设置。具体的缘由,博文Memory Translation and Segmentation (http://blog.csdn.net/drshenlei/article/details/4261909) 中讲的较为清楚。
(2) 如上节所说,采用段内存管理,能够跟方便地进行地址保护(同一类型的地址逻辑地址在一块儿)。
下面讲逻辑地址到线性地址的部分。
在IBM OS/2 32位版本的操做系统,和Intel 386的环境下。操做系统采用的内存分配方式就是分段和分页合并的方式。
逻辑地址的其实是一对<选择符,偏移>。
选择符的内容以下:
从左开始,13位是索引(或者称为段号),经过这个索引,能够定位到段描述符(segment descriptor),而段描述符是能够真正记载了有关一个段的位置和大小信息, 以及访问控制的状态信息。段描述符通常由8个字节组成。因为8B较大,而Intel为了保持向后兼容,将段寄存器仍然规定为16-bit(尽管每一个段寄存器事实上有一个64-bit长的不可见部分,但对于程序员来讲,段寄存器就是16-bit的),那么很明显,咱们没法经过16-bit长度的段寄存器来直接引用64-bit的段描述符。所以在逻辑地址中,只用13bit记录其索引。而真正的段描述符,被放于数组之中。
这个内存中的数组就叫作GDT(Global Descriptor Table,全局描述表),Intel的设计者门提供了一个寄存器GDTR用来存放GDT的入口地址。程序员将GDT设定在内存中某个位置以后,能够经过LGDT指令将GDT的入口地址装入此寄存器,今后之后,CPU就根据此寄存器中的内容做为GDT的入口来访问GDT了。
除了GDT以外,还有LDT(Local Descriptor Table,本地描述表),但与GDT不一样的是,LDT在系统中能够存在多个,每一个进程能够拥有本身的LDT。LDT的内存地址在LDTR寄存器中。
在以前图中的TI位,就是用来表示此索引所指向的段描述符是存于全局描述表中,仍是本地描述表中。=0,表示用GDT,=1表示用LDT。
RPL位,占2bit,是保护信息位,尚未仔细了解过这一块,暂时先不写。
找到,段描述符后,加上偏移量,即是线性地址。转换过程以下:
在Intel 386的环境下,线性地址转换为物理地址的过程,和第二节分页式内存管理中,层次分页中,逻辑地址转换为物理地址的方法相似。以下图。
Intel 80386的地址转换全过程以下图:
内存管理部分是操做系统的核心功能之一,此次将理论部分整理出来,一是为了复习,二也是为了提纲挈领地为深刻学习操做系统作准备。
文中的图片均非本人原创,主要来自文章开头所引用的博文,以及参考书籍中的图片。如有侵权行为,请指出,博主将尽快移除。