因为看ogre的别人的日志偶然看到描述内存泄露的内容,发现自己也对于内存管理不是很清楚,遂写此文,防止以后遗忘。
内存管理——是指软件 运行的时候对计算机内存资源进行分配和使用的技术,主要目的是如何高效、快速的分配,并且在适当时候释放和回收内存资源。
内存可以通过许多没接实现,例如磁带或者是硬盘,或者是小阵列容量的微芯片,从1950年代开始,计算机变得更复杂,甚至必须在一台机器同时执行多个进程。
物理内存——就是插在主板上的内存条,它是固定的,内存条的容量多大,物理内存就有多大(集成显卡系统除外),但是如果程序运行很多或者程序本身很大的话,就会导致大量的物理内存占用,甚至导致物理内存消耗殆尽。
虚拟内存——虚拟内存就是在硬盘上面划分一块页面文件,充当内存。当程序在运行的时候,有一部分资源还没有用上或者同时打开几个程序却只能操作其中一个程序时,系统没必要将程序所有的资源都塞在物理内存中,所以紫铜个暂时将这些不用的资源放在虚拟内存里面,等到需要的时候再调用。
物理地址(physical address)——用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。内存并不是通过物理地址的方式来寻址的,(虽然可以直接把物理地址理解成插在机器上的内存本身,把内存看成一个从0直接一直到最大空间逐字节编号的大数组,然后把这个数组叫做物理地址),说它是“与地址总线相对应”还更贴切一些。
逻辑地址(logical address)——是指由程序产生的与段相关的偏移地址部分。例如,你在进行c语言指针编程的过程中的&操作,取得的就是逻辑地址,它是相对于你当前进程数据段的地址,不和绝对物理地址相干。只有在Intel实模式下,逻辑地址才和物理地址相等(因为实模式没有分段或者分页,cpu不进行自动地址转换),逻辑地址也就是在Intel保护模式下程序执行代码段限长内的偏移地址,(假定代码段、数据段如果完全一样)。应用程序员仅仅需要与逻辑地址打交道,而分段和分页是对程序员完全透明的,仅仅由系统编程人员涉及。应用程序员虽然自己可以直接操作内存,也只能在操作系统给你分配的那段内存来操作。
Intel为了兼容,保留了之前的段式内存管理方式,逻辑地址是机器语言指令用用来指定一个操作数或者是一条指令的地址,一个逻辑地址=段标识符+段内偏移量。
线性地址(linear address)/虚拟地址(virtual address)——与逻辑地址类似。它也是一个不真实的地址,如果逻辑地址是对应的硬件平台段式管理转换前地址,那么线性地址就对应了硬件页式内存的转换前地址。
Ps:cpu工作的三个模式(从Intel开发80386以后的计算机都具有)
1) 实模式(实地址工作模式|real mode)——是最基本的工作方式,实地址模式与16位微处理器8086/8088的实地址模式保持兼容,原有的16位微处理器的程序不加任何修改就可以再80486的微处理器实地址模式下面运行,80486处理器的实地址模式具有更强的功能,增加了寄存器,扩充了指令,可以进行32位操作。
8086/8088只能工作于实模式,80286及以上的微处理器可以工作于实模式、保护模式和虚拟8086模式。
实模式操作方式只允许微处理器寻址第一个1MB存储空间,存储器中第一个1MB存储单元成为实模式存储器或者常规内存,Dos操作系统要求微处理器工作于实模式,当80486微处理器工作于实地址模式时,存储器的管理方式与8086微处理器存储器的管理方式完全相同。
2) 保护模式(保护工作模式|protected vrirtual address mode)——特点是引入了虚拟内存的概念,同时可以使用附加的指令集,所以80486支持多任务操作,在保护模式下,80486微处理器可访问的物理存储空间为4GB(232),程序可用的虚拟存储空间为64TB(246)。
保护模式通常是为了防止下列情况的发生:
a. 应用程序破坏系统程序;
b. 某一应用程序破坏了其它应用程序;
c. 错误地把数据当做程序运行。
保护模式下的存储器寻址(80286及其以上的微处理器)允许访问第一个1MB及其以上的存储器内的数据和程序,寻址这个扩展的存储器段,需要更改用于实模式存储器寻址的段基址加上偏移地址的机制
在保护模式下,当寻址扩展内存里的数据和程序时,仍然使用偏移地址访问位于存储段内的信息。区别是,实模式下的段基址由段寄存器提供,而保护模式下的段寄存器里存放着一个选择符(selector),用于选择描述表内的一个描述符,描述符(descriptor)描述存储器段的位置、长度和访问权限。由于段基址加偏移地址仍然用于访问第一个1MB存储器内的数据,所以保护模式下的指令和实模式下的指令完全相同。
保护模式和实地址模式的不同之处在于存储地址空间的扩大(由1MB扩展到4GB),以及存储器管理机制的不同。
3) 虚拟8086工作模式——80486微处理器允许在实地址模式和虚拟8086(virtual 8086 mode)模式下执行8086的应用程序,虚拟8086模式为系统设计人员提供了80486微处理器保护模式的全部功能,因而具有更大的灵活性,有了虚拟8086模式,允许80486微处理器同时执行8086操作系统和8086应用程序,以及80486微处理器操作系统和80486微处理器的应用程序。因此,在一台多用户的80486微处理器的计算机里,多个用户可以同时使用计算机。
在虚拟8086模式下,还可以用与实地址模式相同的形式应用段寄存器,而形成现行基地址。通过使用分页功能,就可以把虚拟8086模式下的1MB地址空间映像到80486微处理器的4GB物理空间中的任何位置。
每个进程有4GB的虚拟地址空间,每个空间都做了如下划分:
a. 一部分映射物理内存
b. 一部分映射硬盘上的交换文件
c. 一部分什么也不做
程序中都是使用4GB的虚拟地址,访问物理内存需要使用物理地址,物理地址是放在寻址总线上的地址,以字节(8位)为单位。
Cpu将一个虚拟内存空间中地址转换为物理地址,需要进行两步:
1. 将给定的一个逻辑地址(即段内偏移量)利用cpu的段式内存管理单元,转换成一个线性地址,
2. 然后利用其页式内存管理单元,转换为最后的物理地址。
虚拟地址由操作系统维护,由mmu(内存管理单元)可以进行转换,扩大了内存空间分页管理。大多数使用虚拟存储器的系统都使用一种称为分页(Paging)机制。采用分页机制的系统,虚拟地址空间以页面为单位进行划分,虚拟地址空间会被划分为多个等大小的页面。物理地址空间也按照页面为单位进行划分每一块成为页帧,或者页框。每一虚拟页面可以随意对应到物理页框,也可以对应到磁盘的页面文件上。(按照IA32的分页机制来说,标准页面大小为4k。
以下面的指令作为例子:
Mov指令: mov eax,[0]
此时虚拟地址0将被发给MMU,MMU发现0属于页面0的范围内,如果页面0对应的页框号为1,那么物理地址就在4096-8191范围内,此时就会将4096发送到地址总线上。因为虚拟地址0的页内偏移也是0(页内偏移:在页面里的位置,比如1,页面偏移死1,4097的页面偏移也是1,这是因为一个页面大小为4K,用虚拟地址mod 4K就得到了页内偏移
上面的是虚拟地址到物理地址的映射的简单情况,但是在记录这些页面到页框的映射关系的时候(当然有些处理器是页框到页面的转化),在IA处理器上面使用的是页表,就是在物理内存里面有一块连续的空间,来记录这些页面到页框的映射关系,每一个页表项里面都有一部分去指向页框的起始地址,还有部分记录了这个页面的属性。可以通过页面号来做索引。页面号就是虚拟地址/4k得到的整数部分。如果只是单一的页表,也是有问题的,如果虚拟地址空间过大,那么页表所占的空间也会很大,这时候可以采用多级页表。IA32在采用4K页面的时候就使用了2级页表,IA64使用了四级。
使用了分页机制以后,4G的地址空间被分成了固定大小的页,每一页或者被映射到物理内存,或者被映射到硬盘上的交换文件中,或者没有映射任何东西,对于一般程序员来说,4G的地址空间,只有一小部分映射了物理内存,很大部分都是没有映射任何东西的。物理内存也被分页,来映射地址空间。对于32bit的win2k,页的大小是4k字节。CPU用来把虚拟地址转化成物理地址的信息存放在叫做页目录和页表的结构里。
物理内存分页,一个物理页的大小为4K字节,第0个物理页从物理地址0X00000000处开始,由于页的大小是4kb,就是0x1000字节,所以第一页从物理地址0X00001000处开始,第2页从物理地址0X00002000处开始。可以看到由于页的大小是4kb,所以需要32bit的地址中高20bit来寻址物理页。(4G/4k=2^20)
页目录:一个页目录大小为4K字节(2^20),放在一个物理页中.由1024个4字节的页目录组成(因为是win32),页目录中的每一项内容(每项4个字节)高20bit用来放一个页表(页表放在一个物理页中)的物理地址,低12bit放着一些标志。
页表:一个页表的大小为4K字节,放在一个物理页中,由1024个4字节的页表项组成,页表项的大小为4字节(32bit),所以页表中有1024个页表项,页表中每一项的内容(每项4个字节,32bit)高20bit用来放一个物理页的物理地址,低12bit放着一些标志。
对于x86系统,页目录的物理地址放在cpu的CR3寄存器中。
一个虚拟地址大小为4字节,其中包含找到物理地址的信息,虚拟地址分为3个部分:
1) 31-22位(10位)是页目录中的索引;
2) 21-12位(10位)是页表中的索引;
3) 11-0位(12位)是页内偏移
转换过程:
首先通过CR3找到页目录所在的物理页——》根据虚拟地址中的31-22找到该页目录项——》通过该页目录项找到该虚拟地址对应的页表地址——》根据虚拟地址21-12找到物理页的物理地址——》根据虚拟地址的11-0位作为位偏移加上该物理页的地址就找到了该虚拟地址对应的物理地址。
CPU把虚拟地址转换成物理地址:一个虚拟地址,大小4个字节(32bit),包含着找到物理地址的信息,分为3个部分:第22位到第31位这10位(最高10位)是页目录中的索引,第12位到第21位这10位是页表中的索引,第0位到第11位这12位(低12位)是页内偏移。对于一个要转换成物理地址的虚拟地址,CPU首先根据CR3中的值,找到页目录所在的物理页。然后根据虚拟地址的第22位到第31位这10位(最高的10bit)的值作为索引,找到相应的页目录项(PDE,page director entry ),页表项中就有这个虚拟地址所对应物理页的物理地址。最后用虚拟地址的最低12位,也就是页内偏移,加上物理页的物理地址,就得到了该虚拟地址所对应的物理地址。
一个页目录有1024项,虚拟地址最高的10bit刚好可以索引1024项(2的10次方等于1024)。一个页表也有1024项,虚拟地址中间部分的10bit,刚好索引1024项。虚拟地址最低的10bit(2的12次方等于4096,作为页内偏移,刚好可以索引4kb,也就是一个物理页中的每个字节)。
每个进程都有自己的4G空间,从0X00000000~0XFFFFFFFF。通过每个进程自己的一套页目录和页表来实现。由于每个进程有自己的页目录和页表,所以每个进程的地址空间映射的物理内存是不一样的。两个进程的同一个虚拟地址处(如果都有物理内存映射)的值一班是不同的,因为ietamen往往对应不同的物理页。
4G地址空间中低2G,0X00000000-0X7FFFFFFF是用户地址空间,4G地址空间中高2G,即0X80000000-0XFFFFFFFF是系统地址空间。访问地址空间需要程序有ring()的权限。
Windows中,我们接触的一般都是线性地址,而其并非真实存在的,而真正的物理地址是利用一段N长的数组来定位的,这样能够完成保护模式。
假设我们没有使用线性地址,那么我们可以直接访问物理地址,但是这样我们在往内存中写东西的时候操作系统更无法检查这块内存是否是可写的。这样会造成非法覆盖等,最后操作系统的内核函数可能就被覆盖了。
由于操作系统安全的需要催生了虚拟地址的应用,在CPU中有MMU(Memory Manager Unit 内存管理单元),专门负责线性地址和物理地址之间的转化,在我们每次读写内存的时候,从CPU的结构来看都要经过ALU,ALU拿到虚拟地址以后就会通过总线传输给MMU转化成物理地址以后再把数据读入寄存器。
虚拟内存:
编程时我们面对的都是虚拟地址,win32中对于每个进程来说都拥有4G的虚拟内存(4G虚拟内存中,高2G内存属于内核部分,是所有进程共有的,低2G内存数据时进程独有的,每个进程低2G内存都不一样),但是需要注意虚拟地址并不是真正存在的,所以不构成任何资源损失,比如我们要在)X80000000的地方写“sga”的时候,操作系统就会将这个虚拟地址映射到一块物理地址A中,写这块虚拟地址就相当于写入物理地址A,但是假如我们申请一段1Kb的虚拟内存空间,并未读写,系统是不会分配任何物理内存的,只有当虚拟内存要使用的时候操作系统才会分配相应的物理空间。
虚拟内存的管理方式(通过一堆数据结构来实现内存管理)
在EPROCESS中有个数据结构如下:
内存空间: typedef struct _MADDRESS_SPACE { PMEMORY_AREA MemoryAreaRoot ; //这个指针指向一颗二叉排序树,主要是这个情况下采用二叉排序树能加快内存的搜索速度 ... ... ... }MADDRESS_SPACE , *PMADDRESS_SPACE ;
段结构节点(程序段的内存分配): typedef struct _MEMORY_AREA { PVOID StartingAddress ; //虚拟内存段的起始地址 PVOID EndingAddress ; //虚拟内存段的结束地址 struct _MEMORY_AREA *Parent ; //该节点的老爹 struct _MEMORY_AREA *LeftChild ; //该节点的左儿子 struct _MEMORY_AREA *RrightChild ; //该节点的左儿子 ... ... ...
}MEMORY_AREA , *PMEMORY_AREA ;
这个节点内主要记录了已分配的虚拟内存空间,如果要申请虚拟内存空间就可以来此处创建一个节点,如果要删除空间同样只需要把对应节点删除,但是其中还要设计平衡二叉树的操作。我们在分配虚拟内存空间的时候系统就会找到这棵树,然后通过一定的算法便利此树,找到符合条件的内存间隙(未被分配的内存空间),创建个节点挂到这棵树上,返回起始地址就完成了。
物理内存:
物理内存的管理是基于一个数组的,win32下分页是4kb一页,假设我们物理内存有4GB,那么windows会将这4GB空间分页,分成4GB/4KB=1M页,那么每页(就是4KB)的物理空间都由一个叫PHYSICAL_PAGE的数据结构管理,这个数据结构可以看做是一个数组,这个数组有1M个元素,整好覆盖了4GB的物理地址。
物理内存的管理
在内核中有3个队列,这些队列内的元素就是PHYSICAL_PAGE结构:
A. 已分配的内存队列:存放正在被使用的内存;
B. 待清理的内存队列:存放已经被释放的内存,到那时这些内存还没有被清理(清零)
C. 空闲队列:存放可以使用的内存
系统管理流程:
1) 每隔一段时间,系统就会自动从B队列中提取队列元素进行清理,然后放入C队列中;
2) 每当释放物理内存的时候,系统将要释放的内存从A队列中提取出来,放入B队列中
3) 申请内存的时候,系统将要分配的内存从C队列中提取出来,放入A队列中
映射
主要要了解虚拟内存到物理内存的映射,在win32中,虚拟内存有32bit的地址总线,在CPU中存在个CR3寄存器,里面存放了每个进程的页目录地址
我们将转换过程分成下面几步看
1) 根据CR3(CR3中的值是物理地址)的值我们可以定位到当前进程的页目录基址,然后通过虚拟地址的高10位作偏移量来获得指定的PDE(page directory entry),PED内容有4字节,高20位部分用来做页表基址,剩下的比特位用来实现权限控制和判断页是否在内存中之类的事,系统只要检测相应的比特位就可以实现内存的权限控制。
2) 通过PDE提供的基址加上虚拟内存中的10位(21-12)做偏移量就找到了页表PTE(page table entry)地址,然后PTE的高20位就是物理内存的基址了(其实就是PHYSICAL_PAGE的下标),剩下的比特位同样用于访问控制。
3) 通过虚拟内存的低12位加上PTE中高20位作为基址就可以确定唯一的物理内存了。
CPU段式内存管理,逻辑地址如何转换为线性地址
一个逻辑地址由两部分组成,段标识符:段内偏移量,。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号,后面3位包含一些硬件细节:
最后两位涉及权限检查
索引号——直接可以理解成数组下标,它是“段描述符”的索引,段描述符具体地址描述了一个段,这样,可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就代表了一个段的描述,每一个段描述符由8个字节组成:
上面的描述符虽然很复杂,但是我们需要利用一个数据结构来定义它,不过,在这里只需要关心Base字段,它描述了一个段开始位置的线性地址。
Intel设计的本意是,一些全局的段描述符,就放在“全局段描述表(GDT)”中,一些局部的,例如每个进程自己的,就放在所谓的“局部段描述表(LDT)”中,在段选择符的T1字段中=0,表示用GDT,=1,表示用LDT。
GDT在内存中的地址和大小存放在CPU的gdtr控制寄存器中,而LDT则在ldtr寄存器中。如下可看出:
首先,给定一个完整的逻辑地址[段选择符:段偏移地址]
1. 看段选择符的T1=0还是1,知道当前要转换的是GDT中的段,还是LDT中的段,再根据相应的寄存器,得到其地址和大小,我们就有一个数组了。
2. 拿出段选择符中的前13位,可以在这个数组中,找到对应的段描述符,这样,它的base,即基地址就知道了。
3. 把Base+offset,就是要转换的线性地址了。
CPU页式内存管理
CPU的页式内存管理单元,负责把一个线性地址,最终翻译为一个物理地址。从管理和效率的角度出发,线性地址被分为以固定长度为单位的组,成为页(page),例如一个32位的机器,线性地址最大可为4G,可以用4KB位一个页来划分,这个页,作为一个线性地址就被划分为一个total_page[2^20]的大数组,共有2的20个次方个页,这个大数组我们称之为页目录。目录中的每一个目录项,就是一个地址一一对应的页的地址。
另一类“页“,我们称之为物理页,或者是页框,是分页段元把所有的物理内存也划分为固定长度的管理单位,它的长度一般与内存页是一一对应的。
这里注意到,total_page数组有2^20个成员,每个成员是一个地址(32位机,一个地址也就是4字节),那么要单单表示这么一个数组,就要占去4MB的内存空间,为了节省空间,引入了一个二级管理模式的机器来组织分页单元:
如上图:
1. 分页单元中,页目录是唯一的,它的地址放在CPU的cr3就成年期中,是进行地址转换的开始点;
2. 每一个活动的进程,因为都有其独立对应的虚拟内存(页目录也是唯一的),那么它也对应了而一个独立的页目录地址。(运行一个进程,需要将它的页目录地址放到cr3寄存器中,将别的进程的地址保存);
3. 每一个32位的线性地址被划分为三部分,面目录索引(10位):页表索引(10位):偏移(12位)。依据以下的步骤进行转换:
1) 从cr3中取出进程的页目录地址(操作系统负责在调度进程的时候,把这个地址装入对应寄存器);
2) 根据线性地址前十位,在数组中,找到对应的索引项,因为引入了二级管理模式,页目录中的项,不再是页的地址,而是一个页表的地址。(又引入了一个数组),页的地址被放到页表中去了。
3) 根据线性地址的中间十位,在页表(也是数组)中找到页的起始地址;
4) 将页的起始地址与线性地址中最后12位相加,得到最终我们想要的内存块
页置换算法(主要是查看是否缺页或者调度)
Win32通过一个两层的表结构来实现地址映射,因为每个进程都拥有私有的4G的虚拟内存空间,相应的,每个进程都有自己的层次表结构来实现其地址映射。 第一层称为页目录,实际就是一个内存页,Win32的内存页有4KB大小,这个内存页以4个字节分为1024项,每一项称为“页目录项”(PDE); 第二层称为页表,这一层共有1024个页表,页表结构与页目录相似,每个页表也都是一个内存页,这个内存页以4KB的大小被分为1024项,页表的每一项被称为页表项(PTE),易知共有1024×1024个页表项。每一个页表项对应一个物理内存中的某一个“内存页”,即共有1024×1024个物理内存页,每个物理内存页为4KB,这样就可以索引到4G大小的虚拟物理内存。如下图所示(注下图中的页目录项和页表项的大小应该是4个字节,而不是4kB):
Win32提供了4GB大小的虚拟地址空间。因此每个虚拟地址都是一个32位的整数值,也就是我们平时所说的指针,即指针的大小为4B。它由三部分组成,如下图:
这三个部分的第一部分,即前10位为页目录下标,用来寻址页目录项,页目录项刚好1024个。找到页目录项后,找对页目录项对应的的页表。第二部分则是用来在页表内寻址,用来找到页表项,共有1024个页表项,通过页表项找到物理内存页。第三部分用来在物理内存页中找到对应的字节,一个页的大小是4KB,12位刚好可以满足寻址要求。
具体的例子:假设一个线程正在访问一个指针(Win32的指针指的就是虚拟地址)指向的数据,此指针指为0x2A8E317F,下图表示了这一个过程:
0x2A8E317F的二进制写法为0010101010_0011100011_000101111111,为了方便我们把它分为三个部分。首先按照0010101010寻址,找到页目录项。因为一个页目录项为4KB,那么先将0010101010左移两位,001010101000(0x2A8),用此下标找到页目录项,然后根据此页目录项定位到下一层的某个页表。
然后按照0011100011寻址,在上一步找到页表中寻找页表项。寻址方法与上述方法类似。找到页表项后,就可以找到对应的物理内存页。最后按照000101111111寻址,寻找页内偏移。 上面的假设的是此数据已在物理内存中,其实判断访问的数据是否在内存中也是在地址映射过程中完成的。Win32系统总是假设数据已在物理内存中,并进行地址映射。页表项中有一位标志位,用来标识包含此数据的页是否在物理内存中,如果在的话,就直接做地址映射,否则,抛出缺页中断,此时页表项也可标识包含此数据的页是否在调页文件中(外存),如果不在则访问违例,程序将会退出,如果在,页表项会查出此数据页在哪个调页文件中,然后将此数据页调入物理内存,再继续进行地址映射。为了实现每个进程拥有私有4G的虚拟地址空间,也就是说每个进程都拥有自己的页目录和页表结构,对不同进程而言,即使是相同的指针(虚拟地址)被不同的进程映射到的物理地址也是不同的,这也意味着在进程之间传递指针是没有意义的。
存储方式(分段/分页)
分段机制是必须的,分页机制是可选的,当不使用分页的时候线性地址将直接映射为物理地址,设立分页机制的目的主要是为了实现虚拟存储(分页机制在后面介绍)。先来介绍一下分段机制,以下文字是介绍如何由逻辑地址转换为线性地址。
分段机制在保护模式中是不能被绕过得,回到我们的seg:offset地址结构,在保护模式中seg有个新名字叫做“段选择子”(seg..selector)。段选择子、GDT、LDT构成了保护模式的存储结构,GDT、LDT分别叫做全局描述符表和局部描述符表,描述符表是一个线性表(数组),表中存放的是描述符。“描述符”是保护模式中的一个新概念,它是一个8字节的数据结构,它的作用主要是描述一个段(还有其他作用以后再说),用描述表中记录的段基址加上逻辑地址(sel:offset)的offset转换成线性地址。描述符主要包括三部分:段基址(Base)、段限制(Limit)、段属性(Attr)。一个任务会涉及多个段,每个段需要一个描述符来描述,为了便于组织管理,80386及以后处理器把描述符组织成表,即描述符表。在保护模式中存在三种描述符表 “全局描述符表”(GDT)、“局部描述符表”(LDT)和中断描述符表(IDT)(IDT在以后讨论)。
(1)全局描述符表GDT(Global Descriptor Table)在整个系统中,全局描述符表GDT只有一张,GDT可以被放在内存的任何位置,但CPU必须知道GDT的入口,也就是基地址放在哪里,Intel的设计者门提供了一个寄存器GDTR用来存放GDT的入口地址,程序员将GDT设定在内存中某个位置之后,可以通过LGDT指令将GDT的入口地址装入此积存器,从此以后,CPU就根据此寄存器中的内容作为GDT的入口来访问GDT了。GDTR中存放的是GDT在内存中的基地址和其表长界限。
(2)段选择子(Selector)由GDTR访问全局描述符表是通过“段选择子”(实模式下的段寄存器)来完成的,如图三①步。段选择子是一个16位的寄存器(同实模式下的段寄存器相同)
段选择子包括三部分:描述符索引(index)、TI、请求特权级(RPL)。他的index(描述符索引)部分表示所需要的段的描述符在描述符表的位置,由这个位置再根据在GDTR中存储的描述符表基址就可以找到相应的描述符(如图三①步)。然后用描述符表中的段基址加上逻辑地址(SEL:OFFSET)的OFFSET就可以转换成线性地址(如图三②步),段选择子中的TI值只有一位0或1,0代表选择子是在GDT选择,1代表选择子是在LDT选择。请求特权级(RPL)则代表选择子的特权级,共有4个特权级(0级、1级、2级、3级)。例如给出逻辑地址:21h:12345678h转换为线性地址
a. 选择子SEL=21h=0000000000100 0 01b 他代表的意思是:选择子的index=4即100b选择GDT中的第4个描述符;TI=0代表选择子是在GDT选择;左后的01b代表特权级RPL=1 b. OFFSET=12345678h若此时GDT第四个描述符中描述的段基址(Base)为11111111h,则线性地址=11111111h+12345678h=23456789h(3)局部描述符表LDT(Local Descriptor Table)局部描述符表可以有若干张,每个任务可以有一张。我们可以这样理解GDT和LDT:GDT为一级描述符表,LDT为二级描述符表。如图五
LDT和GDT从本质上说是相同的,只是LDT嵌套在GDT之中。LDTR记录局部描述符表的起始位置,与GDTR不同LDTR的内容是一个段选择子。由于LDT本身同样是一段内存,也是一个段,所以它也有个描述符描述它,这个描述符就存储在GDT中,对应这个表述符也会有一个选择子,LDTR装载的就是这样一个选择子。LDTR可以在程序中随时改变,通过使用lldt指令。如图五,如果装载的是Selector 2则LDTR指向的是表LDT2。举个例子:如果我们想在表LDT2中选择第三个描述符所描述的段的地址12345678h。
1. 首先需要装载LDTR使它指向LDT2 使用指令lldt将Select2装载到LDTR 2. 通过逻辑地址(SEL:OFFSET)访问时SEL的index=3代表选择第三个描述符;TI=1代表选择子是在LDT选择,此时LDTR指向的是LDT2,所以是在LDT2中选择,此时的SEL值为1Ch(二进制为11 1 00b)。OFFSET=12345678h。逻辑地址为1C:12345678h 3. 由SEL选择出描述符,由描述符中的基址(Base)加上OFFSET可得到线性地址,例如基址是11111111h,则线性地址=11111111h+12345678h=23456789h
4. 此时若再想访问LDT1中的第三个描述符,只要使用lldt指令将选择子Selector 1装入再执行2、3两步就可以了(因为此时LDTR又指向了LDT1)由于每个进程都有自己的一套程序段、数据段、堆栈段,有了局部描述符表则可以将每个进程的程序段、数据段、堆栈段封装在一起,只要改变LDTR就可以实现对不同进程的段进行访问。存储方式是保护模式的基础,学习他主要注意与实模式下的存储模式的对比,总的思想就是首先通过段选择子在描述符表中找到相应段的描述符,根据描述符中的段基址首先确定段的位置,再通过OFFSET加上段基址计算出线性地址。
段页存储管理
产生原因
分页式:等分内存,但是需要仔细设计页面的大小,灵活性很低
分段式:支持了用户内存观点,反映了程序结构,但是粒度一般很大,碎片问题很明显
段页式存储管理优点:
对于用户来讲 按照段的逻辑划分程序
对于系统来讲,按页划分每一段
逻辑地址
管理
段表——记录了每一段的页表开始地址和页表长度;
页表——记录了逻辑页号与内存块号的对应关系,每一段有一个,一个程序可能有多个页表
空块管理:同页式管理
分配:同页式管理
硬件支持
1) 段表始址寄存器
2) 段表长度寄存器
3) 相联存储器(快表)
PS:感谢红黑联盟的图片和相关资料